summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorKiyoshi Aman <kiyoshi.aman+adelie@gmail.com>2019-02-01 22:55:37 +0000
committerKiyoshi Aman <kiyoshi.aman+adelie@gmail.com>2019-02-03 18:22:05 -0600
commit5b57d28ffb6e1ef86b50f7d05d977826eae89bfe (patch)
tree154a22fe556b49e6927197336f8bf91b12eacd5e
downloaduserland-5b57d28ffb6e1ef86b50f7d05d977826eae89bfe.tar.gz
userland-5b57d28ffb6e1ef86b50f7d05d977826eae89bfe.tar.bz2
userland-5b57d28ffb6e1ef86b50f7d05d977826eae89bfe.tar.xz
userland-5b57d28ffb6e1ef86b50f7d05d977826eae89bfe.zip
initial population
-rw-r--r--LICENSE29
-rw-r--r--README.md22
-rw-r--r--bin/cat/cat.1217
-rw-r--r--bin/cat/cat.c321
-rw-r--r--bin/chgrp/chgrp.1164
-rw-r--r--bin/chgrp/chown.8181
-rw-r--r--bin/chgrp/chown.c284
-rw-r--r--bin/chmod/chmod.1313
-rw-r--r--bin/chmod/chmod.c239
-rw-r--r--bin/chown/chgrp.1169
-rw-r--r--bin/chown/chown.8184
-rw-r--r--bin/chown/chown.c302
-rw-r--r--bin/cp/cp.1263
-rw-r--r--bin/cp/cp.c548
-rw-r--r--bin/cp/extern.h61
-rw-r--r--bin/cp/utils.c419
-rw-r--r--bin/date/date.1262
-rw-r--r--bin/date/date.c364
-rw-r--r--bin/date/extern.h39
-rw-r--r--bin/date/netdate.c200
-rw-r--r--bin/dd/args.c504
-rw-r--r--bin/dd/conv.c283
-rw-r--r--bin/dd/conv_tab.c287
-rw-r--r--bin/dd/dd.1588
-rw-r--r--bin/dd/dd.c616
-rw-r--r--bin/dd/dd.h126
-rw-r--r--bin/dd/dd_hostops.c53
-rw-r--r--bin/dd/dd_rumpops.c52
-rw-r--r--bin/dd/extern.h86
-rw-r--r--bin/dd/misc.c342
-rw-r--r--bin/dd/position.c185
-rw-r--r--bin/df/df.1206
-rw-r--r--bin/df/df.c520
-rw-r--r--bin/echo/echo.171
-rw-r--r--bin/echo/echo.c81
-rw-r--r--bin/ed/POSIX103
-rw-r--r--bin/ed/README23
-rw-r--r--bin/ed/buf.c319
-rw-r--r--bin/ed/cbc.c460
-rw-r--r--bin/ed/ed.1979
-rw-r--r--bin/ed/ed.h265
-rw-r--r--bin/ed/glbl.c213
-rw-r--r--bin/ed/io.c358
-rw-r--r--bin/ed/main.c1433
-rw-r--r--bin/ed/re.c146
-rw-r--r--bin/ed/sub.c262
-rw-r--r--bin/ed/test/=.err1
-rw-r--r--bin/ed/test/Makefile28
-rw-r--r--bin/ed/test/README32
-rw-r--r--bin/ed/test/TODO17
-rw-r--r--bin/ed/test/a.d5
-rw-r--r--bin/ed/test/a.r8
-rw-r--r--bin/ed/test/a.t9
-rw-r--r--bin/ed/test/a1.err3
-rw-r--r--bin/ed/test/a2.err3
-rw-r--r--bin/ed/test/addr.d9
-rw-r--r--bin/ed/test/addr.r2
-rw-r--r--bin/ed/test/addr.t5
-rw-r--r--bin/ed/test/addr1.err1
-rw-r--r--bin/ed/test/addr2.err1
-rw-r--r--bin/ed/test/ascii.dbin0 -> 256 bytes
-rw-r--r--bin/ed/test/ascii.rbin0 -> 256 bytes
-rw-r--r--bin/ed/test/ascii.t0
-rw-r--r--bin/ed/test/bang1.d0
-rw-r--r--bin/ed/test/bang1.err1
-rw-r--r--bin/ed/test/bang1.r1
-rw-r--r--bin/ed/test/bang1.t5
-rw-r--r--bin/ed/test/bang2.err1
-rw-r--r--bin/ed/test/c.d5
-rw-r--r--bin/ed/test/c.r4
-rw-r--r--bin/ed/test/c.t12
-rw-r--r--bin/ed/test/c1.err3
-rw-r--r--bin/ed/test/c2.err3
-rwxr-xr-xbin/ed/test/ckscripts.sh37
-rw-r--r--bin/ed/test/d.d5
-rw-r--r--bin/ed/test/d.err1
-rw-r--r--bin/ed/test/d.r1
-rw-r--r--bin/ed/test/d.t3
-rw-r--r--bin/ed/test/e1.d1
-rw-r--r--bin/ed/test/e1.err1
-rw-r--r--bin/ed/test/e1.r1
-rw-r--r--bin/ed/test/e1.t1
-rw-r--r--bin/ed/test/e2.d1
-rw-r--r--bin/ed/test/e2.err1
-rw-r--r--bin/ed/test/e2.r1
-rw-r--r--bin/ed/test/e2.t1
-rw-r--r--bin/ed/test/e3.d1
-rw-r--r--bin/ed/test/e3.err1
-rw-r--r--bin/ed/test/e3.r1
-rw-r--r--bin/ed/test/e3.t1
-rw-r--r--bin/ed/test/e4.d1
-rw-r--r--bin/ed/test/e4.r1
-rw-r--r--bin/ed/test/e4.t1
-rw-r--r--bin/ed/test/f1.err1
-rw-r--r--bin/ed/test/f2.err1
-rw-r--r--bin/ed/test/g1.d5
-rw-r--r--bin/ed/test/g1.err1
-rw-r--r--bin/ed/test/g1.r15
-rw-r--r--bin/ed/test/g1.t6
-rw-r--r--bin/ed/test/g2.d5
-rw-r--r--bin/ed/test/g2.err1
-rw-r--r--bin/ed/test/g2.r1
-rw-r--r--bin/ed/test/g2.t2
-rw-r--r--bin/ed/test/g3.d5
-rw-r--r--bin/ed/test/g3.err1
-rw-r--r--bin/ed/test/g3.r5
-rw-r--r--bin/ed/test/g3.t4
-rw-r--r--bin/ed/test/g4.d5
-rw-r--r--bin/ed/test/g4.r7
-rw-r--r--bin/ed/test/g4.t13
-rw-r--r--bin/ed/test/g5.d3
-rw-r--r--bin/ed/test/g5.r9
-rw-r--r--bin/ed/test/g5.t2
-rw-r--r--bin/ed/test/h.err1
-rw-r--r--bin/ed/test/i.d5
-rw-r--r--bin/ed/test/i.r8
-rw-r--r--bin/ed/test/i.t9
-rw-r--r--bin/ed/test/i1.err3
-rw-r--r--bin/ed/test/i2.err3
-rw-r--r--bin/ed/test/i3.err3
-rw-r--r--bin/ed/test/j.d5
-rw-r--r--bin/ed/test/j.r4
-rw-r--r--bin/ed/test/j.t2
-rw-r--r--bin/ed/test/k.d5
-rw-r--r--bin/ed/test/k.r5
-rw-r--r--bin/ed/test/k.t10
-rw-r--r--bin/ed/test/k1.err1
-rw-r--r--bin/ed/test/k2.err1
-rw-r--r--bin/ed/test/k3.err1
-rw-r--r--bin/ed/test/k4.err6
-rw-r--r--bin/ed/test/l.d0
-rw-r--r--bin/ed/test/l.r0
-rw-r--r--bin/ed/test/l.t0
-rw-r--r--bin/ed/test/m.d5
-rw-r--r--bin/ed/test/m.err4
-rw-r--r--bin/ed/test/m.r5
-rw-r--r--bin/ed/test/m.t7
-rwxr-xr-xbin/ed/test/mkscripts.sh75
-rw-r--r--bin/ed/test/n.d0
-rw-r--r--bin/ed/test/n.r0
-rw-r--r--bin/ed/test/n.t0
-rw-r--r--bin/ed/test/nl.err1
-rw-r--r--bin/ed/test/nl1.d5
-rw-r--r--bin/ed/test/nl1.r8
-rw-r--r--bin/ed/test/nl1.t8
-rw-r--r--bin/ed/test/nl2.d5
-rw-r--r--bin/ed/test/nl2.r6
-rw-r--r--bin/ed/test/nl2.t4
-rw-r--r--bin/ed/test/p.d0
-rw-r--r--bin/ed/test/p.r0
-rw-r--r--bin/ed/test/p.t0
-rw-r--r--bin/ed/test/q.d0
-rw-r--r--bin/ed/test/q.r0
-rw-r--r--bin/ed/test/q.t5
-rw-r--r--bin/ed/test/q1.err1
-rw-r--r--bin/ed/test/r1.d5
-rw-r--r--bin/ed/test/r1.err1
-rw-r--r--bin/ed/test/r1.r7
-rw-r--r--bin/ed/test/r1.t3
-rw-r--r--bin/ed/test/r2.d5
-rw-r--r--bin/ed/test/r2.err1
-rw-r--r--bin/ed/test/r2.r10
-rw-r--r--bin/ed/test/r2.t1
-rw-r--r--bin/ed/test/r3.d1
-rw-r--r--bin/ed/test/r3.r2
-rw-r--r--bin/ed/test/r3.t1
-rw-r--r--bin/ed/test/s1.d5
-rw-r--r--bin/ed/test/s1.err1
-rw-r--r--bin/ed/test/s1.r5
-rw-r--r--bin/ed/test/s1.t6
-rw-r--r--bin/ed/test/s10.err4
-rw-r--r--bin/ed/test/s2.d5
-rw-r--r--bin/ed/test/s2.err4
-rw-r--r--bin/ed/test/s2.r5
-rw-r--r--bin/ed/test/s2.t4
-rw-r--r--bin/ed/test/s3.d0
-rw-r--r--bin/ed/test/s3.err1
-rw-r--r--bin/ed/test/s3.r1
-rw-r--r--bin/ed/test/s3.t6
-rw-r--r--bin/ed/test/s4.err1
-rw-r--r--bin/ed/test/s5.err1
-rw-r--r--bin/ed/test/s6.err1
-rw-r--r--bin/ed/test/s7.err5
-rw-r--r--bin/ed/test/s8.err4
-rw-r--r--bin/ed/test/s9.err4
-rw-r--r--bin/ed/test/t.d5
-rw-r--r--bin/ed/test/t.r16
-rw-r--r--bin/ed/test/t1.d5
-rw-r--r--bin/ed/test/t1.err1
-rw-r--r--bin/ed/test/t1.r16
-rw-r--r--bin/ed/test/t1.t3
-rw-r--r--bin/ed/test/t2.d5
-rw-r--r--bin/ed/test/t2.err1
-rw-r--r--bin/ed/test/t2.r6
-rw-r--r--bin/ed/test/t2.t1
-rw-r--r--bin/ed/test/u.d5
-rw-r--r--bin/ed/test/u.err1
-rw-r--r--bin/ed/test/u.r9
-rw-r--r--bin/ed/test/u.t31
-rw-r--r--bin/ed/test/v.d5
-rw-r--r--bin/ed/test/v.r11
-rw-r--r--bin/ed/test/v.t6
-rw-r--r--bin/ed/test/w.d5
-rw-r--r--bin/ed/test/w.r10
-rw-r--r--bin/ed/test/w.t2
-rw-r--r--bin/ed/test/w1.err1
-rw-r--r--bin/ed/test/w2.err1
-rw-r--r--bin/ed/test/w3.err1
-rw-r--r--bin/ed/test/x.err1
-rw-r--r--bin/ed/test/z.err2
-rw-r--r--bin/ed/undo.c150
-rw-r--r--bin/expr/expr.1280
-rw-r--r--bin/expr/expr.y467
-rw-r--r--bin/kill/kill.1156
-rw-r--r--bin/kill/kill.c320
-rw-r--r--bin/ln/ln.1320
-rw-r--r--bin/ln/ln.c365
-rw-r--r--bin/ls/cmp.c199
-rw-r--r--bin/ls/extern.h53
-rw-r--r--bin/ls/ls.1516
-rw-r--r--bin/ls/ls.c715
-rw-r--r--bin/ls/ls.h81
-rw-r--r--bin/ls/main.c50
-rw-r--r--bin/ls/print.c497
-rw-r--r--bin/ls/util.c168
-rw-r--r--bin/mkdir/mkdir.197
-rw-r--r--bin/mkdir/mkdir.c223
-rw-r--r--bin/mv/mv.1149
-rw-r--r--bin/mv/mv.c427
-rw-r--r--bin/mv/pathnames.h40
-rw-r--r--bin/pax/ar_io.c1725
-rw-r--r--bin/pax/ar_subs.c1449
-rw-r--r--bin/pax/buf_subs.c1022
-rw-r--r--bin/pax/cpio.1307
-rw-r--r--bin/pax/cpio.c1134
-rw-r--r--bin/pax/cpio.h149
-rw-r--r--bin/pax/dumptar.c131
-rw-r--r--bin/pax/extern.h326
-rw-r--r--bin/pax/file_subs.c1156
-rw-r--r--bin/pax/ftree.c741
-rw-r--r--bin/pax/ftree.h48
-rw-r--r--bin/pax/gen_subs.c437
-rw-r--r--bin/pax/getoldopt.c92
-rw-r--r--bin/pax/options.c2229
-rw-r--r--bin/pax/options.h116
-rw-r--r--bin/pax/pat_rep.c1139
-rw-r--r--bin/pax/pat_rep.h51
-rw-r--r--bin/pax/pax.11304
-rw-r--r--bin/pax/pax.c492
-rw-r--r--bin/pax/pax.h283
-rw-r--r--bin/pax/sel_subs.c617
-rw-r--r--bin/pax/sel_subs.h69
-rw-r--r--bin/pax/tables.c1379
-rw-r--r--bin/pax/tables.h176
-rw-r--r--bin/pax/tar.1372
-rw-r--r--bin/pax/tar.c1430
-rw-r--r--bin/pax/tar.h154
-rw-r--r--bin/pax/tty_subs.c200
-rw-r--r--bin/ps/extern.h99
-rw-r--r--bin/ps/fmt.c60
-rw-r--r--bin/ps/keyword.c415
-rw-r--r--bin/ps/nlist.c234
-rw-r--r--bin/ps/print.c1413
-rw-r--r--bin/ps/ps.1706
-rw-r--r--bin/ps/ps.c947
-rw-r--r--bin/ps/ps.h104
-rw-r--r--bin/pwd/pwd.1110
-rw-r--r--bin/pwd/pwd.c143
-rw-r--r--bin/rm/rm.1215
-rw-r--r--bin/rm/rm.c611
-rw-r--r--bin/rmdir/rmdir.195
-rw-r--r--bin/rmdir/rmdir.c125
-rw-r--r--bin/sh/TOUR357
-rw-r--r--bin/sh/USD.doc/Makefile12
-rw-r--r--bin/sh/USD.doc/Rv7man405
-rw-r--r--bin/sh/USD.doc/referargs8
-rw-r--r--bin/sh/USD.doc/t.mac69
-rw-r--r--bin/sh/USD.doc/t1553
-rw-r--r--bin/sh/USD.doc/t2971
-rw-r--r--bin/sh/USD.doc/t3976
-rw-r--r--bin/sh/USD.doc/t4180
-rw-r--r--bin/sh/alias.c314
-rw-r--r--bin/sh/alias.h48
-rw-r--r--bin/sh/arith_token.c262
-rw-r--r--bin/sh/arith_tokens.h120
-rw-r--r--bin/sh/arithmetic.c502
-rw-r--r--bin/sh/arithmetic.h42
-rw-r--r--bin/sh/bltin/bltin.h105
-rw-r--r--bin/sh/bltin/echo.1109
-rw-r--r--bin/sh/bltin/echo.c122
-rw-r--r--bin/sh/builtins.def98
-rw-r--r--bin/sh/cd.c463
-rw-r--r--bin/sh/cd.h34
-rw-r--r--bin/sh/error.c378
-rw-r--r--bin/sh/error.h120
-rw-r--r--bin/sh/eval.c1680
-rw-r--r--bin/sh/eval.h87
-rw-r--r--bin/sh/exec.c1183
-rw-r--r--bin/sh/exec.h78
-rw-r--r--bin/sh/expand.c2125
-rw-r--r--bin/sh/expand.h71
-rw-r--r--bin/sh/funcs/cmv43
-rw-r--r--bin/sh/funcs/dirs67
-rw-r--r--bin/sh/funcs/kill43
-rw-r--r--bin/sh/funcs/login32
-rw-r--r--bin/sh/funcs/newgrp31
-rw-r--r--bin/sh/funcs/popd67
-rw-r--r--bin/sh/funcs/pushd67
-rw-r--r--bin/sh/funcs/suspend35
-rw-r--r--bin/sh/histedit.c576
-rw-r--r--bin/sh/init.h39
-rw-r--r--bin/sh/input.c695
-rw-r--r--bin/sh/input.h69
-rw-r--r--bin/sh/jobs.c1812
-rw-r--r--bin/sh/jobs.h105
-rw-r--r--bin/sh/machdep.h47
-rw-r--r--bin/sh/mail.c144
-rw-r--r--bin/sh/mail.h37
-rw-r--r--bin/sh/main.c393
-rw-r--r--bin/sh/main.h42
-rw-r--r--bin/sh/memalloc.c334
-rw-r--r--bin/sh/memalloc.h79
-rw-r--r--bin/sh/miscbltin.c458
-rw-r--r--bin/sh/miscbltin.h31
-rw-r--r--bin/sh/mkbuiltins136
-rwxr-xr-xbin/sh/mkinit.sh224
-rwxr-xr-xbin/sh/mknodenames.sh69
-rwxr-xr-xbin/sh/mknodes.sh242
-rw-r--r--bin/sh/mkoptions.sh198
-rw-r--r--bin/sh/mktokens99
-rw-r--r--bin/sh/myhistedit.h48
-rw-r--r--bin/sh/mystring.c140
-rw-r--r--bin/sh/mystring.h45
-rw-r--r--bin/sh/nodes.c.pat210
-rw-r--r--bin/sh/nodetypes149
-rw-r--r--bin/sh/option.list79
-rw-r--r--bin/sh/options.c631
-rw-r--r--bin/sh/options.h72
-rw-r--r--bin/sh/output.c755
-rw-r--r--bin/sh/output.h109
-rw-r--r--bin/sh/parser.c2756
-rw-r--r--bin/sh/parser.h169
-rw-r--r--bin/sh/redir.c982
-rw-r--r--bin/sh/redir.h55
-rw-r--r--bin/sh/sh.14549
-rw-r--r--bin/sh/shell.h224
-rw-r--r--bin/sh/show.c1175
-rw-r--r--bin/sh/show.h46
-rw-r--r--bin/sh/syntax.c111
-rw-r--r--bin/sh/syntax.h98
-rw-r--r--bin/sh/trap.c837
-rw-r--r--bin/sh/trap.h52
-rw-r--r--bin/sh/var.c1587
-rw-r--r--bin/sh/var.h151
-rw-r--r--bin/sh/version.h36
-rw-r--r--bin/sleep/sleep.1177
-rw-r--r--bin/sleep/sleep.c229
-rw-r--r--bin/stty/cchar.c148
-rw-r--r--bin/stty/extern.h51
-rw-r--r--bin/stty/gfmt.c132
-rw-r--r--bin/stty/key.c332
-rw-r--r--bin/stty/modes.c224
-rw-r--r--bin/stty/print.c257
-rw-r--r--bin/stty/stty.1635
-rw-r--r--bin/stty/stty.c168
-rw-r--r--bin/stty/stty.h62
-rw-r--r--bin/test/TEST.csh138
-rw-r--r--bin/test/test.1383
-rw-r--r--bin/test/test.c904
-rw-r--r--usr.bin/asa/asa.185
-rw-r--r--usr.bin/asa/asa.c116
-rw-r--r--usr.bin/at/at.1339
-rw-r--r--usr.bin/at/at.c758
-rw-r--r--usr.bin/at/at.h43
-rw-r--r--usr.bin/at/panic.c110
-rw-r--r--usr.bin/at/panic.h37
-rw-r--r--usr.bin/at/parsetime.c652
-rw-r--r--usr.bin/at/parsetime.h35
-rw-r--r--usr.bin/at/pathnames.h51
-rw-r--r--usr.bin/at/perm.c118
-rw-r--r--usr.bin/at/perm.h33
-rw-r--r--usr.bin/at/privs.c104
-rw-r--r--usr.bin/at/privs.h76
-rw-r--r--usr.bin/at/stime.c116
-rw-r--r--usr.bin/at/stime.h53
-rw-r--r--usr.bin/basename/basename.192
-rw-r--r--usr.bin/basename/basename.c103
-rw-r--r--usr.bin/c99/c99.181
-rw-r--r--usr.bin/c99/c99.sh2
-rw-r--r--usr.bin/cal/README42
-rw-r--r--usr.bin/cal/cal.1164
-rw-r--r--usr.bin/cal/cal.c924
-rw-r--r--usr.bin/cksum/cksum.1341
-rw-r--r--usr.bin/cksum/cksum.c551
-rw-r--r--usr.bin/cksum/crc.c162
-rw-r--r--usr.bin/cksum/crc_extern.h38
-rw-r--r--usr.bin/cksum/extern.h88
-rw-r--r--usr.bin/cksum/md2.c21
-rw-r--r--usr.bin/cksum/md4.c21
-rw-r--r--usr.bin/cksum/md5.c153
-rw-r--r--usr.bin/cksum/print.c76
-rw-r--r--usr.bin/cksum/rmd160.c25
-rw-r--r--usr.bin/cksum/sha1.c21
-rw-r--r--usr.bin/cksum/sha256.c25
-rw-r--r--usr.bin/cksum/sha384.c25
-rw-r--r--usr.bin/cksum/sha512.c25
-rw-r--r--usr.bin/cksum/sum1.c76
-rw-r--r--usr.bin/cksum/sum2.c80
-rw-r--r--usr.bin/cmp/cmp.1122
-rw-r--r--usr.bin/cmp/cmp.c168
-rw-r--r--usr.bin/cmp/extern.h44
-rw-r--r--usr.bin/cmp/misc.c84
-rw-r--r--usr.bin/cmp/regular.c118
-rw-r--r--usr.bin/cmp/special.c111
-rw-r--r--usr.bin/comm/comm.1102
-rw-r--r--usr.bin/comm/comm.c213
-rw-r--r--usr.bin/compress/compress.1171
-rw-r--r--usr.bin/compress/compress.c459
-rw-r--r--usr.bin/compress/doc/NOTES139
-rw-r--r--usr.bin/compress/doc/README283
-rw-r--r--usr.bin/compress/doc/revision.log116
-rw-r--r--usr.bin/compress/zopen.3138
-rw-r--r--usr.bin/compress/zopen.c699
-rw-r--r--usr.bin/csplit/csplit.1163
-rw-r--r--usr.bin/csplit/csplit.c479
-rw-r--r--usr.bin/ctags/C.c573
-rw-r--r--usr.bin/ctags/ctags.1225
-rw-r--r--usr.bin/ctags/ctags.c275
-rw-r--r--usr.bin/ctags/ctags.h92
-rw-r--r--usr.bin/ctags/fortran.c174
-rw-r--r--usr.bin/ctags/lisp.c112
-rw-r--r--usr.bin/ctags/print.c121
-rw-r--r--usr.bin/ctags/test/ctags.test69
-rw-r--r--usr.bin/ctags/tree.c145
-rw-r--r--usr.bin/ctags/yacc.c160
-rw-r--r--usr.bin/cut/cut.1126
-rw-r--r--usr.bin/cut/cut.c306
-rw-r--r--usr.bin/cut/x_cut.c95
-rw-r--r--usr.bin/dirname/dirname.c86
-rw-r--r--usr.bin/du/du.1168
-rw-r--r--usr.bin/du/du.c362
-rw-r--r--usr.bin/env/env.1122
-rw-r--r--usr.bin/env/env.c99
-rw-r--r--usr.bin/expand/expand.187
-rw-r--r--usr.bin/expand/expand.c182
-rw-r--r--usr.bin/false/false.156
-rw-r--r--usr.bin/false/false.sh2
-rw-r--r--usr.bin/find/extern.h102
-rw-r--r--usr.bin/find/find.1974
-rw-r--r--usr.bin/find/find.c306
-rw-r--r--usr.bin/find/find.h138
-rw-r--r--usr.bin/find/function.c2025
-rw-r--r--usr.bin/find/ls.c124
-rw-r--r--usr.bin/find/main.c164
-rw-r--r--usr.bin/find/misc.c151
-rw-r--r--usr.bin/find/operator.c272
-rw-r--r--usr.bin/find/option.c194
-rw-r--r--usr.bin/fold/fold.193
-rw-r--r--usr.bin/fold/fold.c248
-rw-r--r--usr.bin/gencat/gencat.1233
-rw-r--r--usr.bin/gencat/gencat.c879
-rw-r--r--usr.bin/getconf/getconf.194
-rw-r--r--usr.bin/getconf/getconf.c342
-rw-r--r--usr.bin/grep/fastgrep.c336
-rw-r--r--usr.bin/grep/file.c276
-rw-r--r--usr.bin/grep/grep.1486
-rw-r--r--usr.bin/grep/grep.c722
-rw-r--r--usr.bin/grep/grep.h162
-rw-r--r--usr.bin/grep/nls/C.msg13
-rw-r--r--usr.bin/grep/nls/es_ES.ISO8859-1.msg14
-rw-r--r--usr.bin/grep/nls/gl_ES.ISO8859-1.msg14
-rw-r--r--usr.bin/grep/nls/hu_HU.ISO8859-2.msg14
-rw-r--r--usr.bin/grep/nls/ja_JP.SJIS.msg14
-rw-r--r--usr.bin/grep/nls/ja_JP.UTF-8.msg14
-rw-r--r--usr.bin/grep/nls/ja_JP.eucJP.msg14
-rw-r--r--usr.bin/grep/nls/pt_BR.ISO8859-1.msg14
-rw-r--r--usr.bin/grep/nls/ru_RU.KOI8-R.msg14
-rw-r--r--usr.bin/grep/nls/uk_UA.UTF-8.msg13
-rw-r--r--usr.bin/grep/nls/zh_CN.UTF-8.msg14
-rw-r--r--usr.bin/grep/queue.c116
-rw-r--r--usr.bin/grep/util.c500
-rw-r--r--usr.bin/head/head.195
-rw-r--r--usr.bin/head/head.c204
-rw-r--r--usr.bin/iconv/iconv.1122
-rw-r--r--usr.bin/iconv/iconv.c236
-rw-r--r--usr.bin/id/groups.159
-rw-r--r--usr.bin/id/id.1136
-rw-r--r--usr.bin/id/id.c370
-rw-r--r--usr.bin/id/whoami.158
-rw-r--r--usr.bin/ipcrm/ipcrm.197
-rw-r--r--usr.bin/ipcrm/ipcrm.c313
-rw-r--r--usr.bin/ipcs/ipcs.1141
-rw-r--r--usr.bin/ipcs/ipcs.c678
-rw-r--r--usr.bin/join/join.1206
-rw-r--r--usr.bin/join/join.c637
-rw-r--r--usr.bin/ldd/Makefile.common16
-rw-r--r--usr.bin/ldd/Makefile.elf10
-rw-r--r--usr.bin/ldd/build/Makefile42
-rw-r--r--usr.bin/ldd/dummy.c3
-rw-r--r--usr.bin/ldd/elf32/Makefile33
-rw-r--r--usr.bin/ldd/elf32_compat/Makefile26
-rw-r--r--usr.bin/ldd/elf64/Makefile51
-rw-r--r--usr.bin/ldd/ldd.1126
-rw-r--r--usr.bin/ldd/ldd.c237
-rw-r--r--usr.bin/ldd/ldd.h47
-rw-r--r--usr.bin/ldd/ldd_elfxx.c269
-rw-r--r--usr.bin/locale/locale.1113
-rw-r--r--usr.bin/locale/locale.c707
-rw-r--r--usr.bin/logger/logger.1117
-rw-r--r--usr.bin/logger/logger.c203
-rw-r--r--usr.bin/logname/logname.172
-rw-r--r--usr.bin/logname/logname.c85
-rw-r--r--usr.bin/m4/NOTES64
-rw-r--r--usr.bin/m4/PSD.doc/Makefile10
-rw-r--r--usr.bin/m4/TEST/ack.m441
-rw-r--r--usr.bin/m4/TEST/hanoi.m446
-rw-r--r--usr.bin/m4/TEST/hash.m456
-rw-r--r--usr.bin/m4/TEST/math.m4182
-rw-r--r--usr.bin/m4/TEST/sqroot.m446
-rw-r--r--usr.bin/m4/TEST/string.m446
-rw-r--r--usr.bin/m4/TEST/test.m4244
-rw-r--r--usr.bin/m4/eval.c1053
-rw-r--r--usr.bin/m4/expr.c50
-rw-r--r--usr.bin/m4/extern.h190
-rw-r--r--usr.bin/m4/gnum4.c817
-rw-r--r--usr.bin/m4/lib/ohash.h73
-rw-r--r--usr.bin/m4/lib/ohash_create_entry.c38
-rw-r--r--usr.bin/m4/lib/ohash_delete.c31
-rw-r--r--usr.bin/m4/lib/ohash_do.c111
-rw-r--r--usr.bin/m4/lib/ohash_entries.c26
-rw-r--r--usr.bin/m4/lib/ohash_enum.c36
-rw-r--r--usr.bin/m4/lib/ohash_init.3229
-rw-r--r--usr.bin/m4/lib/ohash_init.c41
-rw-r--r--usr.bin/m4/lib/ohash_int.h23
-rw-r--r--usr.bin/m4/lib/ohash_interval.390
-rw-r--r--usr.bin/m4/lib/ohash_interval.c36
-rw-r--r--usr.bin/m4/lib/ohash_lookup_interval.c68
-rw-r--r--usr.bin/m4/lib/ohash_lookup_memory.c64
-rw-r--r--usr.bin/m4/lib/ohash_qlookup.c27
-rw-r--r--usr.bin/m4/lib/ohash_qlookupi.c29
-rw-r--r--usr.bin/m4/lib/strtonum.c73
-rw-r--r--usr.bin/m4/look.c315
-rw-r--r--usr.bin/m4/m4.1522
-rw-r--r--usr.bin/m4/main.c811
-rw-r--r--usr.bin/m4/mdef.h237
-rw-r--r--usr.bin/m4/misc.c414
-rw-r--r--usr.bin/m4/parser.y86
-rw-r--r--usr.bin/m4/pathnames.h40
-rw-r--r--usr.bin/m4/stdd.h54
-rw-r--r--usr.bin/m4/tokenizer.l108
-rw-r--r--usr.bin/m4/trace.c203
-rw-r--r--usr.bin/make/Makefile.boot45
-rw-r--r--usr.bin/make/PSD.doc/Makefile10
-rw-r--r--usr.bin/make/PSD.doc/tutorial.ms3794
-rw-r--r--usr.bin/make/arch.c1369
-rw-r--r--usr.bin/make/buf.c291
-rw-r--r--usr.bin/make/buf.h119
-rw-r--r--usr.bin/make/compat.c778
-rw-r--r--usr.bin/make/cond.c1436
-rw-r--r--usr.bin/make/config.h160
-rw-r--r--usr.bin/make/dir.c1849
-rw-r--r--usr.bin/make/dir.h108
-rw-r--r--usr.bin/make/for.c496
-rw-r--r--usr.bin/make/hash.c466
-rw-r--r--usr.bin/make/hash.h149
-rw-r--r--usr.bin/make/job.c3070
-rw-r--r--usr.bin/make/job.h274
-rw-r--r--usr.bin/make/lst.h189
-rw-r--r--usr.bin/make/lst.lib/Makefile10
-rw-r--r--usr.bin/make/lst.lib/lstAppend.c122
-rw-r--r--usr.bin/make/lst.lib/lstAtEnd.c79
-rw-r--r--usr.bin/make/lst.lib/lstAtFront.c76
-rw-r--r--usr.bin/make/lst.lib/lstClose.c86
-rw-r--r--usr.bin/make/lst.lib/lstConcat.c185
-rw-r--r--usr.bin/make/lst.lib/lstDatum.c77
-rw-r--r--usr.bin/make/lst.lib/lstDeQueue.c87
-rw-r--r--usr.bin/make/lst.lib/lstDestroy.c101
-rw-r--r--usr.bin/make/lst.lib/lstDupl.c107
-rw-r--r--usr.bin/make/lst.lib/lstEnQueue.c78
-rw-r--r--usr.bin/make/lst.lib/lstFind.c74
-rw-r--r--usr.bin/make/lst.lib/lstFindFrom.c90
-rw-r--r--usr.bin/make/lst.lib/lstFirst.c77
-rw-r--r--usr.bin/make/lst.lib/lstForEach.c76
-rw-r--r--usr.bin/make/lst.lib/lstForEachFrom.c125
-rw-r--r--usr.bin/make/lst.lib/lstInit.c85
-rw-r--r--usr.bin/make/lst.lib/lstInsert.c122
-rw-r--r--usr.bin/make/lst.lib/lstInt.h105
-rw-r--r--usr.bin/make/lst.lib/lstIsAtEnd.c87
-rw-r--r--usr.bin/make/lst.lib/lstIsEmpty.c75
-rw-r--r--usr.bin/make/lst.lib/lstLast.c77
-rw-r--r--usr.bin/make/lst.lib/lstMember.c77
-rw-r--r--usr.bin/make/lst.lib/lstNext.c120
-rw-r--r--usr.bin/make/lst.lib/lstOpen.c87
-rw-r--r--usr.bin/make/lst.lib/lstPrev.c79
-rw-r--r--usr.bin/make/lst.lib/lstRemove.c136
-rw-r--r--usr.bin/make/lst.lib/lstReplace.c78
-rw-r--r--usr.bin/make/lst.lib/lstSucc.c79
-rw-r--r--usr.bin/make/main.c2189
-rw-r--r--usr.bin/make/make.12413
-rw-r--r--usr.bin/make/make.c1555
-rw-r--r--usr.bin/make/make.h535
-rw-r--r--usr.bin/make/make_malloc.c119
-rw-r--r--usr.bin/make/make_malloc.h41
-rw-r--r--usr.bin/make/meta.c1641
-rw-r--r--usr.bin/make/meta.h56
-rw-r--r--usr.bin/make/metachar.c88
-rw-r--r--usr.bin/make/metachar.h61
-rw-r--r--usr.bin/make/nonints.h198
-rw-r--r--usr.bin/make/parse.c3357
-rw-r--r--usr.bin/make/pathnames.h53
-rw-r--r--usr.bin/make/sprite.h116
-rw-r--r--usr.bin/make/str.c526
-rw-r--r--usr.bin/make/strlist.c93
-rw-r--r--usr.bin/make/strlist.h62
-rw-r--r--usr.bin/make/suff.c2676
-rw-r--r--usr.bin/make/targ.c846
-rw-r--r--usr.bin/make/trace.c116
-rw-r--r--usr.bin/make/trace.h49
-rw-r--r--usr.bin/make/unit-tests/Makefile139
-rw-r--r--usr.bin/make/unit-tests/comment.exp5
-rw-r--r--usr.bin/make/unit-tests/comment.mk31
-rw-r--r--usr.bin/make/unit-tests/cond1.exp23
-rw-r--r--usr.bin/make/unit-tests/cond1.mk109
-rw-r--r--usr.bin/make/unit-tests/cond2.exp7
-rw-r--r--usr.bin/make/unit-tests/cond2.mk29
-rw-r--r--usr.bin/make/unit-tests/doterror.exp9
-rw-r--r--usr.bin/make/unit-tests/doterror.mk20
-rw-r--r--usr.bin/make/unit-tests/dotwait.exp30
-rw-r--r--usr.bin/make/unit-tests/dotwait.mk61
-rw-r--r--usr.bin/make/unit-tests/error.exp4
-rw-r--r--usr.bin/make/unit-tests/error.mk10
-rw-r--r--usr.bin/make/unit-tests/escape.exp104
-rw-r--r--usr.bin/make/unit-tests/escape.mk246
-rw-r--r--usr.bin/make/unit-tests/export-all.exp12
-rw-r--r--usr.bin/make/unit-tests/export-all.mk23
-rw-r--r--usr.bin/make/unit-tests/export-env.exp11
-rw-r--r--usr.bin/make/unit-tests/export-env.mk27
-rw-r--r--usr.bin/make/unit-tests/export.exp6
-rw-r--r--usr.bin/make/unit-tests/export.mk22
-rw-r--r--usr.bin/make/unit-tests/forloop.exp19
-rw-r--r--usr.bin/make/unit-tests/forloop.mk45
-rw-r--r--usr.bin/make/unit-tests/forsubst.exp2
-rw-r--r--usr.bin/make/unit-tests/forsubst.mk10
-rw-r--r--usr.bin/make/unit-tests/hash.exp9
-rw-r--r--usr.bin/make/unit-tests/hash.mk18
-rw-r--r--usr.bin/make/unit-tests/impsrc.exp13
-rw-r--r--usr.bin/make/unit-tests/impsrc.mk43
-rw-r--r--usr.bin/make/unit-tests/misc.exp1
-rw-r--r--usr.bin/make/unit-tests/misc.mk16
-rw-r--r--usr.bin/make/unit-tests/moderrs.exp16
-rw-r--r--usr.bin/make/unit-tests/moderrs.mk31
-rw-r--r--usr.bin/make/unit-tests/modmatch.exp20
-rw-r--r--usr.bin/make/unit-tests/modmatch.mk34
-rw-r--r--usr.bin/make/unit-tests/modmisc.exp10
-rw-r--r--usr.bin/make/unit-tests/modmisc.mk38
-rw-r--r--usr.bin/make/unit-tests/modorder.exp11
-rw-r--r--usr.bin/make/unit-tests/modorder.mk22
-rw-r--r--usr.bin/make/unit-tests/modts.exp39
-rw-r--r--usr.bin/make/unit-tests/modts.mk44
-rw-r--r--usr.bin/make/unit-tests/modword.exp122
-rw-r--r--usr.bin/make/unit-tests/modword.mk151
-rw-r--r--usr.bin/make/unit-tests/order.exp4
-rw-r--r--usr.bin/make/unit-tests/order.mk20
-rw-r--r--usr.bin/make/unit-tests/phony-end.exp6
-rw-r--r--usr.bin/make/unit-tests/phony-end.mk9
-rw-r--r--usr.bin/make/unit-tests/posix.exp23
-rw-r--r--usr.bin/make/unit-tests/posix.mk24
-rw-r--r--usr.bin/make/unit-tests/posix1.exp186
-rw-r--r--usr.bin/make/unit-tests/posix1.mk184
-rw-r--r--usr.bin/make/unit-tests/qequals.exp2
-rw-r--r--usr.bin/make/unit-tests/qequals.mk8
-rw-r--r--usr.bin/make/unit-tests/suffixes.exp35
-rw-r--r--usr.bin/make/unit-tests/suffixes.mk89
-rw-r--r--usr.bin/make/unit-tests/sunshcmd.exp4
-rw-r--r--usr.bin/make/unit-tests/sunshcmd.mk10
-rw-r--r--usr.bin/make/unit-tests/sysv.exp7
-rw-r--r--usr.bin/make/unit-tests/sysv.mk26
-rw-r--r--usr.bin/make/unit-tests/ternary.exp10
-rw-r--r--usr.bin/make/unit-tests/ternary.mk8
-rw-r--r--usr.bin/make/unit-tests/unexport-env.exp2
-rw-r--r--usr.bin/make/unit-tests/unexport-env.mk14
-rw-r--r--usr.bin/make/unit-tests/unexport.exp4
-rw-r--r--usr.bin/make/unit-tests/unexport.mk8
-rw-r--r--usr.bin/make/unit-tests/varcmd.exp11
-rw-r--r--usr.bin/make/unit-tests/varcmd.mk60
-rw-r--r--usr.bin/make/unit-tests/varmisc.exp25
-rw-r--r--usr.bin/make/unit-tests/varmisc.mk62
-rw-r--r--usr.bin/make/unit-tests/varquote.exp3
-rw-r--r--usr.bin/make/unit-tests/varquote.mk14
-rw-r--r--usr.bin/make/unit-tests/varshell.exp12
-rw-r--r--usr.bin/make/unit-tests/varshell.mk18
-rw-r--r--usr.bin/make/util.c494
-rw-r--r--usr.bin/make/var.c4355
-rw-r--r--usr.bin/man/man.1269
-rw-r--r--usr.bin/man/man.c1077
-rw-r--r--usr.bin/man/man.conf.5286
-rw-r--r--usr.bin/man/manconf.c272
-rw-r--r--usr.bin/man/manconf.h60
-rw-r--r--usr.bin/man/pathnames.h39
-rw-r--r--usr.bin/mesg/mesg.189
-rw-r--r--usr.bin/mesg/mesg.c108
-rw-r--r--usr.bin/mkfifo/mkfifo.186
-rw-r--r--usr.bin/mkfifo/mkfifo.c107
-rw-r--r--usr.bin/mkstr/mkstr.1131
-rw-r--r--usr.bin/mkstr/mkstr.c320
-rw-r--r--usr.bin/newgrp/grutil.c338
-rw-r--r--usr.bin/newgrp/grutil.h40
-rw-r--r--usr.bin/newgrp/newgrp.1121
-rw-r--r--usr.bin/newgrp/newgrp.c195
-rw-r--r--usr.bin/nice/nice.1120
-rw-r--r--usr.bin/nice/nice.c123
-rw-r--r--usr.bin/nl/nl.1214
-rw-r--r--usr.bin/nl/nl.c406
-rw-r--r--usr.bin/nohup/nohup.1115
-rw-r--r--usr.bin/nohup/nohup.c142
-rw-r--r--usr.bin/paste/paste.1116
-rw-r--r--usr.bin/paste/paste.c233
-rw-r--r--usr.bin/patch/backupfile.c254
-rw-r--r--usr.bin/patch/backupfile.h42
-rw-r--r--usr.bin/patch/common.h123
-rw-r--r--usr.bin/patch/inp.c516
-rw-r--r--usr.bin/patch/inp.h35
-rw-r--r--usr.bin/patch/mkpath.c84
-rw-r--r--usr.bin/patch/patch.1663
-rw-r--r--usr.bin/patch/patch.c1064
-rw-r--r--usr.bin/patch/pathnames.h14
-rw-r--r--usr.bin/patch/pch.c1600
-rw-r--r--usr.bin/patch/pch.h59
-rw-r--r--usr.bin/patch/util.c447
-rw-r--r--usr.bin/patch/util.h51
-rw-r--r--usr.bin/pathchk/pathchk.1123
-rw-r--r--usr.bin/pathchk/pathchk.c191
-rw-r--r--usr.bin/pr/egetopt.c215
-rw-r--r--usr.bin/pr/extern.h42
-rw-r--r--usr.bin/pr/pr.1361
-rw-r--r--usr.bin/pr/pr.c1901
-rw-r--r--usr.bin/pr/pr.h79
-rw-r--r--usr.bin/printf/printf.1438
-rw-r--r--usr.bin/printf/printf.c709
-rw-r--r--usr.bin/renice/renice.8151
-rw-r--r--usr.bin/renice/renice.c181
-rw-r--r--usr.bin/sed/POSIX205
-rw-r--r--usr.bin/sed/TEST/hanoi.sed103
-rw-r--r--usr.bin/sed/TEST/math.sed164
-rw-r--r--usr.bin/sed/TEST/sed.test554
-rw-r--r--usr.bin/sed/compile.c946
-rw-r--r--usr.bin/sed/defs.h150
-rw-r--r--usr.bin/sed/extern.h61
-rw-r--r--usr.bin/sed/main.c517
-rw-r--r--usr.bin/sed/misc.c119
-rw-r--r--usr.bin/sed/process.c792
-rw-r--r--usr.bin/sed/sed.1640
-rw-r--r--usr.bin/sort/append.c94
-rw-r--r--usr.bin/sort/fields.c380
-rw-r--r--usr.bin/sort/files.c279
-rw-r--r--usr.bin/sort/fsort.c202
-rw-r--r--usr.bin/sort/fsort.h78
-rw-r--r--usr.bin/sort/init.c447
-rw-r--r--usr.bin/sort/msort.c431
-rw-r--r--usr.bin/sort/pathnames.h66
-rw-r--r--usr.bin/sort/radix_sort.c217
-rw-r--r--usr.bin/sort/sort.1525
-rw-r--r--usr.bin/sort/sort.c419
-rw-r--r--usr.bin/sort/sort.h201
-rw-r--r--usr.bin/sort/tmp.c106
-rw-r--r--usr.bin/split/split.1132
-rw-r--r--usr.bin/split/split.c362
-rw-r--r--usr.bin/tabs/tabs.1166
-rw-r--r--usr.bin/tabs/tabs.c232
-rw-r--r--usr.bin/tail/extern.h52
-rw-r--r--usr.bin/tail/forward.c355
-rw-r--r--usr.bin/tail/misc.c89
-rw-r--r--usr.bin/tail/read.c210
-rw-r--r--usr.bin/tail/reverse.c269
-rw-r--r--usr.bin/tail/tac.156
-rw-r--r--usr.bin/tail/tail.1202
-rw-r--r--usr.bin/tail/tail.c323
-rw-r--r--usr.bin/talk/ctl.c125
-rw-r--r--usr.bin/talk/ctl_transact.c108
-rw-r--r--usr.bin/talk/display.c195
-rw-r--r--usr.bin/talk/get_addrs.c81
-rw-r--r--usr.bin/talk/get_names.c124
-rw-r--r--usr.bin/talk/init_disp.c153
-rw-r--r--usr.bin/talk/invite.c190
-rw-r--r--usr.bin/talk/io.c139
-rw-r--r--usr.bin/talk/look_up.c119
-rw-r--r--usr.bin/talk/msgs.c82
-rw-r--r--usr.bin/talk/talk.1143
-rw-r--r--usr.bin/talk/talk.c79
-rw-r--r--usr.bin/talk/talk.h89
-rw-r--r--usr.bin/talk/talk_ctl.h41
-rw-r--r--usr.bin/tee/tee.184
-rw-r--r--usr.bin/tee/tee.c149
-rw-r--r--usr.bin/time/ext.h5
-rw-r--r--usr.bin/time/time.1220
-rw-r--r--usr.bin/time/time.c215
-rw-r--r--usr.bin/time/xtime.c9
-rw-r--r--usr.bin/touch/touch.1215
-rw-r--r--usr.bin/touch/touch.c337
-rw-r--r--usr.bin/tput/clear.sh38
-rw-r--r--usr.bin/tput/tput.1139
-rw-r--r--usr.bin/tput/tput.c195
-rw-r--r--usr.bin/tr/extern.h43
-rw-r--r--usr.bin/tr/str.c453
-rw-r--r--usr.bin/tr/tr.1352
-rw-r--r--usr.bin/tr/tr.c283
-rw-r--r--usr.bin/true/true.156
-rw-r--r--usr.bin/true/true.sh2
-rw-r--r--usr.bin/tsort/tsort.187
-rw-r--r--usr.bin/tsort/tsort.c433
-rw-r--r--usr.bin/tty/tty.175
-rw-r--r--usr.bin/tty/tty.c81
-rw-r--r--usr.bin/uname/uname.186
-rw-r--r--usr.bin/uname/uname.c159
-rw-r--r--usr.bin/unexpand/unexpand.c218
-rw-r--r--usr.bin/unifdef/unifdef.1347
-rw-r--r--usr.bin/unifdef/unifdef.c1037
-rw-r--r--usr.bin/unifdef/unifdefall.sh29
-rw-r--r--usr.bin/uniq/uniq.1130
-rw-r--r--usr.bin/uniq/uniq.c266
-rw-r--r--usr.bin/uudecode/uudecode.c323
-rw-r--r--usr.bin/uuencode/uuencode.1175
-rw-r--r--usr.bin/uuencode/uuencode.5145
-rw-r--r--usr.bin/uuencode/uuencode.c202
-rw-r--r--usr.bin/wc/wc.1146
-rw-r--r--usr.bin/wc/wc.c354
-rw-r--r--usr.bin/what/what.182
-rw-r--r--usr.bin/what/what.c127
-rw-r--r--usr.bin/who/utmpentry.c329
-rw-r--r--usr.bin/who/utmpentry.h76
-rw-r--r--usr.bin/who/who.1197
-rw-r--r--usr.bin/who/who.c391
-rw-r--r--usr.bin/write/term_chk.c137
-rw-r--r--usr.bin/write/term_chk.h36
-rw-r--r--usr.bin/write/write.1106
-rw-r--r--usr.bin/write/write.c292
-rw-r--r--usr.bin/xargs/pathnames.h36
-rw-r--r--usr.bin/xargs/strnsubst.c115
-rw-r--r--usr.bin/xargs/xargs.1387
-rw-r--r--usr.bin/xargs/xargs.c652
-rw-r--r--usr.sbin/link/link.864
-rw-r--r--usr.sbin/link/link.c71
-rw-r--r--usr.sbin/unlink/unlink.858
-rw-r--r--usr.sbin/unlink/unlink.c70
844 files changed, 192027 insertions, 0 deletions
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..bc465c6
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,29 @@
+Copyright (c) 2019 Adélie Userland Team. All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+
+1. Redistributions of source code must retain the above copyright
+notice, this list of conditions and the following disclaimer.
+
+2. Redistributions in binary form must reproduce the above copyright
+notice, this list of conditions and the following disclaimer in the
+documentation and/or other materials provided with the distribution.
+
+3. Neither the name of the copyright holder nor the names of its
+contributors may be used to endorse or promote products derived from
+this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
+PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..ec406f1
--- /dev/null
+++ b/README.md
@@ -0,0 +1,22 @@
+# Userland
+
+This project is a port of NetBSD userland (`bin`, `sbin`, `usr.bin`, and `usr.sbin` in the NetBSD `src` distribution) to the musl implementation of the C standard library. It aims to provide a complete POSIX userland, with support for user-friendly extensions.
+
+# Rationale
+
+There are a multitude of other projects which aim to provide a Linux userland. None of them are POSIX-compliant, and many have no interest in becoming 100% POSIX-compliant. Some of those projects additionally do not wish to support extensions provided by other projects.
+
+# License
+
+All code in this repository is licensed under `BSD-3-Clause`; see `LICENSE` for a copy of the license text.
+
+# Contributing
+
+Contributions may be made in several ways:
+
+* Write code.
+* Write manual pages.
+* Write ancillary documentation.
+* File tickets in the issue tracker for features which are missing but implemented by another project.
+* Participate constructively in project discussions.
+
diff --git a/bin/cat/cat.1 b/bin/cat/cat.1
new file mode 100644
index 0000000..f27ce59
--- /dev/null
+++ b/bin/cat/cat.1
@@ -0,0 +1,217 @@
+.\" $NetBSD: cat.1,v 1.41 2017/10/02 08:24:17 wiz Exp $
+.\"
+.\" Copyright (c) 1989, 1990, 1993
+.\" The Regents of the University of California. All rights reserved.
+.\"
+.\" This code is derived from software contributed to Berkeley by
+.\" the Institute of Electrical and Electronics Engineers, Inc.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\" notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must reproduce the above copyright
+.\" notice, this list of conditions and the following disclaimer in the
+.\" documentation and/or other materials provided with the distribution.
+.\" 3. Neither the name of the University nor the names of its contributors
+.\" may be used to endorse or promote products derived from this software
+.\" without specific prior written permission.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+.\" ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+.\" SUCH DAMAGE.
+.\"
+.\" @(#)cat.1 8.3 (Berkeley) 5/2/95
+.\"
+.Dd June 15, 2014
+.Dt CAT 1
+.Os
+.Sh NAME
+.Nm cat
+.Nd concatenate and print files
+.Sh SYNOPSIS
+.Nm
+.Op Fl beflnstuv
+.Op Fl B Ar bsize
+.Op Fl
+.Op Ar
+.Sh DESCRIPTION
+The
+.Nm
+utility reads files sequentially, writing them to the standard output.
+The
+.Ar file
+operands are processed in command line order.
+A single dash represents the standard input,
+and may appear multiple times in the
+.Ar file
+list.
+If no
+.Ar file
+operands are given, standard input is read.
+.Pp
+The word
+.Dq concatenate
+is just a verbose synonym for
+.Dq catenate .
+.Pp
+The options are as follows:
+.Bl -tag -width Ds
+.It Fl B Ar bsize
+Read with a buffer size of
+.Ar bsize
+bytes, instead of the default buffer size which is the blocksize of the
+output file.
+.It Fl b
+Implies the
+.Fl n
+option, but doesn't number blank lines.
+.It Fl e
+Implies the
+.Fl v
+option, and displays a dollar sign
+.Pq Ql \&$
+at the end of each line
+as well.
+.It Fl f
+Only attempt to display regular files.
+.It Fl l
+Set an exclusive advisory lock on the standard output file descriptor.
+This lock is set using
+.Xr fcntl 2
+with the
+.Dv F_SETLKW
+command.
+If the output file is already locked,
+.Nm
+will block until the lock is acquired.
+.It Fl n
+Number the output lines, starting at 1.
+.It Fl s
+Squeeze multiple adjacent empty lines, causing the output to be
+single spaced.
+.It Fl t
+Implies the
+.Fl v
+option, and displays tab characters as
+.Ql ^I
+as well.
+.It Fl u
+The
+.Fl u
+option guarantees that the output is unbuffered.
+.It Fl v
+Displays non-printing characters so they are visible.
+Control characters print as
+.Ql ^X
+for control-X; the delete
+character (octal 0177) prints as
+.Ql ^? .
+Non-ascii characters (with the high bit set) are printed as
+.Ql M-
+(for meta) followed by the character for the low 7 bits.
+.El
+.Sh EXIT STATUS
+.Ex -std cat
+.Sh EXAMPLES
+The command:
+.Bd -literal -offset indent
+.Ic cat file1
+.Ed
+.Pp
+will print the contents of
+.Ar file1
+to the standard output.
+.Pp
+The command:
+.Bd -literal -offset indent
+.Ic cat file1 file2 > file3
+.Ed
+.Pp
+will sequentially print the contents of
+.Ar file1
+and
+.Ar file2
+to the file
+.Ar file3 ,
+truncating
+.Ar file3
+if it already exists.
+See the manual page for your shell (e.g.,
+.Xr sh 1 )
+for more information on redirection.
+.Pp
+The command:
+.Bd -literal -offset indent
+.Ic cat file1 - file2 - file3
+.Ed
+.Pp
+will print the contents of
+.Ar file1 ,
+print data it receives from the standard input until it receives an
+.Dv EOF
+.Pq Sq ^D
+character, print the contents of
+.Ar file2 ,
+read and output contents of the standard input again, then finally output
+the contents of
+.Ar file3 .
+Note that if the standard input referred to a file, the second dash
+on the command-line would have no effect, since the entire contents of the file
+would have already been read and printed by
+.Nm
+when it encountered the first
+.Ql \&-
+operand.
+.Sh SEE ALSO
+.Xr head 1 ,
+.Xr hexdump 1 ,
+.Xr lpr 1 ,
+.Xr more 1 ,
+.Xr pr 1 ,
+.Xr tac 1 ,
+.Xr tail 1 ,
+.Xr view 1 ,
+.Xr vis 1 ,
+.Xr fcntl 2
+.Rs
+.%A Rob Pike
+.%T "UNIX Style, or cat -v Considered Harmful"
+.%J "USENIX Summer Conference Proceedings"
+.%D 1983
+.Re
+.Sh STANDARDS
+The
+.Nm
+utility is expected to conform to the
+.St -p1003.2-92
+specification.
+.Pp
+The flags
+.Op Fl Bbeflnstv
+are extensions to the specification.
+.Sh HISTORY
+A
+.Nm
+utility appeared in
+.At v1 .
+Dennis Ritchie designed and wrote the first man page.
+It appears to have been
+.Xr cat 1 .
+.Sh BUGS
+Because of the shell language mechanism used to perform output
+redirection, the command
+.Dq Li cat file1 file2 > file1
+will cause the original data in file1 to be destroyed!
+This is performed by the shell before
+.Nm
+is run.
diff --git a/bin/cat/cat.c b/bin/cat/cat.c
new file mode 100644
index 0000000..563ba63
--- /dev/null
+++ b/bin/cat/cat.c
@@ -0,0 +1,321 @@
+/* $NetBSD: cat.c,v 1.57 2016/06/16 00:52:37 sevan Exp $ */
+
+/*
+ * Copyright (c) 1989, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Kevin Fall.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#if HAVE_NBTOOL_CONFIG_H
+#include "nbtool_config.h"
+#endif
+
+#include <sys/cdefs.h>
+#if !defined(lint)
+__COPYRIGHT(
+"@(#) Copyright (c) 1989, 1993\
+ The Regents of the University of California. All rights reserved.");
+#if 0
+static char sccsid[] = "@(#)cat.c 8.2 (Berkeley) 4/27/95";
+#else
+__RCSID("$NetBSD: cat.c,v 1.57 2016/06/16 00:52:37 sevan Exp $");
+#endif
+#endif /* not lint */
+
+#include <sys/param.h>
+#include <sys/stat.h>
+
+#include <ctype.h>
+#include <err.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <locale.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+static int bflag, eflag, fflag, lflag, nflag, sflag, tflag, vflag;
+static size_t bsize;
+static int rval;
+static const char *filename;
+
+void cook_args(char *argv[]);
+void cook_buf(FILE *);
+void raw_args(char *argv[]);
+void raw_cat(int);
+
+int
+main(int argc, char *argv[])
+{
+ int ch;
+ struct flock stdout_lock;
+
+ setprogname(argv[0]);
+ (void)setlocale(LC_ALL, "");
+
+ while ((ch = getopt(argc, argv, "B:beflnstuv")) != -1)
+ switch (ch) {
+ case 'B':
+ bsize = (size_t)strtol(optarg, NULL, 0);
+ break;
+ case 'b':
+ bflag = nflag = 1; /* -b implies -n */
+ break;
+ case 'e':
+ eflag = vflag = 1; /* -e implies -v */
+ break;
+ case 'f':
+ fflag = 1;
+ break;
+ case 'l':
+ lflag = 1;
+ break;
+ case 'n':
+ nflag = 1;
+ break;
+ case 's':
+ sflag = 1;
+ break;
+ case 't':
+ tflag = vflag = 1; /* -t implies -v */
+ break;
+ case 'u':
+ setbuf(stdout, NULL);
+ break;
+ case 'v':
+ vflag = 1;
+ break;
+ default:
+ case '?':
+ (void)fprintf(stderr,
+ "Usage: %s [-beflnstuv] [-B bsize] [-] "
+ "[file ...]\n", getprogname());
+ return EXIT_FAILURE;
+ }
+ argv += optind;
+
+ if (lflag) {
+ stdout_lock.l_len = 0;
+ stdout_lock.l_start = 0;
+ stdout_lock.l_type = F_WRLCK;
+ stdout_lock.l_whence = SEEK_SET;
+ if (fcntl(STDOUT_FILENO, F_SETLKW, &stdout_lock) == -1)
+ err(EXIT_FAILURE, "stdout");
+ }
+
+ if (bflag || eflag || nflag || sflag || tflag || vflag)
+ cook_args(argv);
+ else
+ raw_args(argv);
+ if (fclose(stdout))
+ err(EXIT_FAILURE, "stdout");
+ return rval;
+}
+
+void
+cook_args(char **argv)
+{
+ FILE *fp;
+
+ fp = stdin;
+ filename = "stdin";
+ do {
+ if (*argv) {
+ if (!strcmp(*argv, "-"))
+ fp = stdin;
+ else if ((fp = fopen(*argv,
+ fflag ? "rf" : "r")) == NULL) {
+ warn("%s", *argv);
+ rval = EXIT_FAILURE;
+ ++argv;
+ continue;
+ }
+ filename = *argv++;
+ }
+ cook_buf(fp);
+ if (fp != stdin)
+ (void)fclose(fp);
+ else
+ clearerr(fp);
+ } while (*argv);
+}
+
+void
+cook_buf(FILE *fp)
+{
+ int ch, gobble, line, prev;
+
+ line = gobble = 0;
+ for (prev = '\n'; (ch = getc(fp)) != EOF; prev = ch) {
+ if (prev == '\n') {
+ if (sflag) {
+ if (ch == '\n') {
+ if (gobble)
+ continue;
+ gobble = 1;
+ } else
+ gobble = 0;
+ }
+ if (nflag) {
+ if (!bflag || ch != '\n') {
+ (void)fprintf(stdout,
+ "%6d\t", ++line);
+ if (ferror(stdout))
+ break;
+ } else if (eflag) {
+ (void)fprintf(stdout,
+ "%6s\t", "");
+ if (ferror(stdout))
+ break;
+ }
+ }
+ }
+ if (ch == '\n') {
+ if (eflag)
+ if (putchar('$') == EOF)
+ break;
+ } else if (ch == '\t') {
+ if (tflag) {
+ if (putchar('^') == EOF || putchar('I') == EOF)
+ break;
+ continue;
+ }
+ } else if (vflag) {
+ if (!isascii(ch)) {
+ if (putchar('M') == EOF || putchar('-') == EOF)
+ break;
+ ch = toascii(ch);
+ }
+ if (iscntrl(ch)) {
+ if (putchar('^') == EOF ||
+ putchar(ch == '\177' ? '?' :
+ ch | 0100) == EOF)
+ break;
+ continue;
+ }
+ }
+ if (putchar(ch) == EOF)
+ break;
+ }
+ if (ferror(fp)) {
+ warn("%s", filename);
+ rval = EXIT_FAILURE;
+ clearerr(fp);
+ }
+ if (ferror(stdout))
+ err(EXIT_FAILURE, "stdout");
+}
+
+void
+raw_args(char **argv)
+{
+ int fd;
+
+ fd = fileno(stdin);
+ filename = "stdin";
+ do {
+ if (*argv) {
+ if (!strcmp(*argv, "-")) {
+ fd = fileno(stdin);
+ if (fd < 0)
+ goto skip;
+ } else if (fflag) {
+ struct stat st;
+ fd = open(*argv, O_RDONLY|O_NONBLOCK, 0);
+ if (fd < 0)
+ goto skip;
+
+ if (fstat(fd, &st) == -1) {
+ close(fd);
+ goto skip;
+ }
+ if (!S_ISREG(st.st_mode)) {
+ close(fd);
+ warnx("%s: not a regular file", *argv);
+ goto skipnomsg;
+ }
+ }
+ else if ((fd = open(*argv, O_RDONLY, 0)) < 0) {
+skip:
+ warn("%s", *argv);
+skipnomsg:
+ rval = EXIT_FAILURE;
+ ++argv;
+ continue;
+ }
+ filename = *argv++;
+ } else if (fd < 0) {
+ err(EXIT_FAILURE, "stdin");
+ }
+ raw_cat(fd);
+ if (fd != fileno(stdin))
+ (void)close(fd);
+ } while (*argv);
+}
+
+void
+raw_cat(int rfd)
+{
+ static char *buf;
+ static char fb_buf[BUFSIZ];
+
+ ssize_t nr, nw, off;
+ int wfd;
+
+ wfd = fileno(stdout);
+ if (wfd < 0)
+ err(EXIT_FAILURE, "stdout");
+ if (buf == NULL) {
+ struct stat sbuf;
+
+ if (bsize == 0) {
+ if (fstat(wfd, &sbuf) == 0 && sbuf.st_blksize > 0 &&
+ (size_t)sbuf.st_blksize > sizeof(fb_buf))
+ bsize = sbuf.st_blksize;
+ }
+ if (bsize > sizeof(fb_buf)) {
+ buf = malloc(bsize);
+ if (buf == NULL)
+ warnx("malloc, using %zu buffer", bsize);
+ }
+ if (buf == NULL) {
+ bsize = sizeof(fb_buf);
+ buf = fb_buf;
+ }
+ }
+ while ((nr = read(rfd, buf, bsize)) > 0)
+ for (off = 0; nr; nr -= nw, off += nw)
+ if ((nw = write(wfd, buf + off, (size_t)nr)) < 0)
+ err(EXIT_FAILURE, "stdout");
+ if (nr < 0) {
+ warn("%s", filename);
+ rval = EXIT_FAILURE;
+ }
+}
diff --git a/bin/chgrp/chgrp.1 b/bin/chgrp/chgrp.1
new file mode 100644
index 0000000..ffdd5a6
--- /dev/null
+++ b/bin/chgrp/chgrp.1
@@ -0,0 +1,164 @@
+.\" Copyright (c) 1983, 1990, 1993, 1994, 2003
+.\" The Regents of the University of California. All rights reserved.
+.\"
+.\" This code is derived from software contributed to Berkeley by
+.\" the Institute of Electrical and Electronics Engineers, Inc.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\" notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must reproduce the above copyright
+.\" notice, this list of conditions and the following disclaimer in the
+.\" documentation and/or other materials provided with the distribution.
+.\" 3. Neither the name of the University nor the names of its contributors
+.\" may be used to endorse or promote products derived from this software
+.\" without specific prior written permission.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+.\" ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+.\" SUCH DAMAGE.
+.\"
+.\" from: @(#)chgrp.1 8.3 (Berkeley) 3/31/94
+.\" $NetBSD: chgrp.1,v 1.8 2017/07/04 06:52:20 wiz Exp $
+.\"
+.Dd October 22, 2012
+.Dt CHGRP 1
+.Os
+.Sh NAME
+.Nm chgrp
+.Nd change group
+.Sh SYNOPSIS
+.Nm
+.Oo
+.Fl R
+.Op Fl H | Fl L | Fl P
+.Oc
+.Op Fl fhv
+.Ar group
+.Ar
+.Nm
+.Oo
+.Fl R
+.Op Fl H | Fl L | Fl P
+.Oc
+.Op Fl fhv
+.Fl Fl reference=rfile
+.Ar
+.Sh DESCRIPTION
+The
+.Nm
+utility sets the group ID of the file named by each
+.Ar file
+operand to the
+.Ar group
+ID specified by the group operand.
+.Pp
+Options:
+.Bl -tag -width Ds
+.It Fl H
+If the
+.Fl R
+option is specified, symbolic links on the command line are followed.
+(Symbolic links encountered in the tree traversal are not followed.)
+.It Fl L
+If the
+.Fl R
+option is specified, all symbolic links are followed.
+.It Fl P
+If the
+.Fl R
+option is specified, no symbolic links are followed.
+.It Fl R
+Change the group ID for the file hierarchies rooted
+in the files instead of just the files themselves.
+.It Fl f
+The force option ignores errors, except for usage errors and doesn't
+query about strange modes (unless the user does not have proper permissions).
+.It Fl h
+If
+.Ar file
+is a symbolic link, the group of the link is changed.
+.It Fl v
+Cause
+.Nm
+to be verbose, showing files as they are processed.
+.El
+.Pp
+If
+.Fl h
+is not given, unless the
+.Fl H
+or
+.Fl L
+option is set,
+.Nm
+on a symbolic link always succeeds and has no effect.
+The
+.Fl H ,
+.Fl L
+and
+.Fl P
+options are ignored unless the
+.Fl R
+option is specified.
+In addition, these options override each other and the
+command's actions are determined by the last one specified.
+The default is as if the
+.Fl P
+option had been specified.
+.Pp
+The
+.Ar group
+operand can be either a group name from the group database,
+or a numeric group ID.
+Since it is valid to have a group name that is numeric (and
+doesn't have the numeric ID that matches its name) the name lookup
+is always done first.
+Preceding the ID with a ``#'' character will force it to be taken
+as a number.
+.Pp
+The user invoking
+.Nm
+must belong to the specified group and be the owner of the file,
+or be the super-user.
+.Pp
+Unless invoked by the super-user,
+.Nm
+clears the set-user-id and set-group-id bits on a file to prevent
+accidental or mischievous creation of set-user-id or set-group-id
+programs.
+.Sh FILES
+.Bl -tag -width /etc/group -compact
+.It Pa /etc/group
+Group ID file
+.El
+.Sh EXIT STATUS
+.Ex -std chgrp
+.Sh SEE ALSO
+.Xr chown 2 ,
+.Xr lchown 2 ,
+.Xr fts 3 ,
+.Xr group 5 ,
+.Xr passwd 5 ,
+.Xr symlink 7 ,
+.Xr chown 8
+.Sh STANDARDS
+The
+.Nm
+utility is expected to be POSIX 1003.2 compatible.
+.Pp
+The
+.Fl v
+option and the use of ``#'' to force a numeric group ID
+are extensions to
+.St -p1003.2 .
diff --git a/bin/chgrp/chown.8 b/bin/chgrp/chown.8
new file mode 100644
index 0000000..a4cd79d
--- /dev/null
+++ b/bin/chgrp/chown.8
@@ -0,0 +1,181 @@
+.\" Copyright (c) 1990, 1991, 1993, 1994, 2003
+.\" The Regents of the University of California. All rights reserved.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\" notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must reproduce the above copyright
+.\" notice, this list of conditions and the following disclaimer in the
+.\" documentation and/or other materials provided with the distribution.
+.\" 3. Neither the name of the University nor the names of its contributors
+.\" may be used to endorse or promote products derived from this software
+.\" without specific prior written permission.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+.\" ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+.\" SUCH DAMAGE.
+.\"
+.\" from: @(#)chown.8 8.3 (Berkeley) 3/31/94
+.\" $NetBSD: chown.8,v 1.12 2017/07/04 06:53:12 wiz Exp $
+.\"
+.Dd September 11, 2016
+.Dt CHOWN 8
+.Os
+.Sh NAME
+.Nm chown
+.Nd change file owner and group
+.Sh SYNOPSIS
+.Nm
+.Oo
+.Fl R
+.Op Fl H | Fl L | Fl P
+.Oc
+.Op Fl fhv
+.Ar owner Ns Op Ar :group
+.Ar
+.Nm
+.Oo
+.Fl R
+.Op Fl H | Fl L | Fl P
+.Oc
+.Op Fl fhv
+.Ar :group
+.Ar
+.Nm
+.Oo
+.Fl R
+.Op Fl H | Fl L | Fl P
+.Oc
+.Op Fl fhv
+.Fl Fl reference=rfile
+.Ar
+.Sh DESCRIPTION
+.Nm
+sets the user ID and/or the group ID of the specified files.
+Symbolic links named by arguments are silently left unchanged unless
+.Fl h
+is used.
+.Pp
+The options are as follows:
+.Bl -tag -width Ds
+.It Fl H
+If the
+.Fl R
+option is specified, symbolic links on the command line are followed.
+(Symbolic links encountered in the tree traversal are not followed.)
+.It Fl L
+If the
+.Fl R
+option is specified, all symbolic links are followed.
+.It Fl P
+If the
+.Fl R
+option is specified, no symbolic links are followed.
+.It Fl R
+Change the user ID and/or the group ID for the file hierarchies rooted
+in the files instead of just the files themselves.
+.It Fl f
+Do not report any failure to change file owner or group, nor modify
+the exit status to reflect such failures.
+.It Fl h
+If
+.Ar file
+is a symbolic link, the owner and/or group of the link is changed.
+.It Fl v
+Cause
+.Nm
+to be verbose, showing files as they are processed.
+.El
+.Pp
+The
+.Fl H ,
+.Fl L
+and
+.Fl P
+options are ignored unless the
+.Fl R
+option is specified.
+In addition, these options override each other and the
+command's actions are determined by the last one specified.
+The default is as if the
+.Fl P
+option had been specified.
+.Pp
+The
+.Fl R
+option cannot be used together with the
+.Fl h
+option.
+.Pp
+The
+.Ar owner
+and
+.Ar group
+operands are both optional, however, one must be specified.
+If the
+.Ar group
+operand is specified, it must be preceded by a colon (``:'') character.
+.Pp
+The
+.Ar owner
+may be either a user name or a numeric user ID.
+The
+.Ar group
+may be either a group name or a numeric group ID.
+Since it is valid to have a user or group name that is numeric (and
+does not have the numeric ID that matches its name) the name lookup
+is always done first.
+Preceding an ID with a ``#'' character will force it to be taken
+as a number.
+.Pp
+The ownership of a file may only be altered by a super-user for
+obvious security reasons.
+.Pp
+Unless invoked by the super-user,
+.Nm
+clears the set-user-id and set-group-id bits on a file to prevent
+accidental or mischievous creation of set-user-id and set-group-id
+programs.
+.Sh EXIT STATUS
+.Ex -std chown
+.Sh COMPATIBILITY
+Previous versions of the
+.Nm
+utility used the dot (``.'') character to distinguish the group name.
+This has been changed to be a colon (``:'') character so that user and
+group names may contain the dot character.
+.Sh SEE ALSO
+.Xr chflags 1 ,
+.Xr chgrp 1 ,
+.Xr find 1 ,
+.Xr chown 2 ,
+.Xr lchown 2 ,
+.Xr fts 3 ,
+.Xr symlink 7
+.Sh STANDARDS
+The
+.Nm
+command is specified by POSIX.1-2017.
+.Pp
+The
+.Fl v
+and
+.Fl f
+options and the use of ``#'' to force a numeric lookup
+are extensions to
+.St -p1003.2 .
+.Sh HISTORY
+A
+.Nm
+utility appeared in
+.At v1 .
diff --git a/bin/chgrp/chown.c b/bin/chgrp/chown.c
new file mode 100644
index 0000000..493d103
--- /dev/null
+++ b/bin/chgrp/chown.c
@@ -0,0 +1,284 @@
+/* $NetBSD: chown.c,v 1.8 2012/10/24 01:12:51 enami Exp $ */
+
+/*
+ * Copyright (c) 1988, 1993, 1994, 2003
+ * The Regents of the University of California. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+#ifndef lint
+__COPYRIGHT("@(#) Copyright (c) 1988, 1993, 1994, 2003\
+ The Regents of the University of California. All rights reserved.");
+#endif /* not lint */
+
+#ifndef lint
+#if 0
+static char sccsid[] = "@(#)chown.c 8.8 (Berkeley) 4/4/94";
+#else
+__RCSID("$NetBSD: chown.c,v 1.8 2012/10/24 01:12:51 enami Exp $");
+#endif
+#endif /* not lint */
+
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#include <ctype.h>
+#include <dirent.h>
+#include <err.h>
+#include <errno.h>
+#include <locale.h>
+#include <fts.h>
+#include <grp.h>
+#include <pwd.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <getopt.h>
+
+static void a_gid(const char *);
+static void a_uid(const char *);
+static id_t id(const char *, const char *);
+__dead static void usage(void);
+
+static uid_t uid;
+static gid_t gid;
+static int ischown;
+static const char *myname;
+
+int
+main(int argc, char **argv)
+{
+ FTS *ftsp;
+ FTSENT *p;
+ int Hflag, Lflag, Rflag, ch, fflag, fts_options, hflag, rval, vflag;
+ char *cp, *reference;
+ int (*change_owner)(const char *, uid_t, gid_t);
+
+ setprogname(*argv);
+
+ (void)setlocale(LC_ALL, "");
+
+ myname = getprogname();
+ ischown = (myname[2] == 'o');
+ reference = NULL;
+
+ Hflag = Lflag = Rflag = fflag = hflag = vflag = 0;
+ while ((ch = getopt(argc, argv, "HLPRfhv")) != -1)
+ switch (ch) {
+ case 'H':
+ Hflag = 1;
+ Lflag = 0;
+ break;
+ case 'L':
+ Lflag = 1;
+ Hflag = 0;
+ break;
+ case 'P':
+ Hflag = Lflag = 0;
+ break;
+ case 'R':
+ Rflag = 1;
+ break;
+ case 'f':
+ fflag = 1;
+ break;
+ case 'h':
+ /*
+ * In System V the -h option causes chown/chgrp to
+ * change the owner/group of the symbolic link.
+ * 4.4BSD's symbolic links didn't have owners/groups,
+ * so it was an undocumented noop.
+ * In NetBSD 1.3, lchown(2) is introduced.
+ */
+ hflag = 1;
+ break;
+ case 'v':
+ vflag = 1;
+ break;
+ case '?':
+ default:
+ usage();
+ }
+ argv += optind;
+ argc -= optind;
+
+ if (argc == 0 || (argc == 1 && reference == NULL))
+ usage();
+
+ fts_options = FTS_PHYSICAL;
+ if (Rflag) {
+ if (hflag) {
+ errx(EXIT_FAILURE,
+ "the -R and -h options "
+ "may not be specified together.");
+ }
+ if (Hflag)
+ fts_options |= FTS_COMFOLLOW;
+ if (Lflag) {
+ fts_options &= ~FTS_PHYSICAL;
+ fts_options |= FTS_LOGICAL;
+ }
+ } else if (!hflag) {
+ fts_options |= FTS_COMFOLLOW;
+ }
+
+ uid = (uid_t)-1;
+ gid = (gid_t)-1;
+ if (ischown) {
+ if ((cp = strchr(*argv, ':')) != NULL) {
+ *cp++ = '\0';
+ a_gid(cp);
+ }
+ a_uid(*argv);
+ } else {
+ a_gid(*argv);
+ }
+ argv++;
+
+ if ((ftsp = fts_open(argv, fts_options, NULL)) == NULL) {
+ err(EXIT_FAILURE, "fts_open");
+ }
+
+ for (rval = EXIT_SUCCESS; (p = fts_read(ftsp)) != NULL;) {
+ change_owner = chown;
+ switch (p->fts_info) {
+ case FTS_D:
+ if (!Rflag) /* Change it at FTS_DP. */
+ fts_set(ftsp, p, FTS_SKIP);
+ continue;
+ case FTS_DNR: /* Warn, chown, continue. */
+ warnx("%s: %s", p->fts_path, strerror(p->fts_errno));
+ rval = EXIT_FAILURE;
+ break;
+ case FTS_ERR: /* Warn, continue. */
+ case FTS_NS:
+ warnx("%s: %s", p->fts_path, strerror(p->fts_errno));
+ rval = EXIT_FAILURE;
+ continue;
+ case FTS_SL: /* Ignore unless -h. */
+ /*
+ * All symlinks we found while doing a physical
+ * walk end up here.
+ */
+ if (!hflag)
+ continue;
+ /*
+ * Note that if we follow a symlink, fts_info is
+ * not FTS_SL but FTS_F or whatever. And we should
+ * use lchown only for FTS_SL and should use chown
+ * for others.
+ */
+ change_owner = lchown;
+ break;
+ case FTS_SLNONE: /* Ignore. */
+ /*
+ * The only symlinks that end up here are ones that
+ * don't point to anything. Note that if we are
+ * doing a phisycal walk, we never reach here unless
+ * we asked to follow explicitly.
+ */
+ continue;
+ default:
+ break;
+ }
+
+ if ((*change_owner)(p->fts_accpath, uid, gid) && !fflag) {
+ warn("%s", p->fts_path);
+ rval = EXIT_FAILURE;
+ } else {
+ if (vflag)
+ printf("%s\n", p->fts_path);
+ }
+ }
+ if (errno) {
+ err(EXIT_FAILURE, "fts_read");
+ }
+ exit(rval);
+ /* NOTREACHED */
+}
+
+static void
+a_gid(const char *s)
+{
+ struct group *gr;
+
+ if (*s == '\0') { /* Argument was "uid[:.]". */
+ return;
+ }
+ gr = *s == '#' ? NULL : getgrnam(s);
+ if (gr == NULL) {
+ gid = id(s, "group");
+ } else {
+ gid = gr->gr_gid;
+ }
+ return;
+}
+
+static void
+a_uid(const char *s)
+{
+ if (*s == '\0') { /* Argument was "[:.]gid". */
+ return;
+ }
+ if (*s == '#' || uid_from_user(s, &uid) == -1) {
+ uid = id(s, "user");
+ }
+ return;
+}
+
+static id_t
+id(const char *name, const char *type)
+{
+ id_t val;
+ char *ep;
+
+ errno = 0;
+ if (*name == '#') {
+ name++;
+ }
+ val = (id_t)strtoul(name, &ep, 10);
+ if (errno) {
+ err(EXIT_FAILURE, "%s", name);
+ }
+ if (*ep != '\0') {
+ errx(EXIT_FAILURE, "%s: invalid %s name", name, type);
+ }
+ return (val);
+}
+
+static void
+usage(void)
+{
+
+ (void)fprintf(stderr,
+ "Usage: %s [-fhv] %s file ...\n"
+ "\t%s -R [-H | -L | -P] [-fv] file ...\n",
+ myname, ischown ? "owner:group|owner|:group" : "group",
+ myname);
+ exit(EXIT_FAILURE);
+}
diff --git a/bin/chmod/chmod.1 b/bin/chmod/chmod.1
new file mode 100644
index 0000000..c9c1150
--- /dev/null
+++ b/bin/chmod/chmod.1
@@ -0,0 +1,313 @@
+.\" $NetBSD: chmod.1,v 1.28 2017/07/04 06:47:27 wiz Exp $
+.\"
+.\" Copyright (c) 1989, 1990, 1993, 1994
+.\" The Regents of the University of California. All rights reserved.
+.\"
+.\" This code is derived from software contributed to Berkeley by
+.\" the Institute of Electrical and Electronics Engineers, Inc.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\" notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must reproduce the above copyright
+.\" notice, this list of conditions and the following disclaimer in the
+.\" documentation and/or other materials provided with the distribution.
+.\" 3. Neither the name of the University nor the names of its contributors
+.\" may be used to endorse or promote products derived from this software
+.\" without specific prior written permission.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+.\" ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+.\" SUCH DAMAGE.
+.\"
+.\" @(#)chmod.1 8.4 (Berkeley) 3/31/94
+.\"
+.Dd August 11, 2016
+.Dt CHMOD 1
+.Os
+.Sh NAME
+.Nm chmod
+.Nd change file modes
+.Sh SYNOPSIS
+.Nm
+.Oo
+.Fl R
+.Op Fl H | Fl L | Fl P
+.Oc
+.Op Fl fh
+.Ar mode
+.Ar
+.Nm
+.Oo
+.Fl R
+.Op Fl H | Fl L | Fl P
+.Oc
+.Op Fl fh
+.Fl Fl reference=rfile
+.Ar
+.Sh DESCRIPTION
+The
+.Nm
+utility modifies the file mode bits of the listed files
+as specified by the
+.Ar mode
+operand, or
+copied from a reference
+.Ar rfile ,
+as specified with the
+.Fl Fl reference
+argument.
+.Pp
+The options are as follows:
+.Bl -tag -width Ds
+.It Fl H
+If the
+.Fl R
+option is specified, symbolic links on the command line are followed.
+(Symbolic links encountered in the tree traversal are not followed.)
+.It Fl L
+If the
+.Fl R
+option is specified, all symbolic links are followed.
+.It Fl P
+If the
+.Fl R
+option is specified, no symbolic links are followed.
+.It Fl R
+Change the modes of the file hierarchies rooted in the files
+instead of just the files themselves.
+.It Fl f
+Do not display a diagnostic message or modify the exit status if
+.Nm
+fails to change the mode of a file.
+.It Fl h
+If
+.Ar file
+is symbolic link, the mode of the link is changed.
+.El
+.Pp
+The
+.Fl H ,
+.Fl L
+and
+.Fl P
+options are ignored unless the
+.Fl R
+option is specified.
+In addition, these options override each other and the
+command's actions are determined by the last one specified.
+The default is as if the
+.Fl P
+option had been specified.
+.Pp
+Only the owner of a file or the super-user is permitted to change
+the mode of a file.
+.Sh EXIT STATUS
+.Ex -std chmod
+.Sh MODES
+Modes may be absolute or symbolic.
+An absolute mode is an octal number constructed by
+.Em or Ap ing
+the following values:
+.Pp
+.Bl -tag -width 6n -compact -offset indent
+.It Li 4000
+set-user-ID-on-execution
+.It Li 2000
+set-group-ID-on-execution
+.It Li 1000
+sticky bit, see
+.Xr chmod 2
+.It Li 0400
+read by owner
+.It Li 0200
+write by owner
+.It Li 0100
+execute (or search for directories) by owner
+.It Li 0070
+read, write, execute/search by group
+.It Li 0007
+read, write, execute/search by others
+.El
+.Pp
+The read, write, and execute/search values for group and others
+are encoded as described for owner.
+.Pp
+The symbolic mode is described by the following grammar:
+.Bd -literal -offset indent
+mode ::= clause [, clause ...]
+clause ::= [who ...] [action ...] last_action
+action ::= op [perm ...]
+last_action ::= op [perm ...]
+who ::= a | u | g | o
+op ::= + | \- | =
+perm ::= r | s | t | w | x | X | u | g | o
+.Ed
+.Pp
+The
+.Ar who
+symbols ``u'', ``g'', and ``o'' specify the user, group, and other parts
+of the mode bits, respectively.
+The
+.Ar who
+symbol ``a'' is equivalent to ``ugo''.
+.Pp
+The
+.Ar perm
+symbols represent the portions of the mode bits as follows:
+.Pp
+.Bl -tag -width Ds -compact -offset indent
+.It r
+The read bits.
+.It s
+The set-user-ID-on-execution and set-group-ID-on-execution bits.
+.It t
+The sticky bit.
+.It w
+The write bits.
+.It x
+The execute/search bits.
+.It X
+The execute/search bits if the file is a directory or any of the
+execute/search bits are set in the original (unmodified) mode.
+Operations with the
+.Ar perm
+symbol ``X'' are only meaningful in conjunction with the
+.Ar op
+symbol ``+'', and are ignored in all other cases.
+.It u
+The user permission bits in the mode of the original file.
+.It g
+The group permission bits in the mode of the original file.
+.It o
+The other permission bits in the mode of the original file.
+.El
+.Pp
+The
+.Ar op
+symbols represent the operation performed, as follows:
+.Bl -tag -width 4n
+.It +
+If no value is supplied for
+.Ar perm ,
+the ``+'' operation has no effect.
+If no value is supplied for
+.Ar who ,
+each permission bit specified in
+.Ar perm ,
+for which the corresponding bit in the file mode creation mask
+is clear, is set.
+Otherwise, the mode bits represented by the specified
+.Ar who
+and
+.Ar perm
+values are set.
+.It \&\-
+If no value is supplied for
+.Ar perm ,
+the ``\-'' operation has no effect.
+If no value is supplied for
+.Ar who ,
+each permission bit specified in
+.Ar perm ,
+for which the corresponding bit in the file mode creation mask
+is clear, is cleared.
+Otherwise, the mode bits represented by the specified
+.Ar who
+and
+.Ar perm
+values are cleared.
+.It =
+The mode bits specified by the
+.Ar who
+value are cleared, or, if no who value is specified, the owner, group
+and other mode bits are cleared.
+Then, if no value is supplied for
+.Ar who ,
+each permission bit specified in
+.Ar perm ,
+for which the corresponding bit in the file mode creation mask
+is clear, is set.
+Otherwise, the mode bits represented by the specified
+.Ar who
+and
+.Ar perm
+values are set.
+.El
+.Pp
+Each
+.Ar clause
+specifies one or more operations to be performed on the mode
+bits, and each operation is applied to the mode bits in the
+order specified.
+.Pp
+Operations upon the other permissions only (specified by the symbol
+``o'' by itself), in combination with the
+.Ar perm
+symbols ``s'' or ``t'', are ignored.
+.Sh EXAMPLES
+.Bl -tag -width "u=rwx,go=u-w" -compact
+.It Li 644
+make a file readable by anyone and writable by the owner only.
+.Pp
+.It Li go-w
+deny write permission to group and others.
+.Pp
+.It Li =rw,+X
+set the read and write permissions to the usual defaults, but
+retain any execute permissions that are currently set.
+.Pp
+.It Li +X
+make a directory or file searchable/executable by everyone if it is
+already searchable/executable by anyone.
+.Pp
+.It Li 755
+.It Li u=rwx,go=rx
+.It Li u=rwx,go=u-w
+make a file readable/executable by everyone and writable by the owner only.
+.Pp
+.It Li go=
+clear all mode bits for group and others.
+.Pp
+.It Li g=u-w
+set the group bits equal to the user bits, but clear the group write bit.
+.El
+.Sh SEE ALSO
+.Xr chflags 1 ,
+.Xr install 1 ,
+.Xr chmod 2 ,
+.Xr stat 2 ,
+.Xr umask 2 ,
+.Xr fts 3 ,
+.Xr setmode 3 ,
+.Xr symlink 7 ,
+.Xr chown 8
+.Sh STANDARDS
+The
+.Nm
+utility is expected to be
+.St -p1003.2-92
+compatible with the exception of the
+.Ar perm
+symbol
+.Dq t
+which is not included in that standard.
+.Sh HISTORY
+A
+.Nm
+utility appeared in
+.At v1 .
+.Sh BUGS
+There's no
+.Ar perm
+option for the naughty bits.
diff --git a/bin/chmod/chmod.c b/bin/chmod/chmod.c
new file mode 100644
index 0000000..c10101e
--- /dev/null
+++ b/bin/chmod/chmod.c
@@ -0,0 +1,239 @@
+/* $NetBSD: chmod.c,v 1.38 2012/10/22 18:00:46 christos Exp $ */
+
+/*
+ * Copyright (c) 1989, 1993, 1994
+ * The Regents of the University of California. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+#ifndef lint
+__COPYRIGHT(
+"@(#) Copyright (c) 1989, 1993, 1994\
+ The Regents of the University of California. All rights reserved.");
+#endif /* not lint */
+
+#ifndef lint
+#if 0
+static char sccsid[] = "@(#)chmod.c 8.8 (Berkeley) 4/1/94";
+#else
+__RCSID("$NetBSD: chmod.c,v 1.38 2012/10/22 18:00:46 christos Exp $");
+#endif
+#endif /* not lint */
+
+#include <sys/param.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+
+#include <err.h>
+#include <errno.h>
+#include <fts.h>
+#include <limits.h>
+#include <locale.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <getopt.h>
+
+__dead static void usage(void);
+
+struct option chmod_longopts[] = {
+ { "reference", required_argument, 0,
+ 1 },
+ { NULL, 0, 0,
+ 0 },
+};
+
+int
+main(int argc, char *argv[])
+{
+ FTS *ftsp;
+ FTSENT *p;
+ void *set;
+ mode_t mval;
+ int Hflag, Lflag, Rflag, ch, fflag, fts_options, hflag, rval;
+ char *mode, *reference;
+ int (*change_mode)(const char *, mode_t);
+
+ setprogname(argv[0]);
+ (void)setlocale(LC_ALL, "");
+
+ Hflag = Lflag = Rflag = fflag = hflag = 0;
+ reference = NULL;
+ while ((ch = getopt_long(argc, argv, "HLPRXfghorstuwx",
+ chmod_longopts, NULL)) != -1)
+ switch (ch) {
+ case 1:
+ reference = optarg;
+ break;
+ case 'H':
+ Hflag = 1;
+ Lflag = 0;
+ break;
+ case 'L':
+ Lflag = 1;
+ Hflag = 0;
+ break;
+ case 'P':
+ Hflag = Lflag = 0;
+ break;
+ case 'R':
+ Rflag = 1;
+ break;
+ case 'f':
+ fflag = 1;
+ break;
+ case 'h':
+ /*
+ * In System V the -h option causes chmod to
+ * change the mode of the symbolic link.
+ * 4.4BSD's symbolic links didn't have modes,
+ * so it was an undocumented noop. In NetBSD
+ * 1.3, lchmod(2) is introduced and this
+ * option does real work.
+ */
+ hflag = 1;
+ break;
+ /*
+ * XXX
+ * "-[rwx]" are valid mode commands. If they are the entire
+ * argument, getopt has moved past them, so decrement optind.
+ * Regardless, we're done argument processing.
+ */
+ case 'g': case 'o': case 'r': case 's':
+ case 't': case 'u': case 'w': case 'X': case 'x':
+ if (argv[optind - 1][0] == '-' &&
+ argv[optind - 1][1] == ch &&
+ argv[optind - 1][2] == '\0')
+ --optind;
+ goto done;
+ case '?':
+ default:
+ usage();
+ }
+done: argv += optind;
+ argc -= optind;
+
+ if (argc == 0 || (argc == 1 && reference == NULL))
+ usage();
+
+ fts_options = FTS_PHYSICAL;
+ if (Rflag) {
+ if (hflag) {
+ errx(EXIT_FAILURE,
+ "the -R and -h options may not be specified together.");
+ /* NOTREACHED */
+ }
+ if (Hflag)
+ fts_options |= FTS_COMFOLLOW;
+ if (Lflag) {
+ fts_options &= ~FTS_PHYSICAL;
+ fts_options |= FTS_LOGICAL;
+ }
+ } else if (!hflag)
+ fts_options |= FTS_COMFOLLOW;
+ if (hflag)
+ change_mode = lchmod;
+ else
+ change_mode = chmod;
+
+ if (reference == NULL) {
+ mode = *argv++;
+ if ((set = setmode(mode)) == NULL) {
+ err(EXIT_FAILURE, "Cannot set file mode `%s'", mode);
+ /* NOTREACHED */
+ }
+ mval = 0;
+ } else {
+ struct stat st;
+
+ if (stat(reference, &st) == -1)
+ err(EXIT_FAILURE, "Cannot stat `%s'", reference);
+ mval = st.st_mode;
+ set = NULL;
+ }
+
+ if ((ftsp = fts_open(argv, fts_options, 0)) == NULL) {
+ err(EXIT_FAILURE, "fts_open");
+ /* NOTREACHED */
+ }
+ for (rval = 0; (p = fts_read(ftsp)) != NULL;) {
+ switch (p->fts_info) {
+ case FTS_D:
+ if (!Rflag)
+ (void)fts_set(ftsp, p, FTS_SKIP);
+ break;
+ case FTS_DNR: /* Warn, chmod, continue. */
+ warnx("%s: %s", p->fts_path, strerror(p->fts_errno));
+ rval = 1;
+ break;
+ case FTS_DP: /* Already changed at FTS_D. */
+ continue;
+ case FTS_ERR: /* Warn, continue. */
+ case FTS_NS:
+ warnx("%s: %s", p->fts_path, strerror(p->fts_errno));
+ rval = 1;
+ continue;
+ case FTS_SL: /* Ignore. */
+ case FTS_SLNONE:
+ /*
+ * The only symlinks that end up here are ones that
+ * don't point to anything and ones that we found
+ * doing a physical walk.
+ */
+ if (!hflag)
+ continue;
+ /* else */
+ /* FALLTHROUGH */
+ default:
+ break;
+ }
+ if ((*change_mode)(p->fts_accpath,
+ set ? getmode(set, p->fts_statp->st_mode) : mval)
+ && !fflag) {
+ warn("%s", p->fts_path);
+ rval = 1;
+ }
+ }
+ if (errno) {
+ err(EXIT_FAILURE, "fts_read");
+ /* NOTREACHED */
+ }
+ exit(rval);
+ /* NOTREACHED */
+}
+
+static void
+usage(void)
+{
+ (void)fprintf(stderr,
+ "Usage: %s [-R [-H | -L | -P]] [-fh] mode file ...\n"
+ "\t%s [-R [-H | -L | -P]] [-fh] --reference=rfile file ...\n",
+ getprogname(), getprogname());
+ exit(1);
+ /* NOTREACHED */
+}
diff --git a/bin/chown/chgrp.1 b/bin/chown/chgrp.1
new file mode 100644
index 0000000..9f289e3
--- /dev/null
+++ b/bin/chown/chgrp.1
@@ -0,0 +1,169 @@
+.\" Copyright (c) 1983, 1990, 1993, 1994, 2003
+.\" The Regents of the University of California. All rights reserved.
+.\"
+.\" This code is derived from software contributed to Berkeley by
+.\" the Institute of Electrical and Electronics Engineers, Inc.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\" notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must reproduce the above copyright
+.\" notice, this list of conditions and the following disclaimer in the
+.\" documentation and/or other materials provided with the distribution.
+.\" 3. Neither the name of the University nor the names of its contributors
+.\" may be used to endorse or promote products derived from this software
+.\" without specific prior written permission.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+.\" ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+.\" SUCH DAMAGE.
+.\"
+.\" from: @(#)chgrp.1 8.3 (Berkeley) 3/31/94
+.\" $NetBSD: chgrp.1,v 1.8 2017/07/04 06:52:20 wiz Exp $
+.\"
+.Dd October 22, 2012
+.Dt CHGRP 1
+.Os
+.Sh NAME
+.Nm chgrp
+.Nd change group
+.Sh SYNOPSIS
+.Nm
+.Oo
+.Fl R
+.Op Fl H | Fl L | Fl P
+.Oc
+.Op Fl fhv
+.Ar group
+.Ar
+.Nm
+.Oo
+.Fl R
+.Op Fl H | Fl L | Fl P
+.Oc
+.Op Fl fhv
+.Fl Fl reference=rfile
+.Ar
+.Sh DESCRIPTION
+The
+.Nm
+utility sets the group ID of the file named by each
+.Ar file
+operand to the
+.Ar group
+ID specified by the group operand,
+or to the group of the given
+.Ar rfile ,
+specified by the
+.Fl Fl reference
+argument.
+.Pp
+Options:
+.Bl -tag -width Ds
+.It Fl H
+If the
+.Fl R
+option is specified, symbolic links on the command line are followed.
+(Symbolic links encountered in the tree traversal are not followed.)
+.It Fl L
+If the
+.Fl R
+option is specified, all symbolic links are followed.
+.It Fl P
+If the
+.Fl R
+option is specified, no symbolic links are followed.
+.It Fl R
+Change the group ID for the file hierarchies rooted
+in the files instead of just the files themselves.
+.It Fl f
+The force option ignores errors, except for usage errors and doesn't
+query about strange modes (unless the user does not have proper permissions).
+.It Fl h
+If
+.Ar file
+is a symbolic link, the group of the link is changed.
+.It Fl v
+Cause
+.Nm
+to be verbose, showing files as they are processed.
+.El
+.Pp
+If
+.Fl h
+is not given, unless the
+.Fl H
+or
+.Fl L
+option is set,
+.Nm
+on a symbolic link always succeeds and has no effect.
+The
+.Fl H ,
+.Fl L
+and
+.Fl P
+options are ignored unless the
+.Fl R
+option is specified.
+In addition, these options override each other and the
+command's actions are determined by the last one specified.
+The default is as if the
+.Fl P
+option had been specified.
+.Pp
+The
+.Ar group
+operand can be either a group name from the group database,
+or a numeric group ID.
+Since it is valid to have a group name that is numeric (and
+doesn't have the numeric ID that matches its name) the name lookup
+is always done first.
+Preceding the ID with a ``#'' character will force it to be taken
+as a number.
+.Pp
+The user invoking
+.Nm
+must belong to the specified group and be the owner of the file,
+or be the super-user.
+.Pp
+Unless invoked by the super-user,
+.Nm
+clears the set-user-id and set-group-id bits on a file to prevent
+accidental or mischievous creation of set-user-id or set-group-id
+programs.
+.Sh FILES
+.Bl -tag -width /etc/group -compact
+.It Pa /etc/group
+Group ID file
+.El
+.Sh EXIT STATUS
+.Ex -std chgrp
+.Sh SEE ALSO
+.Xr chown 2 ,
+.Xr lchown 2 ,
+.Xr fts 3 ,
+.Xr group 5 ,
+.Xr passwd 5 ,
+.Xr symlink 7 ,
+.Xr chown 8
+.Sh STANDARDS
+The
+.Nm
+utility is expected to be POSIX 1003.2 compatible.
+.Pp
+The
+.Fl v
+option and the use of ``#'' to force a numeric group ID
+are extensions to
+.St -p1003.2 .
diff --git a/bin/chown/chown.8 b/bin/chown/chown.8
new file mode 100644
index 0000000..7bfac72
--- /dev/null
+++ b/bin/chown/chown.8
@@ -0,0 +1,184 @@
+.\" Copyright (c) 1990, 1991, 1993, 1994, 2003
+.\" The Regents of the University of California. All rights reserved.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\" notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must reproduce the above copyright
+.\" notice, this list of conditions and the following disclaimer in the
+.\" documentation and/or other materials provided with the distribution.
+.\" 3. Neither the name of the University nor the names of its contributors
+.\" may be used to endorse or promote products derived from this software
+.\" without specific prior written permission.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+.\" ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+.\" SUCH DAMAGE.
+.\"
+.\" from: @(#)chown.8 8.3 (Berkeley) 3/31/94
+.\" $NetBSD: chown.8,v 1.12 2017/07/04 06:53:12 wiz Exp $
+.\"
+.Dd September 11, 2016
+.Dt CHOWN 8
+.Os
+.Sh NAME
+.Nm chown
+.Nd change file owner and group
+.Sh SYNOPSIS
+.Nm
+.Oo
+.Fl R
+.Op Fl H | Fl L | Fl P
+.Oc
+.Op Fl fhv
+.Ar owner Ns Op Ar :group
+.Ar
+.Nm
+.Oo
+.Fl R
+.Op Fl H | Fl L | Fl P
+.Oc
+.Op Fl fhv
+.Ar :group
+.Ar
+.Nm
+.Oo
+.Fl R
+.Op Fl H | Fl L | Fl P
+.Oc
+.Op Fl fhv
+.Fl Fl reference=rfile
+.Ar
+.Sh DESCRIPTION
+.Nm
+sets the user ID and/or the group ID of the specified files.
+Symbolic links named by arguments are silently left unchanged unless
+.Fl h
+is used.
+.Pp
+The options are as follows:
+.Bl -tag -width Ds
+.It Fl H
+If the
+.Fl R
+option is specified, symbolic links on the command line are followed.
+(Symbolic links encountered in the tree traversal are not followed.)
+.It Fl L
+If the
+.Fl R
+option is specified, all symbolic links are followed.
+.It Fl P
+If the
+.Fl R
+option is specified, no symbolic links are followed.
+.It Fl R
+Change the user ID and/or the group ID for the file hierarchies rooted
+in the files instead of just the files themselves.
+.It Fl f
+Do not report any failure to change file owner or group, nor modify
+the exit status to reflect such failures.
+.It Fl h
+If
+.Ar file
+is a symbolic link, the owner and/or group of the link is changed.
+.It Fl v
+Cause
+.Nm
+to be verbose, showing files as they are processed.
+.El
+.Pp
+The
+.Fl H ,
+.Fl L
+and
+.Fl P
+options are ignored unless the
+.Fl R
+option is specified.
+In addition, these options override each other and the
+command's actions are determined by the last one specified.
+The default is as if the
+.Fl P
+option had been specified.
+.Pp
+The
+.Fl L
+option cannot be used together with the
+.Fl h
+option.
+.Pp
+The
+.Ar owner
+and
+.Ar group
+operands are both optional, however, one must be specified; alternatively,
+both the owner and group may be specified using a reference
+.Ar rfile
+specified using the
+.Fl Fl reference
+argument.
+If the
+.Ar group
+operand is specified, it must be preceded by a colon (``:'') character.
+.Pp
+The
+.Ar owner
+may be either a user name or a numeric user ID.
+The
+.Ar group
+may be either a group name or a numeric group ID.
+Since it is valid to have a user or group name that is numeric (and
+does not have the numeric ID that matches its name) the name lookup
+is always done first.
+Preceding an ID with a ``#'' character will force it to be taken
+as a number.
+.Pp
+The ownership of a file may only be altered by a super-user for
+obvious security reasons.
+.Pp
+Unless invoked by the super-user,
+.Nm
+clears the set-user-id and set-group-id bits on a file to prevent
+accidental or mischievous creation of set-user-id and set-group-id
+programs.
+.Sh EXIT STATUS
+.Ex -std chown
+.Sh COMPATIBILITY
+Previous versions of the
+.Nm
+utility used the dot (``.'') character to distinguish the group name.
+This has been changed to be a colon (``:'') character so that user and
+group names may contain the dot character.
+.Sh SEE ALSO
+.Xr chflags 1 ,
+.Xr chgrp 1 ,
+.Xr find 1 ,
+.Xr chown 2 ,
+.Xr lchown 2 ,
+.Xr fts 3 ,
+.Xr symlink 7
+.Sh STANDARDS
+The
+.Nm
+command is expected to be POSIX 1003.2 compliant.
+.Pp
+The
+.Fl v
+option and the use of ``#'' to force a numeric lookup
+are extensions to
+.St -p1003.2 .
+.Sh HISTORY
+A
+.Nm
+utility appeared in
+.At v1 .
diff --git a/bin/chown/chown.c b/bin/chown/chown.c
new file mode 100644
index 0000000..ee46eee
--- /dev/null
+++ b/bin/chown/chown.c
@@ -0,0 +1,302 @@
+/* $NetBSD: chown.c,v 1.8 2012/10/24 01:12:51 enami Exp $ */
+
+/*
+ * Copyright (c) 1988, 1993, 1994, 2003
+ * The Regents of the University of California. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+#ifndef lint
+__COPYRIGHT("@(#) Copyright (c) 1988, 1993, 1994, 2003\
+ The Regents of the University of California. All rights reserved.");
+#endif /* not lint */
+
+#ifndef lint
+#if 0
+static char sccsid[] = "@(#)chown.c 8.8 (Berkeley) 4/4/94";
+#else
+__RCSID("$NetBSD: chown.c,v 1.8 2012/10/24 01:12:51 enami Exp $");
+#endif
+#endif /* not lint */
+
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#include <ctype.h>
+#include <dirent.h>
+#include <err.h>
+#include <errno.h>
+#include <locale.h>
+#include <fts.h>
+#include <grp.h>
+#include <pwd.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <getopt.h>
+
+static void a_gid(const char *);
+static void a_uid(const char *);
+static id_t id(const char *, const char *);
+__dead static void usage(void);
+
+static uid_t uid;
+static gid_t gid;
+static int ischown;
+static const char *myname;
+
+struct option chown_longopts[] = {
+ { "reference", required_argument, 0,
+ 1 },
+ { NULL, 0, 0,
+ 0 },
+};
+
+int
+main(int argc, char **argv)
+{
+ FTS *ftsp;
+ FTSENT *p;
+ int Hflag, Lflag, Rflag, ch, fflag, fts_options, hflag, rval, vflag;
+ char *cp, *reference;
+ int (*change_owner)(const char *, uid_t, gid_t);
+
+ setprogname(*argv);
+
+ (void)setlocale(LC_ALL, "");
+
+ myname = getprogname();
+ ischown = (myname[2] == 'o');
+ reference = NULL;
+
+ Hflag = Lflag = Rflag = fflag = hflag = vflag = 0;
+ while ((ch = getopt_long(argc, argv, "HLPRfhv",
+ chown_longopts, NULL)) != -1)
+ switch (ch) {
+ case 1:
+ reference = optarg;
+ break;
+ case 'H':
+ Hflag = 1;
+ Lflag = 0;
+ break;
+ case 'L':
+ Lflag = 1;
+ Hflag = 0;
+ break;
+ case 'P':
+ Hflag = Lflag = 0;
+ break;
+ case 'R':
+ Rflag = 1;
+ break;
+ case 'f':
+ fflag = 1;
+ break;
+ case 'h':
+ /*
+ * In System V the -h option causes chown/chgrp to
+ * change the owner/group of the symbolic link.
+ * 4.4BSD's symbolic links didn't have owners/groups,
+ * so it was an undocumented noop.
+ * In NetBSD 1.3, lchown(2) is introduced.
+ */
+ hflag = 1;
+ break;
+ case 'v':
+ vflag = 1;
+ break;
+ case '?':
+ default:
+ usage();
+ }
+ argv += optind;
+ argc -= optind;
+
+ if (argc == 0 || (argc == 1 && reference == NULL))
+ usage();
+
+ fts_options = FTS_PHYSICAL;
+ if (Rflag) {
+ if (Hflag)
+ fts_options |= FTS_COMFOLLOW;
+ if (Lflag) {
+ if (hflag)
+ errx(EXIT_FAILURE,
+ "the -L and -h options "
+ "may not be specified together.");
+ fts_options &= ~FTS_PHYSICAL;
+ fts_options |= FTS_LOGICAL;
+ }
+ } else if (!hflag)
+ fts_options |= FTS_COMFOLLOW;
+
+ uid = (uid_t)-1;
+ gid = (gid_t)-1;
+ if (reference == NULL) {
+ if (ischown) {
+ if ((cp = strchr(*argv, ':')) != NULL) {
+ *cp++ = '\0';
+ a_gid(cp);
+ }
+#ifdef SUPPORT_DOT
+ else if ((cp = strrchr(*argv, '.')) != NULL) {
+ if (uid_from_user(*argv, &uid) == -1) {
+ *cp++ = '\0';
+ a_gid(cp);
+ }
+ }
+#endif
+ a_uid(*argv);
+ } else
+ a_gid(*argv);
+ argv++;
+ } else {
+ struct stat st;
+
+ if (stat(reference, &st) == -1)
+ err(EXIT_FAILURE, "Cannot stat `%s'", reference);
+ if (ischown)
+ uid = st.st_uid;
+ gid = st.st_gid;
+ }
+
+ if ((ftsp = fts_open(argv, fts_options, NULL)) == NULL)
+ err(EXIT_FAILURE, "fts_open");
+
+ for (rval = EXIT_SUCCESS; (p = fts_read(ftsp)) != NULL;) {
+ change_owner = chown;
+ switch (p->fts_info) {
+ case FTS_D:
+ if (!Rflag) /* Change it at FTS_DP. */
+ fts_set(ftsp, p, FTS_SKIP);
+ continue;
+ case FTS_DNR: /* Warn, chown, continue. */
+ warnx("%s: %s", p->fts_path, strerror(p->fts_errno));
+ rval = EXIT_FAILURE;
+ break;
+ case FTS_ERR: /* Warn, continue. */
+ case FTS_NS:
+ warnx("%s: %s", p->fts_path, strerror(p->fts_errno));
+ rval = EXIT_FAILURE;
+ continue;
+ case FTS_SL: /* Ignore unless -h. */
+ /*
+ * All symlinks we found while doing a physical
+ * walk end up here.
+ */
+ if (!hflag)
+ continue;
+ /*
+ * Note that if we follow a symlink, fts_info is
+ * not FTS_SL but FTS_F or whatever. And we should
+ * use lchown only for FTS_SL and should use chown
+ * for others.
+ */
+ change_owner = lchown;
+ break;
+ case FTS_SLNONE: /* Ignore. */
+ /*
+ * The only symlinks that end up here are ones that
+ * don't point to anything. Note that if we are
+ * doing a phisycal walk, we never reach here unless
+ * we asked to follow explicitly.
+ */
+ continue;
+ default:
+ break;
+ }
+
+ if ((*change_owner)(p->fts_accpath, uid, gid) && !fflag) {
+ warn("%s", p->fts_path);
+ rval = EXIT_FAILURE;
+ } else {
+ if (vflag)
+ printf("%s\n", p->fts_path);
+ }
+ }
+ if (errno)
+ err(EXIT_FAILURE, "fts_read");
+ exit(rval);
+ /* NOTREACHED */
+}
+
+static void
+a_gid(const char *s)
+{
+ struct group *gr;
+
+ if (*s == '\0') /* Argument was "uid[:.]". */
+ return;
+ gr = *s == '#' ? NULL : getgrnam(s);
+ if (gr == NULL)
+ gid = id(s, "group");
+ else
+ gid = gr->gr_gid;
+ return;
+}
+
+static void
+a_uid(const char *s)
+{
+ if (*s == '\0') /* Argument was "[:.]gid". */
+ return;
+ if (*s == '#' || uid_from_user(s, &uid) == -1) {
+ uid = id(s, "user");
+ }
+ return;
+}
+
+static id_t
+id(const char *name, const char *type)
+{
+ id_t val;
+ char *ep;
+
+ errno = 0;
+ if (*name == '#')
+ name++;
+ val = (id_t)strtoul(name, &ep, 10);
+ if (errno)
+ err(EXIT_FAILURE, "%s", name);
+ if (*ep != '\0')
+ errx(EXIT_FAILURE, "%s: invalid %s name", name, type);
+ return (val);
+}
+
+static void
+usage(void)
+{
+
+ (void)fprintf(stderr,
+ "Usage: %s [-R [-H | -L | -P]] [-fhv] %s file ...\n"
+ "\t%s [-R [-H | -L | -P]] [-fhv] --reference=rfile file ...\n",
+ myname, ischown ? "owner:group|owner|:group" : "group",
+ myname);
+ exit(EXIT_FAILURE);
+}
diff --git a/bin/cp/cp.1 b/bin/cp/cp.1
new file mode 100644
index 0000000..d038da4
--- /dev/null
+++ b/bin/cp/cp.1
@@ -0,0 +1,263 @@
+.\" $NetBSD: cp.1,v 1.46 2018/12/23 01:29:23 gutteridge Exp $
+.\"
+.\" Copyright (c) 1989, 1990, 1993, 1994
+.\" The Regents of the University of California. All rights reserved.
+.\"
+.\" This code is derived from software contributed to Berkeley by
+.\" the Institute of Electrical and Electronics Engineers, Inc.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\" notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must reproduce the above copyright
+.\" notice, this list of conditions and the following disclaimer in the
+.\" documentation and/or other materials provided with the distribution.
+.\" 3. Neither the name of the University nor the names of its contributors
+.\" may be used to endorse or promote products derived from this software
+.\" without specific prior written permission.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+.\" ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+.\" SUCH DAMAGE.
+.\"
+.\" @(#)cp.1 8.3 (Berkeley) 4/18/94
+.\"
+.Dd December 22, 2018
+.Dt CP 1
+.Os
+.Sh NAME
+.Nm cp
+.Nd copy files
+.Sh SYNOPSIS
+.Nm
+.Oo
+.Fl R
+.Op Fl H | Fl L | Fl P
+.Oc
+.Op Fl f | i
+.Op Fl alNpv
+.Ar source_file target_file
+.Nm cp
+.Oo
+.Fl R
+.Op Fl H | Fl L | Fl P
+.Oc
+.Op Fl f | i
+.Op Fl alNpv
+.Ar source_file ... target_directory
+.Sh DESCRIPTION
+In the first synopsis form, the
+.Nm
+utility copies the contents of the
+.Ar source_file
+to the
+.Ar target_file .
+In the second synopsis form,
+the contents of each named
+.Ar source_file
+is copied to the destination
+.Ar target_directory .
+The names of the files themselves are not changed.
+If
+.Nm
+detects an attempt to copy a file to itself, the copy will fail.
+.Pp
+The following options are available:
+.Bl -tag -width flag
+.It Fl a
+Archive mode.
+Same as
+.Fl RpP .
+.It Fl f
+For each existing destination pathname, attempt to overwrite it.
+If permissions do not allow copy to succeed, remove it and create a new
+file, without prompting for confirmation.
+(The
+.Fl i
+option is ignored if the
+.Fl f
+option is specified.)
+.It Fl H
+If the
+.Fl R
+option is specified, symbolic links on the command line are followed.
+(Symbolic links encountered in the tree traversal are not followed.)
+.It Fl i
+Causes
+.Nm
+to write a prompt to the standard error output before copying a file
+that would overwrite an existing file.
+If the response from the standard input begins with the character
+.Sq Li y ,
+the file copy is attempted.
+.It Fl L
+If the
+.Fl R
+option is specified, all symbolic links are followed.
+.It Fl l
+Create hard links to regular files in a hierarchy instead of copying.
+.It Fl N
+When used with
+.Fl p ,
+don't copy file flags.
+.It Fl P
+No symbolic links are followed.
+This is the default.
+.It Fl p
+Causes
+.Nm
+to preserve in the copy as many of the modification time, access time,
+file flags, file mode, user ID, group ID, and extended attributes,
+as allowed by permissions.
+.Pp
+If the user ID and group ID cannot be preserved due to insufficient
+permissions, no error message is displayed and the exit value is not
+altered.
+.Pp
+If the source file has its set user ID bit on and the user ID cannot
+be preserved, the set user ID bit is not preserved
+in the copy's permissions.
+If the source file has its set group ID bit on and the group ID cannot
+be preserved, the set group ID bit is not preserved
+in the copy's permissions.
+If the source file has both its set user ID and set group ID bits on,
+and either the user ID or group ID cannot be preserved, neither
+the set user ID or set group ID bits are preserved in the copy's
+permissions.
+.Pp
+Extended attributes from all accessible namespaces are copied;
+others are ignored.
+If an error occurs during this copy, a message is displayed and
+.Nm
+skips the other extended attributes for that file.
+.It Fl R
+If
+.Ar source_file
+designates a directory,
+.Nm
+copies the directory and the entire subtree connected at that point.
+This option also causes symbolic links to be copied, rather than
+followed, and for
+.Nm
+to create special files rather than copying them as normal files.
+Created directories have the same mode as the corresponding source
+directory, unmodified by the process's umask.
+.Pp
+Note that
+.Nm
+copies hard linked files as separate files.
+If you need to preserve hard links, consider using a utility like
+.Xr pax 1
+instead.
+.It Fl v
+Causes
+.Nm
+to be verbose, showing files as they are copied.
+.El
+.Pp
+For each destination file that already exists, its contents are
+overwritten if permissions allow, but its mode, user ID, and group
+ID are unchanged.
+.Pp
+In the second synopsis form,
+.Ar target_directory
+must exist unless there is only one named
+.Ar source_file
+which is a directory and the
+.Fl R
+flag is specified.
+.Pp
+If the destination file does not exist, the mode of the source file is
+used as modified by the file mode creation mask
+.Ic ( umask ,
+see
+.Xr csh 1 ) .
+If the source file has its set user ID bit on, that bit is removed
+unless both the source file and the destination file are owned by the
+same user.
+If the source file has its set group ID bit on, that bit is removed
+unless both the source file and the destination file are in the same
+group and the user is a member of that group.
+If both the set user ID and set group ID bits are set, all of the above
+conditions must be fulfilled or both bits are removed.
+.Pp
+Appropriate permissions are required for file creation or overwriting.
+.Pp
+Symbolic links are always followed unless the
+.Fl R
+flag is set, in which case symbolic links are not followed, by default.
+The
+.Fl H
+or
+.Fl L
+flags (in conjunction with the
+.Fl R
+flag), as well as the
+.Fl P
+flag cause symbolic links to be followed as described above.
+The
+.Fl H
+and
+.Fl L
+options are ignored unless the
+.Fl R
+option is specified.
+In addition, these options override each other and the
+command's actions are determined by the last one specified.
+The default is as if the
+.Fl P
+option had been specified.
+.Sh EXIT STATUS
+.Ex -std cp
+.Sh COMPATIBILITY
+Historic versions of the
+.Nm
+utility had a
+.Fl r
+option.
+This implementation supports that option, however, its use is strongly
+discouraged, as it does not correctly copy special files, symbolic links,
+or FIFOs.
+.Sh SEE ALSO
+.Xr mv 1 ,
+.Xr pax 1 ,
+.Xr rcp 1 ,
+.Xr umask 2 ,
+.Xr fts 3 ,
+.Xr symlink 7
+.Sh STANDARDS
+The
+.Nm
+utility is expected to be
+.St -p1003.2
+compatible.
+.Pp
+The
+.Fl a
+and
+.Fl l
+flags are non-standard extensions.
+They are intended to be compatible with the same options which
+other implementations, namely GNU coreutils and
+.Fx ,
+of this utility have.
+.Pp
+The
+.Fl v
+option is an extension to
+.St -p1003.2 .
+.Sh HISTORY
+A
+.Nm
+utility appeared in
+.At v1 .
diff --git a/bin/cp/cp.c b/bin/cp/cp.c
new file mode 100644
index 0000000..2f4fab1
--- /dev/null
+++ b/bin/cp/cp.c
@@ -0,0 +1,548 @@
+/* $NetBSD: cp.c,v 1.59 2016/03/05 19:48:55 uwe Exp $ */
+
+/*
+ * Copyright (c) 1988, 1993, 1994
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * David Hitz of Auspex Systems Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+#ifndef lint
+__COPYRIGHT(
+"@(#) Copyright (c) 1988, 1993, 1994\
+ The Regents of the University of California. All rights reserved.");
+#endif /* not lint */
+
+#ifndef lint
+#if 0
+static char sccsid[] = "@(#)cp.c 8.5 (Berkeley) 4/29/95";
+#else
+__RCSID("$NetBSD: cp.c,v 1.59 2016/03/05 19:48:55 uwe Exp $");
+#endif
+#endif /* not lint */
+
+/*
+ * Cp copies source files to target files.
+ *
+ * The global PATH_T structure "to" always contains the path to the
+ * current target file. Since fts(3) does not change directories,
+ * this path can be either absolute or dot-relative.
+ *
+ * The basic algorithm is to initialize "to" and use fts(3) to traverse
+ * the file hierarchy rooted in the argument list. A trivial case is the
+ * case of 'cp file1 file2'. The more interesting case is the case of
+ * 'cp file1 file2 ... fileN dir' where the hierarchy is traversed and the
+ * path (relative to the root of the traversal) is appended to dir (stored
+ * in "to") to form the final target path.
+ */
+
+#include <sys/param.h>
+#include <sys/stat.h>
+
+#include <assert.h>
+#include <err.h>
+#include <errno.h>
+#include <fts.h>
+#include <locale.h>
+#include <signal.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "extern.h"
+
+#define STRIP_TRAILING_SLASH(p) { \
+ while ((p).p_end > (p).p_path + 1 && (p).p_end[-1] == '/') \
+ *--(p).p_end = '\0'; \
+}
+
+static char empty[] = "";
+PATH_T to = { .p_end = to.p_path, .target_end = empty };
+
+uid_t myuid;
+int Hflag, Lflag, Rflag, Pflag, fflag, iflag, lflag, pflag, rflag, vflag, Nflag;
+mode_t myumask;
+sig_atomic_t pinfo;
+
+enum op { FILE_TO_FILE, FILE_TO_DIR, DIR_TO_DNE };
+
+static int copy(char *[], enum op, int);
+
+static void
+progress(int sig __unused)
+{
+
+ pinfo++;
+}
+
+int
+main(int argc, char *argv[])
+{
+ struct stat to_stat, tmp_stat;
+ enum op type;
+ int ch, fts_options, r, have_trailing_slash;
+ char *target, **src;
+
+ setprogname(argv[0]);
+ (void)setlocale(LC_ALL, "");
+
+ Hflag = Lflag = Pflag = Rflag = 0;
+ while ((ch = getopt(argc, argv, "HLNPRfailprv")) != -1)
+ switch (ch) {
+ case 'H':
+ Hflag = 1;
+ Lflag = Pflag = 0;
+ break;
+ case 'L':
+ Lflag = 1;
+ Hflag = Pflag = 0;
+ break;
+ case 'N':
+ Nflag = 1;
+ break;
+ case 'P':
+ Pflag = 1;
+ Hflag = Lflag = 0;
+ break;
+ case 'R':
+ Rflag = 1;
+ break;
+ case 'a':
+ Pflag = 1;
+ pflag = 1;
+ Rflag = 1;
+ Hflag = Lflag = 0;
+ break;
+ case 'f':
+ fflag = 1;
+ iflag = 0;
+ break;
+ case 'i':
+ iflag = 1;
+ fflag = 0;
+ break;
+ case 'l':
+ lflag = 1;
+ break;
+ case 'p':
+ pflag = 1;
+ break;
+ case 'r':
+ rflag = 1;
+ break;
+ case 'v':
+ vflag = 1;
+ break;
+ case '?':
+ default:
+ usage();
+ /* NOTREACHED */
+ }
+ argc -= optind;
+ argv += optind;
+
+ if (argc < 2)
+ usage();
+
+ fts_options = FTS_NOCHDIR | FTS_PHYSICAL;
+ if (rflag) {
+ if (Rflag) {
+ errx(EXIT_FAILURE,
+ "the -R and -r options may not be specified together.");
+ /* NOTREACHED */
+ }
+ if (Hflag || Lflag || Pflag) {
+ errx(EXIT_FAILURE,
+ "the -H, -L, and -P options may not be specified with the -r option.");
+ /* NOTREACHED */
+ }
+ fts_options &= ~FTS_PHYSICAL;
+ fts_options |= FTS_LOGICAL;
+ }
+
+ if (Rflag) {
+ if (Hflag)
+ fts_options |= FTS_COMFOLLOW;
+ if (Lflag) {
+ fts_options &= ~FTS_PHYSICAL;
+ fts_options |= FTS_LOGICAL;
+ }
+ } else if (!Pflag) {
+ fts_options &= ~FTS_PHYSICAL;
+ fts_options |= FTS_LOGICAL | FTS_COMFOLLOW;
+ }
+
+ myuid = getuid();
+
+ /* Copy the umask for explicit mode setting. */
+ myumask = umask(0);
+ (void)umask(myumask);
+
+ /* Save the target base in "to". */
+ target = argv[--argc];
+ if (strlcpy(to.p_path, target, sizeof(to.p_path)) >= sizeof(to.p_path))
+ errx(EXIT_FAILURE, "%s: name too long", target);
+ to.p_end = to.p_path + strlen(to.p_path);
+ have_trailing_slash = (to.p_end[-1] == '/');
+ if (have_trailing_slash)
+ STRIP_TRAILING_SLASH(to);
+ to.target_end = to.p_end;
+
+ /* Set end of argument list for fts(3). */
+ argv[argc] = NULL;
+
+ (void)signal(SIGINFO, progress);
+
+ /*
+ * Cp has two distinct cases:
+ *
+ * cp [-R] source target
+ * cp [-R] source1 ... sourceN directory
+ *
+ * In both cases, source can be either a file or a directory.
+ *
+ * In (1), the target becomes a copy of the source. That is, if the
+ * source is a file, the target will be a file, and likewise for
+ * directories.
+ *
+ * In (2), the real target is not directory, but "directory/source".
+ */
+ if (Pflag)
+ r = lstat(to.p_path, &to_stat);
+ else
+ r = stat(to.p_path, &to_stat);
+ if (r == -1 && errno != ENOENT) {
+ err(EXIT_FAILURE, "%s", to.p_path);
+ /* NOTREACHED */
+ }
+ if (r == -1 || !S_ISDIR(to_stat.st_mode)) {
+ /*
+ * Case (1). Target is not a directory.
+ */
+ if (argc > 1)
+ usage();
+ /*
+ * Need to detect the case:
+ * cp -R dir foo
+ * Where dir is a directory and foo does not exist, where
+ * we want pathname concatenations turned on but not for
+ * the initial mkdir().
+ */
+ if (r == -1) {
+ if (rflag || (Rflag && (Lflag || Hflag)))
+ r = stat(*argv, &tmp_stat);
+ else
+ r = lstat(*argv, &tmp_stat);
+ if (r == -1) {
+ err(EXIT_FAILURE, "%s", *argv);
+ /* NOTREACHED */
+ }
+
+ if (S_ISDIR(tmp_stat.st_mode) && (Rflag || rflag))
+ type = DIR_TO_DNE;
+ else
+ type = FILE_TO_FILE;
+ } else
+ type = FILE_TO_FILE;
+
+ if (have_trailing_slash && type == FILE_TO_FILE) {
+ if (r == -1)
+ errx(1, "directory %s does not exist",
+ to.p_path);
+ else
+ errx(1, "%s is not a directory", to.p_path);
+ }
+ } else {
+ /*
+ * Case (2). Target is a directory.
+ */
+ type = FILE_TO_DIR;
+ }
+
+ /*
+ * make "cp -rp src/ dst" behave like "cp -rp src dst" not
+ * like "cp -rp src/. dst"
+ */
+ for (src = argv; *src; src++) {
+ size_t len = strlen(*src);
+ while (len-- > 1 && (*src)[len] == '/')
+ (*src)[len] = '\0';
+ }
+
+ exit(copy(argv, type, fts_options));
+ /* NOTREACHED */
+}
+
+static int dnestack[MAXPATHLEN]; /* unlikely we'll have more nested dirs */
+static ssize_t dnesp;
+static void
+pushdne(int dne)
+{
+
+ dnestack[dnesp++] = dne;
+ assert(dnesp < MAXPATHLEN);
+}
+
+static int
+popdne(void)
+{
+ int rv;
+
+ rv = dnestack[--dnesp];
+ assert(dnesp >= 0);
+ return rv;
+}
+
+static int
+copy(char *argv[], enum op type, int fts_options)
+{
+ struct stat to_stat;
+ FTS *ftsp;
+ FTSENT *curr;
+ int base, dne, sval;
+ int this_failed, any_failed;
+ size_t nlen;
+ char *p, *target_mid;
+
+ base = 0; /* XXX gcc -Wuninitialized (see comment below) */
+
+ if ((ftsp = fts_open(argv, fts_options, NULL)) == NULL)
+ err(EXIT_FAILURE, "%s", argv[0]);
+ /* NOTREACHED */
+ for (any_failed = 0; (curr = fts_read(ftsp)) != NULL;) {
+ this_failed = 0;
+ switch (curr->fts_info) {
+ case FTS_NS:
+ case FTS_DNR:
+ case FTS_ERR:
+ warnx("%s: %s", curr->fts_path,
+ strerror(curr->fts_errno));
+ this_failed = any_failed = 1;
+ continue;
+ case FTS_DC: /* Warn, continue. */
+ warnx("%s: directory causes a cycle", curr->fts_path);
+ this_failed = any_failed = 1;
+ continue;
+ }
+
+ /*
+ * If we are in case (2) or (3) above, we need to append the
+ * source name to the target name.
+ */
+ if (type != FILE_TO_FILE) {
+ if ((curr->fts_namelen +
+ to.target_end - to.p_path + 1) > MAXPATHLEN) {
+ warnx("%s/%s: name too long (not copied)",
+ to.p_path, curr->fts_name);
+ this_failed = any_failed = 1;
+ continue;
+ }
+
+ /*
+ * Need to remember the roots of traversals to create
+ * correct pathnames. If there's a directory being
+ * copied to a non-existent directory, e.g.
+ * cp -R a/dir noexist
+ * the resulting path name should be noexist/foo, not
+ * noexist/dir/foo (where foo is a file in dir), which
+ * is the case where the target exists.
+ *
+ * Also, check for "..". This is for correct path
+ * concatentation for paths ending in "..", e.g.
+ * cp -R .. /tmp
+ * Paths ending in ".." are changed to ".". This is
+ * tricky, but seems the easiest way to fix the problem.
+ *
+ * XXX
+ * Since the first level MUST be FTS_ROOTLEVEL, base
+ * is always initialized.
+ */
+ if (curr->fts_level == FTS_ROOTLEVEL) {
+ if (type != DIR_TO_DNE) {
+ p = strrchr(curr->fts_path, '/');
+ base = (p == NULL) ? 0 :
+ (int)(p - curr->fts_path + 1);
+
+ if (!strcmp(&curr->fts_path[base],
+ ".."))
+ base += 1;
+ } else
+ base = curr->fts_pathlen;
+ }
+
+ p = &curr->fts_path[base];
+ nlen = curr->fts_pathlen - base;
+ target_mid = to.target_end;
+ if (*p != '/' && target_mid[-1] != '/')
+ *target_mid++ = '/';
+ *target_mid = 0;
+
+ if (target_mid - to.p_path + nlen >= PATH_MAX) {
+ warnx("%s%s: name too long (not copied)",
+ to.p_path, p);
+ this_failed = any_failed = 1;
+ continue;
+ }
+ (void)strncat(target_mid, p, nlen);
+ to.p_end = target_mid + nlen;
+ *to.p_end = 0;
+ STRIP_TRAILING_SLASH(to);
+ }
+
+ sval = Pflag ? lstat(to.p_path, &to_stat) : stat(to.p_path, &to_stat);
+ /* Not an error but need to remember it happened */
+ if (sval == -1)
+ dne = 1;
+ else {
+ if (to_stat.st_dev == curr->fts_statp->st_dev &&
+ to_stat.st_ino == curr->fts_statp->st_ino) {
+ warnx("%s and %s are identical (not copied).",
+ to.p_path, curr->fts_path);
+ this_failed = any_failed = 1;
+ if (S_ISDIR(curr->fts_statp->st_mode))
+ (void)fts_set(ftsp, curr, FTS_SKIP);
+ continue;
+ }
+ if (!S_ISDIR(curr->fts_statp->st_mode) &&
+ S_ISDIR(to_stat.st_mode)) {
+ warnx("cannot overwrite directory %s with non-directory %s",
+ to.p_path, curr->fts_path);
+ this_failed = any_failed = 1;
+ continue;
+ }
+ dne = 0;
+ }
+
+ switch (curr->fts_statp->st_mode & S_IFMT) {
+ case S_IFLNK:
+ /* Catch special case of a non dangling symlink */
+ if((fts_options & FTS_LOGICAL) ||
+ ((fts_options & FTS_COMFOLLOW) && curr->fts_level == 0)) {
+ if (copy_file(curr, dne))
+ this_failed = any_failed = 1;
+ } else {
+ if (copy_link(curr, !dne))
+ this_failed = any_failed = 1;
+ }
+ break;
+ case S_IFDIR:
+ if (!Rflag && !rflag) {
+ if (curr->fts_info == FTS_D)
+ warnx("%s is a directory (not copied).",
+ curr->fts_path);
+ (void)fts_set(ftsp, curr, FTS_SKIP);
+ this_failed = any_failed = 1;
+ break;
+ }
+
+ /*
+ * Directories get noticed twice:
+ * In the first pass, create it if needed.
+ * In the second pass, after the children have been copied, set the permissions.
+ */
+ if (curr->fts_info == FTS_D) /* First pass */
+ {
+ /*
+ * If the directory doesn't exist, create the new
+ * one with the from file mode plus owner RWX bits,
+ * modified by the umask. Trade-off between being
+ * able to write the directory (if from directory is
+ * 555) and not causing a permissions race. If the
+ * umask blocks owner writes, we fail..
+ */
+ pushdne(dne);
+ if (dne) {
+ if (mkdir(to.p_path,
+ curr->fts_statp->st_mode | S_IRWXU) < 0)
+ err(EXIT_FAILURE, "%s",
+ to.p_path);
+ /* NOTREACHED */
+ } else if (!S_ISDIR(to_stat.st_mode)) {
+ errno = ENOTDIR;
+ err(EXIT_FAILURE, "%s",
+ to.p_path);
+ /* NOTREACHED */
+ }
+ }
+ else if (curr->fts_info == FTS_DP) /* Second pass */
+ {
+ /*
+ * If not -p and directory didn't exist, set it to be
+ * the same as the from directory, umodified by the
+ * umask; arguably wrong, but it's been that way
+ * forever.
+ */
+ if (pflag && setfile(curr->fts_statp, 0))
+ this_failed = any_failed = 1;
+ else if ((dne = popdne()))
+ (void)chmod(to.p_path,
+ curr->fts_statp->st_mode);
+ }
+ else
+ {
+ warnx("directory %s encountered when not expected.",
+ curr->fts_path);
+ this_failed = any_failed = 1;
+ break;
+ }
+
+ break;
+ case S_IFBLK:
+ case S_IFCHR:
+ if (Rflag) {
+ if (copy_special(curr->fts_statp, !dne))
+ this_failed = any_failed = 1;
+ } else
+ if (copy_file(curr, dne))
+ this_failed = any_failed = 1;
+ break;
+ case S_IFIFO:
+ if (Rflag) {
+ if (copy_fifo(curr->fts_statp, !dne))
+ this_failed = any_failed = 1;
+ } else
+ if (copy_file(curr, dne))
+ this_failed = any_failed = 1;
+ break;
+ default:
+ if (copy_file(curr, dne))
+ this_failed = any_failed = 1;
+ break;
+ }
+ if (vflag && !this_failed)
+ (void)printf("%s -> %s\n", curr->fts_path, to.p_path);
+ }
+ if (errno) {
+ err(EXIT_FAILURE, "fts_read");
+ /* NOTREACHED */
+ }
+ (void)fts_close(ftsp);
+ return (any_failed);
+}
diff --git a/bin/cp/extern.h b/bin/cp/extern.h
new file mode 100644
index 0000000..e393844
--- /dev/null
+++ b/bin/cp/extern.h
@@ -0,0 +1,61 @@
+/* $NetBSD: extern.h,v 1.17 2012/01/04 15:58:37 christos Exp $ */
+
+/*-
+ * Copyright (c) 1991, 1993, 1994
+ * The Regents of the University of California. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * @(#)extern.h 8.2 (Berkeley) 4/1/94
+ */
+
+#ifndef _EXTERN_H_
+#define _EXTERN_H_
+
+typedef struct {
+ char *p_end; /* pointer to NULL at end of path */
+ char *target_end; /* pointer to end of target base */
+ char p_path[MAXPATHLEN + 1]; /* pointer to the start of a path */
+} PATH_T;
+
+extern PATH_T to;
+extern uid_t myuid;
+extern int Rflag, rflag, Hflag, Lflag, Pflag, fflag, iflag, lflag, pflag, Nflag;
+extern mode_t myumask;
+extern sig_atomic_t pinfo;
+
+#include <sys/cdefs.h>
+
+__BEGIN_DECLS
+int copy_fifo(struct stat *, int);
+int copy_file(FTSENT *, int);
+int copy_link(FTSENT *, int);
+int copy_special(struct stat *, int);
+int set_utimes(const char *, struct stat *);
+int setfile(struct stat *, int);
+void usage(void) __attribute__((__noreturn__));
+__END_DECLS
+
+#endif /* !_EXTERN_H_ */
diff --git a/bin/cp/utils.c b/bin/cp/utils.c
new file mode 100644
index 0000000..6275fc6
--- /dev/null
+++ b/bin/cp/utils.c
@@ -0,0 +1,419 @@
+/* $NetBSD: utils.c,v 1.46 2018/07/17 13:04:58 darcy Exp $ */
+
+/*-
+ * Copyright (c) 1991, 1993, 1994
+ * The Regents of the University of California. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+#ifndef lint
+#if 0
+static char sccsid[] = "@(#)utils.c 8.3 (Berkeley) 4/1/94";
+#else
+__RCSID("$NetBSD: utils.c,v 1.46 2018/07/17 13:04:58 darcy Exp $");
+#endif
+#endif /* not lint */
+
+#include <sys/mman.h>
+#include <sys/param.h>
+#include <sys/stat.h>
+#include <sys/time.h>
+#include <sys/extattr.h>
+
+#include <err.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <fts.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "extern.h"
+
+#define MMAP_MAX_SIZE (8 * 1048576)
+#define MMAP_MAX_WRITE (64 * 1024)
+
+int
+set_utimes(const char *file, struct stat *fs)
+{
+ struct timespec ts[2];
+
+ ts[0] = fs->st_atimespec;
+ ts[1] = fs->st_mtimespec;
+
+ if (lutimens(file, ts)) {
+ warn("lutimens: %s", file);
+ return (1);
+ }
+ return (0);
+}
+
+struct finfo {
+ const char *from;
+ const char *to;
+ off_t size;
+};
+
+static void
+progress(const struct finfo *fi, off_t written)
+{
+ int pcent = (int)((100.0 * written) / fi->size);
+
+ pinfo = 0;
+ (void)fprintf(stderr, "%s => %s %llu/%llu bytes %d%% written\n",
+ fi->from, fi->to, (unsigned long long)written,
+ (unsigned long long)fi->size, pcent);
+}
+
+int
+copy_file(FTSENT *entp, int dne)
+{
+ static char buf[MAXBSIZE];
+ struct stat to_stat, *fs;
+ int ch, checkch, from_fd, rcount, rval, to_fd, tolnk, wcount;
+ char *p;
+ off_t ptotal = 0;
+
+ /* if hard linking then simply link and return */
+ if (lflag) {
+ (void)unlink(to.p_path);
+ if (link(entp->fts_path, to.p_path)) {
+ warn("%s", to.p_path);
+ return (1);
+ }
+ return (0);
+ }
+
+ if ((from_fd = open(entp->fts_path, O_RDONLY, 0)) == -1) {
+ warn("%s", entp->fts_path);
+ return (1);
+ }
+
+ to_fd = -1;
+ fs = entp->fts_statp;
+ tolnk = ((Rflag && !(Lflag || Hflag)) || Pflag);
+
+ /*
+ * If the file exists and we're interactive, verify with the user.
+ * If the file DNE, set the mode to be the from file, minus setuid
+ * bits, modified by the umask; arguably wrong, but it makes copying
+ * executables work right and it's been that way forever. (The
+ * other choice is 666 or'ed with the execute bits on the from file
+ * modified by the umask.)
+ */
+ if (!dne) {
+ struct stat sb;
+ int sval;
+
+ if (iflag) {
+ (void)fprintf(stderr, "overwrite %s? ", to.p_path);
+ checkch = ch = getchar();
+ while (ch != '\n' && ch != EOF)
+ ch = getchar();
+ if (checkch != 'y' && checkch != 'Y') {
+ (void)close(from_fd);
+ return (0);
+ }
+ }
+
+ sval = tolnk ?
+ lstat(to.p_path, &sb) : stat(to.p_path, &sb);
+ if (sval == -1) {
+ warn("stat: %s", to.p_path);
+ (void)close(from_fd);
+ return (1);
+ }
+
+ if (!(tolnk && S_ISLNK(sb.st_mode)))
+ to_fd = open(to.p_path, O_WRONLY | O_TRUNC, 0);
+ } else
+ to_fd = open(to.p_path, O_WRONLY | O_TRUNC | O_CREAT,
+ fs->st_mode & ~(S_ISUID | S_ISGID));
+
+ if (to_fd == -1 && (fflag || tolnk)) {
+ /*
+ * attempt to remove existing destination file name and
+ * create a new file
+ */
+ (void)unlink(to.p_path);
+ to_fd = open(to.p_path, O_WRONLY | O_TRUNC | O_CREAT,
+ fs->st_mode & ~(S_ISUID | S_ISGID));
+ }
+
+ if (to_fd == -1) {
+ warn("%s", to.p_path);
+ (void)close(from_fd);
+ return (1);
+ }
+
+ rval = 0;
+
+ /*
+ * There's no reason to do anything other than close the file
+ * now if it's empty, so let's not bother.
+ */
+ if (fs->st_size > 0) {
+ struct finfo fi;
+
+ fi.from = entp->fts_path;
+ fi.to = to.p_path;
+ fi.size = fs->st_size;
+
+ /*
+ * Mmap and write if less than 8M (the limit is so
+ * we don't totally trash memory on big files).
+ * This is really a minor hack, but it wins some CPU back.
+ */
+ bool use_read;
+
+ use_read = true;
+ if (fs->st_size <= MMAP_MAX_SIZE) {
+ size_t fsize = (size_t)fs->st_size;
+ p = mmap(NULL, fsize, PROT_READ, MAP_FILE|MAP_SHARED,
+ from_fd, (off_t)0);
+ if (p != MAP_FAILED) {
+ size_t remainder;
+
+ use_read = false;
+
+ (void) madvise(p, (size_t)fs->st_size,
+ MADV_SEQUENTIAL);
+
+ /*
+ * Write out the data in small chunks to
+ * avoid locking the output file for a
+ * long time if the reading the data from
+ * the source is slow.
+ */
+ remainder = fsize;
+ do {
+ ssize_t chunk;
+
+ chunk = (remainder > MMAP_MAX_WRITE) ?
+ MMAP_MAX_WRITE : remainder;
+ if (write(to_fd, &p[fsize - remainder],
+ chunk) != chunk) {
+ warn("%s", to.p_path);
+ rval = 1;
+ break;
+ }
+ remainder -= chunk;
+ ptotal += chunk;
+ if (pinfo)
+ progress(&fi, ptotal);
+ } while (remainder > 0);
+
+ if (munmap(p, fsize) < 0) {
+ warn("%s", entp->fts_path);
+ rval = 1;
+ }
+ }
+ }
+
+ if (use_read) {
+ while ((rcount = read(from_fd, buf, MAXBSIZE)) > 0) {
+ wcount = write(to_fd, buf, (size_t)rcount);
+ if (rcount != wcount || wcount == -1) {
+ warn("%s", to.p_path);
+ rval = 1;
+ break;
+ }
+ ptotal += wcount;
+ if (pinfo)
+ progress(&fi, ptotal);
+ }
+ if (rcount < 0) {
+ warn("%s", entp->fts_path);
+ rval = 1;
+ }
+ }
+ }
+
+ if (pflag && (fcpxattr(from_fd, to_fd) != 0))
+ warn("%s: error copying extended attributes", to.p_path);
+
+ (void)close(from_fd);
+
+ if (rval == 1) {
+ (void)close(to_fd);
+ return (1);
+ }
+
+ if (pflag && setfile(fs, to_fd))
+ rval = 1;
+ /*
+ * If the source was setuid or setgid, lose the bits unless the
+ * copy is owned by the same user and group.
+ */
+#define RETAINBITS \
+ (S_ISUID | S_ISGID | S_ISVTX | S_IRWXU | S_IRWXG | S_IRWXO)
+ if (!pflag && dne
+ && fs->st_mode & (S_ISUID | S_ISGID) && fs->st_uid == myuid) {
+ if (fstat(to_fd, &to_stat)) {
+ warn("%s", to.p_path);
+ rval = 1;
+ } else if (fs->st_gid == to_stat.st_gid &&
+ fchmod(to_fd, fs->st_mode & RETAINBITS & ~myumask)) {
+ warn("%s", to.p_path);
+ rval = 1;
+ }
+ }
+ if (close(to_fd)) {
+ warn("%s", to.p_path);
+ rval = 1;
+ }
+ /* set the mod/access times now after close of the fd */
+ if (pflag && set_utimes(to.p_path, fs)) {
+ rval = 1;
+ }
+ return (rval);
+}
+
+int
+copy_link(FTSENT *p, int exists)
+{
+ int len;
+ char target[MAXPATHLEN];
+
+ if ((len = readlink(p->fts_path, target, sizeof(target)-1)) == -1) {
+ warn("readlink: %s", p->fts_path);
+ return (1);
+ }
+ target[len] = '\0';
+ if (exists && unlink(to.p_path)) {
+ warn("unlink: %s", to.p_path);
+ return (1);
+ }
+ if (symlink(target, to.p_path)) {
+ warn("symlink: %s", target);
+ return (1);
+ }
+ return (pflag ? setfile(p->fts_statp, 0) : 0);
+}
+
+int
+copy_fifo(struct stat *from_stat, int exists)
+{
+ if (exists && unlink(to.p_path)) {
+ warn("unlink: %s", to.p_path);
+ return (1);
+ }
+ if (mkfifo(to.p_path, from_stat->st_mode)) {
+ warn("mkfifo: %s", to.p_path);
+ return (1);
+ }
+ return (pflag ? setfile(from_stat, 0) : 0);
+}
+
+int
+copy_special(struct stat *from_stat, int exists)
+{
+ if (exists && unlink(to.p_path)) {
+ warn("unlink: %s", to.p_path);
+ return (1);
+ }
+ if (mknod(to.p_path, from_stat->st_mode, from_stat->st_rdev)) {
+ warn("mknod: %s", to.p_path);
+ return (1);
+ }
+ return (pflag ? setfile(from_stat, 0) : 0);
+}
+
+
+/*
+ * Function: setfile
+ *
+ * Purpose:
+ * Set the owner/group/permissions for the "to" file to the information
+ * in the stat structure. If fd is zero, also call set_utimes() to set
+ * the mod/access times. If fd is non-zero, the caller must do a utimes
+ * itself after close(fd).
+ */
+int
+setfile(struct stat *fs, int fd)
+{
+ int rval, islink;
+
+ rval = 0;
+ islink = S_ISLNK(fs->st_mode);
+ fs->st_mode &= S_ISUID | S_ISGID | S_IRWXU | S_IRWXG | S_IRWXO;
+
+ /*
+ * Changing the ownership probably won't succeed, unless we're root
+ * or POSIX_CHOWN_RESTRICTED is not set. Set uid/gid before setting
+ * the mode; current BSD behavior is to remove all setuid bits on
+ * chown. If chown fails, lose setuid/setgid bits.
+ */
+ if (fd ? fchown(fd, fs->st_uid, fs->st_gid) :
+ lchown(to.p_path, fs->st_uid, fs->st_gid)) {
+ if (errno != EPERM) {
+ warn("chown: %s", to.p_path);
+ rval = 1;
+ }
+ fs->st_mode &= ~(S_ISUID | S_ISGID);
+ }
+ if (fd ? fchmod(fd, fs->st_mode) : lchmod(to.p_path, fs->st_mode)) {
+ warn("chmod: %s", to.p_path);
+ rval = 1;
+ }
+
+ if (!islink && !Nflag) {
+ unsigned long fflags = fs->st_flags;
+ /*
+ * XXX
+ * NFS doesn't support chflags; ignore errors unless
+ * there's reason to believe we're losing bits.
+ * (Note, this still won't be right if the server
+ * supports flags and we were trying to *remove* flags
+ * on a file that we copied, i.e., that we didn't create.)
+ */
+ errno = 0;
+ if ((fd ? fchflags(fd, fflags) :
+ chflags(to.p_path, fflags)) == -1)
+ if (errno != EOPNOTSUPP || fs->st_flags != 0) {
+ warn("chflags: %s", to.p_path);
+ rval = 1;
+ }
+ }
+ /* if fd is non-zero, caller must call set_utimes() after close() */
+ if (fd == 0 && set_utimes(to.p_path, fs))
+ rval = 1;
+ return (rval);
+}
+
+void
+usage(void)
+{
+ (void)fprintf(stderr,
+ "usage: %s [-R [-H | -L | -P]] [-f | -i] [-alNpv] src target\n"
+ " %s [-R [-H | -L | -P]] [-f | -i] [-alNpv] src1 ... srcN directory\n",
+ getprogname(), getprogname());
+ exit(1);
+ /* NOTREACHED */
+}
diff --git a/bin/date/date.1 b/bin/date/date.1
new file mode 100644
index 0000000..f15119a
--- /dev/null
+++ b/bin/date/date.1
@@ -0,0 +1,262 @@
+.\" $NetBSD: date.1,v 1.47 2018/01/27 18:59:38 wiz Exp $
+.\"
+.\" Copyright (c) 1980, 1990, 1993
+.\" The Regents of the University of California. All rights reserved.
+.\"
+.\" This code is derived from software contributed to Berkeley by
+.\" the Institute of Electrical and Electronics Engineers, Inc.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\" notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must reproduce the above copyright
+.\" notice, this list of conditions and the following disclaimer in the
+.\" documentation and/or other materials provided with the distribution.
+.\" 3. Neither the name of the University nor the names of its contributors
+.\" may be used to endorse or promote products derived from this software
+.\" without specific prior written permission.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+.\" ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+.\" SUCH DAMAGE.
+.\"
+.\" @(#)date.1 8.3 (Berkeley) 4/28/95
+.\"
+.Dd January 25, 2018
+.Dt DATE 1
+.Os
+.Sh NAME
+.Nm date
+.Nd display or set date and time
+.Sh SYNOPSIS
+.Nm
+.Op Fl ajnu
+.Op Fl d Ar date
+.Op Fl r Ar seconds
+.Op Cm + Ns Ar format
+.Sm off
+.Oo Oo Oo Oo Oo Oo
+.Ar CC Oc
+.Ar yy Oc
+.Ar mm Oc
+.Ar dd Oc
+.Ar HH Oc Ar MM Oo
+.Li \&. Ar SS Oc Oc
+.Sm on
+.Sh DESCRIPTION
+.Nm
+displays the current date and time when invoked without arguments.
+Providing arguments will format the date and time in a user-defined
+way or set the date.
+Only the superuser may set the date.
+.Pp
+The options are as follows:
+.Bl -tag -width Ds
+.It Fl a
+Use
+.Xr adjtime 2
+to change the local system time slowly,
+maintaining it as a monotonically increasing function.
+.Fl a
+implies
+.Fl n .
+.It Fl d Ar date
+Parse the provided human-described date and time and display the result without
+actually changing the system clock.
+(See
+.Xr parsedate 3
+for examples.)
+.It Fl j
+Parse the provided canonical representation of date and time (described below)
+and display the result without actually changing the system clock.
+.It Fl n
+The utility
+.Xr timed 8
+is used to synchronize the clocks on groups of machines.
+By default, if
+.Xr timed 8
+is running,
+.Nm
+will set the time on all of the machines in the local group.
+The
+.Fl n
+option stops
+.Nm
+from setting the time for other than the current machine.
+.It Fl r Ar seconds
+Print out the date and time that is
+.Ar seconds
+from the Epoch.
+.It Fl u
+Display or set the date in UTC (universal) time.
+.El
+.Pp
+An operand with a leading plus
+.Pq Cm +
+sign signals a user-defined format
+string which specifies the format in which to display the date and time.
+The format string may contain any of the conversion specifications described
+in the
+.Xr strftime 3
+manual page, as well as any arbitrary text.
+A <newline> character is always output after the characters
+specified by the format string.
+The format string for the default display is:
+.Bd -literal -offset indent
+%a %b %e %H:%M:%S %Z %Y
+.Ed
+.Pp
+If an operand does not have a leading plus sign, it is interpreted as
+a value for setting the system's notion of the current date and time.
+The canonical representation for setting the date and time is:
+.Pp
+.Bl -tag -width Ds -compact -offset indent
+.It Ar CC
+The first two digits of the year (the century).
+.It Ar yy
+The second two digits of the year.
+If
+.Ar yy
+is specified, but
+.Ar CC
+is not, a value for
+.Ar yy
+between 69 and 99 results in a
+.Ar CC
+value of 19.
+Otherwise, a
+.Ar CC
+value of 20 is used.
+.It Ar mm
+The month of the year, from 01 to 12.
+.It Ar dd
+The day of the month, from 01 to 31.
+.It Ar HH
+The hour of the day, from 00 to 23.
+.It Ar MM
+The minute of the hour, from 00 to 59.
+.It Ar SS
+The second of the minute, from 00 to 60.
+.El
+.Pp
+Everything but the minutes is optional.
+.Pp
+Time changes for Daylight Saving and Standard Time and leap seconds
+and years are handled automatically.
+.Sh ENVIRONMENT
+The following environment variables affect the execution of
+.Nm :
+.Bl -tag -width iTZ
+.It Ev TZ
+The timezone to use when displaying dates.
+See
+.Xr environ 7
+for more information.
+.El
+.Sh FILES
+.Bl -tag -width /usr/share/zoneinfo/posixrules -compact
+.It Pa /etc/localtime
+Symlink pointing to system's default timezone information file in
+.Pa /usr/share/zoneinfo
+directory.
+.It Pa /usr/lib/locale/<L>/LC_TIME
+Description of time locale <L>.
+.It Pa /usr/share/zoneinfo
+Time zone information directory.
+.It Pa /usr/share/zoneinfo/posixrules
+Used with POSIX-style TZ's.
+.It Pa /usr/share/zoneinfo/GMT
+For UTC leap seconds.
+.It Pa /var/log/wtmp
+A record of date resets and time changes.
+.It Pa /var/log/messages
+A record of the user setting the time.
+.El
+.Pp
+If
+.Pa /usr/share/zoneinfo/GMT
+is absent, UTC leap seconds are loaded from
+.Pa /usr/share/zoneinfo/posixrules .
+.Sh EXAMPLES
+The command:
+.Bd -literal -offset indent
+date '+DATE: %m/%d/%y%nTIME: %H:%M:%S'
+.Ed
+.Pp
+will display:
+.Bd -literal -offset indent
+DATE: 11/21/87
+TIME: 13:36:16
+.Ed
+.Pp
+The command:
+.Bd -literal -offset indent
+date 8506131627
+.Ed
+.Pp
+sets the date to
+.Dq Li "June 13, 1985, 4:27 PM" .
+.Pp
+The command:
+.Bd -literal -offset indent
+date 1432
+.Ed
+.Pp
+sets the time to
+.Li "2:32 PM" ,
+without modifying the date.
+.Sh DIAGNOSTICS
+Exit status is 0 on success, 1 if unable to set the date, and 2
+if able to set the local date, but unable to set it globally.
+.Pp
+Occasionally, when
+.Xr timed 8
+synchronizes the time on many hosts, the setting of a new time value may
+require more than a few seconds.
+On these occasions,
+.Nm
+prints:
+.Ql Network time being set .
+The message
+.Ql Communication error with
+.Xr timed 8
+occurs when the communication
+between
+.Nm
+and
+.Xr timed 8
+fails.
+.Sh SEE ALSO
+.Xr adjtime 2 ,
+.Xr gettimeofday 2 ,
+.Xr settimeofday 2 ,
+.Xr parsedate 3 ,
+.Xr strftime 3 ,
+.Xr utmp 5 ,
+.Xr environ 7 ,
+.Xr timed 8
+.Rs
+.%T "TSP: The Time Synchronization Protocol for UNIX 4.3BSD"
+.%A R. Gusella
+.%A S. Zatti
+.Re
+.Sh STANDARDS
+The
+.Nm
+utility is expected to be compatible with
+.St -p1003.2 .
+.Sh HISTORY
+A
+.Nm
+utility appeared in
+.At v1 .
diff --git a/bin/date/date.c b/bin/date/date.c
new file mode 100644
index 0000000..a067457
--- /dev/null
+++ b/bin/date/date.c
@@ -0,0 +1,364 @@
+/* $NetBSD: date.c,v 1.61 2014/09/01 21:42:21 dholland Exp $ */
+
+/*
+ * Copyright (c) 1985, 1987, 1988, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+#ifndef lint
+__COPYRIGHT(
+"@(#) Copyright (c) 1985, 1987, 1988, 1993\
+ The Regents of the University of California. All rights reserved.");
+#endif /* not lint */
+
+#ifndef lint
+#if 0
+static char sccsid[] = "@(#)date.c 8.2 (Berkeley) 4/28/95";
+#else
+__RCSID("$NetBSD: date.c,v 1.61 2014/09/01 21:42:21 dholland Exp $");
+#endif
+#endif /* not lint */
+
+#include <sys/param.h>
+#include <sys/time.h>
+
+#include <ctype.h>
+#include <err.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <locale.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <syslog.h>
+#include <time.h>
+#include <tzfile.h>
+#include <unistd.h>
+#include <util.h>
+
+#include "extern.h"
+
+static time_t tval;
+static int aflag, jflag, rflag, nflag;
+
+__dead static void badcanotime(const char *, const char *, size_t);
+static void setthetime(const char *);
+__dead static void usage(void);
+
+int
+main(int argc, char *argv[])
+{
+ char *buf;
+ size_t bufsiz;
+ const char *format;
+ int ch;
+ long long val;
+ struct tm *tm;
+
+ setprogname(argv[0]);
+ (void)setlocale(LC_ALL, "");
+
+ while ((ch = getopt(argc, argv, "ad:jnr:u")) != -1) {
+ switch (ch) {
+ case 'a': /* adjust time slowly */
+ aflag = 1;
+ nflag = 1;
+ break;
+ case 'd':
+ rflag = 1;
+ tval = parsedate(optarg, NULL, NULL);
+ if (tval == -1) {
+ errx(EXIT_FAILURE,
+ "%s: Unrecognized date format", optarg);
+ }
+ break;
+ case 'j': /* don't set time */
+ jflag = 1;
+ break;
+ case 'n': /* don't set network */
+ nflag = 1;
+ break;
+ case 'r': /* user specified seconds */
+ if (optarg[0] == '\0') {
+ errx(EXIT_FAILURE, "<empty>: Invalid number");
+ }
+ errno = 0;
+ val = strtoll(optarg, &buf, 0);
+ if (errno) {
+ err(EXIT_FAILURE, "%s", optarg);
+ }
+ if (optarg[0] == '\0' || *buf != '\0') {
+ errx(EXIT_FAILURE,
+ "%s: Invalid number", optarg);
+ }
+ rflag = 1;
+ tval = (time_t)val;
+ break;
+ case 'u': /* do everything in UTC */
+ (void)setenv("TZ", "UTC0", 1);
+ break;
+ default:
+ usage();
+ }
+ }
+ argc -= optind;
+ argv += optind;
+
+ if (!rflag && time(&tval) == -1)
+ err(EXIT_FAILURE, "time");
+
+
+ /* allow the operands in any order */
+ if (*argv && **argv == '+') {
+ format = *argv;
+ ++argv;
+ } else
+ format = "+%a %b %e %H:%M:%S %Z %Y";
+
+ if (*argv) {
+ setthetime(*argv);
+ ++argv;
+ }
+
+ if (*argv && **argv == '+')
+ format = *argv;
+
+ if ((buf = malloc(bufsiz = 1024)) == NULL)
+ goto bad;
+
+ if ((tm = localtime(&tval)) == NULL)
+ err(EXIT_FAILURE, "%lld: localtime", (long long)tval);
+
+ while (strftime(buf, bufsiz, format, tm) == 0)
+ if ((buf = realloc(buf, bufsiz <<= 1)) == NULL)
+ goto bad;
+
+ (void)printf("%s\n", buf + 1);
+ free(buf);
+ return 0;
+bad:
+ err(EXIT_FAILURE, "Cannot allocate format buffer");
+}
+
+static void
+badcanotime(const char *msg, const char *val, size_t where)
+{
+ warnx("%s in canonical time", msg);
+ warnx("%s", val);
+ warnx("%*s", (int)where + 1, "^");
+ usage();
+}
+
+#define ATOI2(s) ((s) += 2, ((s)[-2] - '0') * 10 + ((s)[-1] - '0'))
+
+static void
+setthetime(const char *p)
+{
+ struct timeval tv;
+ time_t new_time;
+ struct tm *lt;
+ const char *dot, *t, *op;
+ size_t len;
+ int yearset;
+
+ for (t = p, dot = NULL; *t; ++t) {
+ if (*t == '.') {
+ if (dot == NULL) {
+ dot = t;
+ } else {
+ badcanotime("Unexpected dot", p, t - p);
+ }
+ } else if (!isdigit((unsigned char)*t)) {
+ badcanotime("Expected digit", p, t - p);
+ }
+ }
+
+ if ((lt = localtime(&tval)) == NULL)
+ err(EXIT_FAILURE, "%lld: localtime", (long long)tval);
+
+ lt->tm_isdst = -1; /* Divine correct DST */
+
+ if (dot != NULL) { /* .ss */
+ len = strlen(dot);
+ if (len > 3) {
+ badcanotime("Unexpected digit after seconds field",
+ p, strlen(p) - 1);
+ } else if (len < 3) {
+ badcanotime("Expected digit in seconds field",
+ p, strlen(p));
+ }
+ ++dot;
+ lt->tm_sec = ATOI2(dot);
+ if (lt->tm_sec > 61)
+ badcanotime("Seconds out of range", p, strlen(p) - 1);
+ } else {
+ len = 0;
+ lt->tm_sec = 0;
+ }
+
+ op = p;
+ yearset = 0;
+ switch (strlen(p) - len) {
+ case 12: /* cc */
+ lt->tm_year = ATOI2(p) * 100 - TM_YEAR_BASE;
+ if (lt->tm_year < 0)
+ badcanotime("Year before 1900", op, p - op + 1);
+ yearset = 1;
+ /* FALLTHROUGH */
+ case 10: /* yy */
+ if (yearset) {
+ lt->tm_year += ATOI2(p);
+ } else {
+ yearset = ATOI2(p);
+ if (yearset < 69)
+ lt->tm_year = yearset + 2000 - TM_YEAR_BASE;
+ else
+ lt->tm_year = yearset + 1900 - TM_YEAR_BASE;
+ }
+ /* FALLTHROUGH */
+ case 8: /* mm */
+ lt->tm_mon = ATOI2(p);
+ if (lt->tm_mon > 12 || lt->tm_mon == 0)
+ badcanotime("Month out of range", op, p - op - 1);
+ --lt->tm_mon; /* time struct is 0 - 11 */
+ /* FALLTHROUGH */
+ case 6: /* dd */
+ lt->tm_mday = ATOI2(p);
+ switch (lt->tm_mon) {
+ case 0:
+ case 2:
+ case 4:
+ case 6:
+ case 7:
+ case 9:
+ case 11:
+ if (lt->tm_mday > 31 || lt->tm_mday == 0)
+ badcanotime("Day out of range (max 31)",
+ op, p - op - 1);
+ break;
+ case 3:
+ case 5:
+ case 8:
+ case 10:
+ if (lt->tm_mday > 30 || lt->tm_mday == 0)
+ badcanotime("Day out of range (max 30)",
+ op, p - op - 1);
+ break;
+ case 1:
+ if (isleap(lt->tm_year + TM_YEAR_BASE)) {
+ if (lt->tm_mday > 29 || lt->tm_mday == 0) {
+ badcanotime("Day out of range "
+ "(max 29)",
+ op, p - op - 1);
+ }
+ } else {
+ if (lt->tm_mday > 28 || lt->tm_mday == 0) {
+ badcanotime("Day out of range "
+ "(max 28)",
+ op, p - op - 1);
+ }
+ }
+ break;
+ default:
+ /*
+ * If the month was given, it's already been
+ * checked. If a bad value came back from
+ * localtime, something's badly broken.
+ * (make this an assertion?)
+ */
+ errx(EXIT_FAILURE, "localtime gave invalid month %d",
+ lt->tm_mon);
+ }
+ /* FALLTHROUGH */
+ case 4: /* hh */
+ lt->tm_hour = ATOI2(p);
+ if (lt->tm_hour > 23)
+ badcanotime("Hour out of range", op, p - op - 1);
+ /* FALLTHROUGH */
+ case 2: /* mm */
+ lt->tm_min = ATOI2(p);
+ if (lt->tm_min > 59)
+ badcanotime("Minute out of range", op, p - op - 1);
+ break;
+ case 0: /* was just .sss */
+ if (len != 0)
+ break;
+ /* FALLTHROUGH */
+ default:
+ if (strlen(p) - len > 12) {
+ badcanotime("Too many digits", p, 12);
+ } else {
+ badcanotime("Not enough digits", p, strlen(p) - len);
+ }
+ }
+
+ /* convert broken-down time to UTC clock time */
+ if ((new_time = mktime(lt)) == -1) {
+ /* Can this actually happen? */
+ err(EXIT_FAILURE, "%s: mktime", op);
+ }
+
+ /* if jflag is set, don't actually change the time, just return */
+ if (jflag) {
+ tval = new_time;
+ return;
+ }
+
+ /* set the time */
+ if (nflag || netsettime(new_time)) {
+ logwtmp("|", "date", "");
+ if (aflag) {
+ tv.tv_sec = new_time - tval;
+ tv.tv_usec = 0;
+ if (adjtime(&tv, NULL))
+ err(EXIT_FAILURE, "adjtime");
+ } else {
+ tval = new_time;
+ tv.tv_sec = tval;
+ tv.tv_usec = 0;
+ if (settimeofday(&tv, NULL))
+ err(EXIT_FAILURE, "settimeofday");
+ }
+ logwtmp("{", "date", "");
+ }
+
+ if ((p = getlogin()) == NULL)
+ p = "???";
+ syslog(LOG_AUTH | LOG_NOTICE, "date set by %s", p);
+}
+
+static void
+usage(void)
+{
+ (void)fprintf(stderr,
+ "Usage: %s [-ajnu] [-d date] [-r seconds] [+format]",
+ getprogname());
+ (void)fprintf(stderr, " [[[[[[CC]yy]mm]dd]HH]MM[.SS]]\n");
+ exit(EXIT_FAILURE);
+ /* NOTREACHED */
+}
diff --git a/bin/date/extern.h b/bin/date/extern.h
new file mode 100644
index 0000000..bbb786f
--- /dev/null
+++ b/bin/date/extern.h
@@ -0,0 +1,39 @@
+/* $NetBSD: extern.h,v 1.8 2006/11/17 22:11:28 christos Exp $ */
+
+/*-
+ * Copyright (c) 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * @(#)extern.h 8.1 (Berkeley) 5/31/93
+ */
+
+#ifndef _EXTERN_H_
+#define _EXTERN_H_
+
+int netsettime(time_t);
+
+#endif /* !_EXTERN_H_ */
diff --git a/bin/date/netdate.c b/bin/date/netdate.c
new file mode 100644
index 0000000..5b5857c
--- /dev/null
+++ b/bin/date/netdate.c
@@ -0,0 +1,200 @@
+/* $NetBSD: netdate.c,v 1.30 2011/01/29 02:16:52 christos Exp $ */
+
+/*-
+ * Copyright (c) 1990, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+#ifndef lint
+#if 0
+static char sccsid[] = "@(#)netdate.c 8.2 (Berkeley) 4/28/95";
+#else
+__RCSID("$NetBSD: netdate.c,v 1.30 2011/01/29 02:16:52 christos Exp $");
+#endif
+#endif /* not lint */
+
+#include <sys/param.h>
+#include <sys/time.h>
+#include <sys/socket.h>
+
+#include <netinet/in.h>
+#include <netdb.h>
+#define TSPTYPES
+#include <protocols/timed.h>
+
+#include <err.h>
+#include <errno.h>
+#include <poll.h>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "extern.h"
+
+#define WAITACK 2000 /* milliseconds */
+#define WAITDATEACK 5000 /* milliseconds */
+
+static const char *
+tsp_type_to_string(const struct tsp *msg)
+{
+ unsigned i;
+
+ i = msg->tsp_type;
+ return i < TSPTYPENUMBER ? tsptype[i] : "unknown";
+}
+
+/*
+ * Set the date in the machines controlled by timedaemons by communicating the
+ * new date to the local timedaemon. If the timedaemon is in the master state,
+ * it performs the correction on all slaves. If it is in the slave state, it
+ * notifies the master that a correction is needed.
+ * Returns 0 on success. Returns > 0 on failure.
+ */
+int
+netsettime(time_t tval)
+{
+ struct sockaddr_in dest;
+ struct tsp msg;
+ char hostname[MAXHOSTNAMELEN];
+ struct servent *sp;
+ struct pollfd ready;
+ int found, s, timed_ack, waittime;
+
+ if ((sp = getservbyname("timed", "udp")) == NULL) {
+ warnx("udp/timed: unknown service");
+ return 2;
+ }
+
+ (void)memset(&dest, 0, sizeof(dest));
+#ifdef BSD4_4
+ dest.sin_len = sizeof(dest);
+#endif
+ dest.sin_family = AF_INET;
+ dest.sin_port = sp->s_port;
+ dest.sin_addr.s_addr = htonl(INADDR_ANY);
+ s = socket(AF_INET, SOCK_DGRAM, 0);
+ if (s == -1) {
+ if (errno != EAFNOSUPPORT)
+ warn("timed");
+ return 2;
+ }
+
+#ifdef IP_PORTRANGE
+ {
+ static const int on = IP_PORTRANGE_LOW;
+
+ if (setsockopt(s, IPPROTO_IP, IP_PORTRANGE, &on,
+ sizeof(on)) == -1) {
+ warn("setsockopt");
+ goto bad;
+ }
+ }
+#endif
+
+ msg.tsp_type = TSP_SETDATE;
+ msg.tsp_vers = TSPVERSION;
+ if (gethostname(hostname, sizeof(hostname)) == -1) {
+ warn("gethostname");
+ goto bad;
+ }
+ (void)strlcpy(msg.tsp_name, hostname, sizeof(msg.tsp_name));
+ msg.tsp_seq = htons((in_port_t)0);
+ msg.tsp_time.tv_sec = htonl((in_addr_t)tval); /* XXX: y2038 */
+ msg.tsp_time.tv_usec = htonl((in_addr_t)0);
+ if (connect(s, (const void *)&dest, sizeof(dest)) == -1) {
+ warn("connect");
+ goto bad;
+ }
+ if (send(s, &msg, sizeof(msg), 0) == -1) {
+ if (errno != ECONNREFUSED)
+ warn("send");
+ goto bad;
+ }
+
+ timed_ack = -1;
+ waittime = WAITACK;
+ ready.fd = s;
+ ready.events = POLLIN;
+loop:
+ found = poll(&ready, 1, waittime);
+
+ {
+ socklen_t len;
+ int error;
+
+ len = sizeof(error);
+ if (getsockopt(s, SOL_SOCKET, SO_ERROR, &error, &len) == -1) {
+ warn("getsockopt");
+ goto bad;
+ }
+ if (error) {
+ if (error != ECONNREFUSED) {
+ errno = error;
+ warn("send (delayed error)");
+ }
+ goto bad;
+ }
+ }
+
+ if (found > 0 && ready.revents & POLLIN) {
+ ssize_t ret;
+
+ if ((ret = recv(s, &msg, sizeof(msg), 0)) == -1) {
+ if (errno != ECONNREFUSED)
+ warn("recv");
+ goto bad;
+ } else if ((size_t)ret < sizeof(msg)) {
+ warnx("recv: incomplete packet");
+ goto bad;
+ }
+
+ msg.tsp_seq = ntohs(msg.tsp_seq);
+ msg.tsp_time.tv_sec = ntohl(msg.tsp_time.tv_sec);
+ msg.tsp_time.tv_usec = ntohl(msg.tsp_time.tv_usec);
+ switch (msg.tsp_type) {
+ case TSP_ACK:
+ timed_ack = TSP_ACK;
+ waittime = WAITDATEACK;
+ goto loop;
+ case TSP_DATEACK:
+ (void)close(s);
+ return 0;
+ default:
+ warnx("wrong ack received from timed: %s",
+ tsp_type_to_string(&msg));
+ timed_ack = -1;
+ break;
+ }
+ }
+ if (timed_ack == -1)
+ warnx("can't reach time daemon, time set locally");
+
+bad:
+ (void)close(s);
+ return 2;
+}
diff --git a/bin/dd/args.c b/bin/dd/args.c
new file mode 100644
index 0000000..748f52c
--- /dev/null
+++ b/bin/dd/args.c
@@ -0,0 +1,504 @@
+/* $NetBSD: args.c,v 1.40 2019/01/30 01:40:02 mrg Exp $ */
+
+/*-
+ * Copyright (c) 1991, 1993, 1994
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Keith Muller of the University of California, San Diego and Lance
+ * Visser of Convex Computer Corporation.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+#ifndef lint
+#if 0
+static char sccsid[] = "@(#)args.c 8.3 (Berkeley) 4/2/94";
+#else
+__RCSID("$NetBSD: args.c,v 1.40 2019/01/30 01:40:02 mrg Exp $");
+#endif
+#endif /* not lint */
+
+#include <sys/types.h>
+#include <sys/time.h>
+
+#ifndef NO_IOFLAG
+#include <fcntl.h>
+#endif /* NO_IOFLAG */
+#include <err.h>
+#include <errno.h>
+#include <limits.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "dd.h"
+#include "extern.h"
+
+static int c_arg(const void *, const void *);
+
+#ifdef NO_MSGFMT
+static void f_msgfmt(char *) __dead;
+#else
+static void f_msgfmt(char *);
+#endif /* NO_MSGFMT */
+
+#ifdef NO_CONV
+static void f_conv(char *) __dead;
+#else
+static void f_conv(char *);
+static int c_conv(const void *, const void *);
+#endif /* NO_CONV */
+
+#ifdef NO_IOFLAG
+static void f_iflag(char *) __dead;
+static void f_oflag(char *) __dead;
+#else
+static void f_iflag(char *);
+static void f_oflag(char *);
+static u_int f_ioflag(char *, u_int);
+static int c_ioflag(const void *, const void *);
+#endif /* NO_IOFLAG */
+
+static void f_bs(char *);
+static void f_cbs(char *);
+static void f_count(char *);
+static void f_files(char *);
+static void f_ibs(char *);
+static void f_if(char *);
+static void f_obs(char *);
+static void f_of(char *);
+static void f_seek(char *);
+static void f_skip(char *);
+static void f_progress(char *);
+
+static const struct arg {
+ const char *name;
+ void (*f)(char *);
+ u_int set, noset;
+} args[] = {
+ /* the array needs to be sorted by the first column so
+ bsearch() can be used to find commands quickly */
+ { "bs", f_bs, C_BS, C_BS|C_IBS|C_OBS|C_OSYNC },
+ { "cbs", f_cbs, C_CBS, C_CBS },
+ { "conv", f_conv, 0, 0 },
+ { "count", f_count, C_COUNT, C_COUNT },
+ { "files", f_files, C_FILES, C_FILES },
+ { "ibs", f_ibs, C_IBS, C_BS|C_IBS },
+ { "if", f_if, C_IF, C_IF },
+ { "iflag", f_iflag, C_IFLAG, C_IFLAG },
+ { "iseek", f_skip, C_SKIP, C_SKIP },
+ { "msgfmt", f_msgfmt, 0, 0 },
+ { "obs", f_obs, C_OBS, C_BS|C_OBS },
+ { "of", f_of, C_OF, C_OF },
+ { "oflag", f_oflag, C_OFLAG, C_OFLAG },
+ { "oseek", f_seek, C_SEEK, C_SEEK },
+ { "progress", f_progress, 0, 0 },
+ { "seek", f_seek, C_SEEK, C_SEEK },
+ { "skip", f_skip, C_SKIP, C_SKIP },
+};
+
+/*
+ * args -- parse JCL syntax of dd.
+ */
+void
+jcl(char **argv)
+{
+ struct arg *ap, tmp;
+ char *oper, *arg;
+
+ in.dbsz = out.dbsz = 512;
+
+ while ((oper = *++argv) != NULL) {
+ if ((oper = strdup(oper)) == NULL) {
+ errx(EXIT_FAILURE,
+ "unable to allocate space for the argument %s",
+ *argv);
+ /* NOTREACHED */
+ }
+ if ((arg = strchr(oper, '=')) == NULL) {
+ errx(EXIT_FAILURE, "unknown operand %s", oper);
+ /* NOTREACHED */
+ }
+ *arg++ = '\0';
+ if (!*arg) {
+ errx(EXIT_FAILURE, "no value specified for %s", oper);
+ /* NOTREACHED */
+ }
+ tmp.name = oper;
+ if (!(ap = bsearch(&tmp, args,
+ __arraycount(args), sizeof(*args), c_arg))) {
+ errx(EXIT_FAILURE, "unknown operand %s", tmp.name);
+ /* NOTREACHED */
+ }
+ if (ddflags & ap->noset) {
+ errx(EXIT_FAILURE,
+ "%s: illegal argument combination or already set",
+ tmp.name);
+ /* NOTREACHED */
+ }
+ ddflags |= ap->set;
+ ap->f(arg);
+ }
+
+ /* Final sanity checks. */
+
+ if (ddflags & C_BS) {
+ /*
+ * Bs is turned off by any conversion -- we assume the user
+ * just wanted to set both the input and output block sizes
+ * and didn't want the bs semantics, so we don't warn.
+ */
+ if (ddflags & (C_BLOCK | C_LCASE | C_SWAB | C_UCASE |
+ C_UNBLOCK | C_OSYNC | C_ASCII | C_EBCDIC | C_SPARSE)) {
+ ddflags &= ~C_BS;
+ ddflags |= C_IBS|C_OBS;
+ }
+
+ /* Bs supersedes ibs and obs. */
+ if (ddflags & C_BS && ddflags & (C_IBS|C_OBS))
+ warnx("bs supersedes ibs and obs");
+ }
+
+ /*
+ * Ascii/ebcdic and cbs implies block/unblock.
+ * Block/unblock requires cbs and vice-versa.
+ */
+ if (ddflags & (C_BLOCK|C_UNBLOCK)) {
+ if (!(ddflags & C_CBS)) {
+ errx(EXIT_FAILURE, "record operations require cbs");
+ /* NOTREACHED */
+ }
+ cfunc = ddflags & C_BLOCK ? block : unblock;
+ } else if (ddflags & C_CBS) {
+ if (ddflags & (C_ASCII|C_EBCDIC)) {
+ if (ddflags & C_ASCII) {
+ ddflags |= C_UNBLOCK;
+ cfunc = unblock;
+ } else {
+ ddflags |= C_BLOCK;
+ cfunc = block;
+ }
+ } else {
+ errx(EXIT_FAILURE,
+ "cbs meaningless if not doing record operations");
+ /* NOTREACHED */
+ }
+ } else
+ cfunc = def;
+
+ /* Read, write and seek calls take off_t as arguments.
+ *
+ * The following check is not done because an off_t is a quad
+ * for current NetBSD implementations.
+ *
+ * if (in.offset > INT_MAX/in.dbsz || out.offset > INT_MAX/out.dbsz)
+ * errx(1, "seek offsets cannot be larger than %d", INT_MAX);
+ */
+}
+
+static int
+c_arg(const void *a, const void *b)
+{
+
+ return (strcmp(((const struct arg *)a)->name,
+ ((const struct arg *)b)->name));
+}
+
+static void
+f_bs(char *arg)
+{
+
+ in.dbsz = out.dbsz = strsuftoll("block size", arg, 1, UINT_MAX);
+}
+
+static void
+f_cbs(char *arg)
+{
+
+ cbsz = strsuftoll("conversion record size", arg, 1, UINT_MAX);
+}
+
+static void
+f_count(char *arg)
+{
+
+ cpy_cnt = strsuftoll("block count", arg, 0, LLONG_MAX);
+ if (!cpy_cnt)
+ terminate(0);
+}
+
+static void
+f_files(char *arg)
+{
+
+ files_cnt = (u_int)strsuftoll("file count", arg, 0, UINT_MAX);
+ if (!files_cnt)
+ terminate(0);
+}
+
+static void
+f_ibs(char *arg)
+{
+
+ if (!(ddflags & C_BS))
+ in.dbsz = strsuftoll("input block size", arg, 1, UINT_MAX);
+}
+
+static void
+f_if(char *arg)
+{
+
+ in.name = arg;
+}
+
+#ifdef NO_MSGFMT
+/* Build a small version (i.e. for a ramdisk root) */
+static void
+f_msgfmt(char *arg)
+{
+
+ errx(EXIT_FAILURE, "msgfmt option disabled");
+ /* NOTREACHED */
+}
+#else /* NO_MSGFMT */
+static void
+f_msgfmt(char *arg)
+{
+
+ /*
+ * If the format string is not valid, dd_write_msg() will print
+ * an error and exit.
+ */
+ dd_write_msg(arg, 0);
+
+ msgfmt = arg;
+}
+#endif /* NO_MSGFMT */
+
+static void
+f_obs(char *arg)
+{
+
+ if (!(ddflags & C_BS))
+ out.dbsz = strsuftoll("output block size", arg, 1, UINT_MAX);
+}
+
+static void
+f_of(char *arg)
+{
+
+ out.name = arg;
+}
+
+static void
+f_seek(char *arg)
+{
+
+ out.offset = strsuftoll("seek blocks", arg, 0, LLONG_MAX);
+}
+
+static void
+f_skip(char *arg)
+{
+
+ in.offset = strsuftoll("skip blocks", arg, 0, LLONG_MAX);
+}
+
+static void
+f_progress(char *arg)
+{
+
+ progress = strsuftoll("progress blocks", arg, 0, LLONG_MAX);
+}
+
+#ifdef NO_CONV
+/* Build a small version (i.e. for a ramdisk root) */
+static void
+f_conv(char *arg)
+{
+
+ errx(EXIT_FAILURE, "conv option disabled");
+ /* NOTREACHED */
+}
+#else /* NO_CONV */
+
+static const struct conv {
+ const char *name;
+ u_int set, noset;
+ const u_char *ctab;
+} clist[] = {
+ { "ascii", C_ASCII, C_EBCDIC, e2a_POSIX },
+ { "block", C_BLOCK, C_UNBLOCK, NULL },
+ { "ebcdic", C_EBCDIC, C_ASCII, a2e_POSIX },
+ { "ibm", C_EBCDIC, C_ASCII, a2ibm_POSIX },
+ { "lcase", C_LCASE, C_UCASE, NULL },
+ { "noerror", C_NOERROR, 0, NULL },
+ { "notrunc", C_NOTRUNC, 0, NULL },
+ { "oldascii", C_ASCII, C_EBCDIC, e2a_32V },
+ { "oldebcdic", C_EBCDIC, C_ASCII, a2e_32V },
+ { "oldibm", C_EBCDIC, C_ASCII, a2ibm_32V },
+ { "osync", C_OSYNC, C_BS, NULL },
+ { "sparse", C_SPARSE, 0, NULL },
+ { "swab", C_SWAB, 0, NULL },
+ { "sync", C_SYNC, 0, NULL },
+ { "ucase", C_UCASE, C_LCASE, NULL },
+ { "unblock", C_UNBLOCK, C_BLOCK, NULL },
+ /* If you add items to this table, be sure to add the
+ * conversions to the C_BS check in the jcl routine above.
+ */
+};
+
+static void
+f_conv(char *arg)
+{
+ struct conv *cp, tmp;
+
+ while (arg != NULL) {
+ tmp.name = strsep(&arg, ",");
+ if (!(cp = bsearch(&tmp, clist,
+ __arraycount(clist), sizeof(*clist), c_conv))) {
+ errx(EXIT_FAILURE, "unknown conversion %s", tmp.name);
+ /* NOTREACHED */
+ }
+ if (ddflags & cp->noset) {
+ errx(EXIT_FAILURE,
+ "%s: illegal conversion combination", tmp.name);
+ /* NOTREACHED */
+ }
+ ddflags |= cp->set;
+ if (cp->ctab)
+ ctab = cp->ctab;
+ }
+}
+
+static int
+c_conv(const void *a, const void *b)
+{
+
+ return (strcmp(((const struct conv *)a)->name,
+ ((const struct conv *)b)->name));
+}
+
+#endif /* NO_CONV */
+
+static void
+f_iflag(char *arg)
+{
+/* Build a small version (i.e. for a ramdisk root) */
+#ifdef NO_IOFLAG
+ errx(EXIT_FAILURE, "iflag option disabled");
+ /* NOTREACHED */
+#else
+ iflag = f_ioflag(arg, C_IFLAG);
+ return;
+#endif
+}
+
+static void
+f_oflag(char *arg)
+{
+/* Build a small version (i.e. for a ramdisk root) */
+#ifdef NO_IOFLAG
+ errx(EXIT_FAILURE, "oflag option disabled");
+ /* NOTREACHED */
+#else
+ oflag = f_ioflag(arg, C_OFLAG);
+ return;
+#endif
+}
+
+#ifndef NO_IOFLAG
+static const struct ioflag {
+ const char *name;
+ u_int set;
+ u_int allowed;
+} olist[] = {
+ /* the array needs to be sorted by the first column so
+ bsearch() can be used to find commands quickly */
+ { "alt_io", O_ALT_IO, C_IFLAG|C_OFLAG },
+ { "append", O_APPEND, C_OFLAG },
+ { "async", O_ASYNC, C_IFLAG|C_OFLAG },
+ { "cloexec", O_CLOEXEC, C_IFLAG|C_OFLAG },
+ { "creat", O_CREAT, C_OFLAG },
+ { "direct", O_DIRECT, C_IFLAG|C_OFLAG },
+ { "directory", O_DIRECTORY, C_NONE },
+ { "dsync", O_DSYNC, C_OFLAG },
+ { "excl", O_EXCL, C_OFLAG },
+ { "exlock", O_EXLOCK, C_IFLAG|C_OFLAG },
+ { "noctty", O_NOCTTY, C_IFLAG|C_OFLAG },
+ { "nofollow", O_NOFOLLOW, C_IFLAG|C_OFLAG },
+ { "nonblock", O_NONBLOCK, C_IFLAG|C_OFLAG },
+ { "nosigpipe", O_NOSIGPIPE, C_IFLAG|C_OFLAG },
+ { "rdonly", O_RDONLY, C_IFLAG },
+ { "rdwr", O_RDWR, C_IFLAG },
+ { "rsync", O_RSYNC, C_IFLAG },
+ { "shlock", O_SHLOCK, C_IFLAG|C_OFLAG },
+ { "sync", O_SYNC, C_IFLAG|C_OFLAG },
+ { "trunc", O_TRUNC, C_OFLAG },
+ { "wronly", O_WRONLY, C_OFLAG },
+};
+
+static u_int
+f_ioflag(char *arg, u_int flagtype)
+{
+ u_int ioflag = 0;
+ struct ioflag *cp, tmp;
+ const char *flagstr = (flagtype == C_IFLAG) ? "iflag" : "oflag";
+
+ while (arg != NULL) {
+ tmp.name = strsep(&arg, ",");
+ if (!(cp = bsearch(&tmp, olist,
+ __arraycount(olist), sizeof(*olist), c_ioflag))) {
+ errx(EXIT_FAILURE, "unknown %s %s", flagstr, tmp.name);
+ /* NOTREACHED */
+ }
+
+ if ((cp->set & O_ACCMODE) && (flagtype == C_OFLAG)) {
+ warnx("rdonly, rdwr and wronly are ignored for oflag");
+ continue;
+ }
+
+ if ((cp->allowed & flagtype) == 0) {
+ warnx("%s set for %s but makes no sense",
+ cp->name, flagstr);
+ }
+
+ ioflag |= cp->set;
+ }
+
+
+ return ioflag;
+}
+
+static int
+c_ioflag(const void *a, const void *b)
+{
+
+ return (strcmp(((const struct ioflag *)a)->name,
+ ((const struct ioflag *)b)->name));
+}
+#endif /* NO_IOFLAG */
diff --git a/bin/dd/conv.c b/bin/dd/conv.c
new file mode 100644
index 0000000..d4a8a09
--- /dev/null
+++ b/bin/dd/conv.c
@@ -0,0 +1,283 @@
+/* $NetBSD: conv.c,v 1.17 2003/08/07 09:05:10 agc Exp $ */
+
+/*-
+ * Copyright (c) 1991, 1993, 1994
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Keith Muller of the University of California, San Diego and Lance
+ * Visser of Convex Computer Corporation.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+#ifndef lint
+#if 0
+static char sccsid[] = "@(#)conv.c 8.3 (Berkeley) 4/2/94";
+#else
+__RCSID("$NetBSD: conv.c,v 1.17 2003/08/07 09:05:10 agc Exp $");
+#endif
+#endif /* not lint */
+
+#include <sys/param.h>
+#include <sys/time.h>
+
+#include <err.h>
+#include <string.h>
+#include <stdlib.h>
+
+#include "dd.h"
+#include "extern.h"
+
+/*
+ * def --
+ * Copy input to output. Input is buffered until reaches obs, and then
+ * output until less than obs remains. Only a single buffer is used.
+ * Worst case buffer calculation is (ibs + obs - 1).
+ */
+void
+def(void)
+{
+ uint64_t cnt;
+ u_char *inp;
+ const u_char *t;
+
+ if ((t = ctab) != NULL)
+ for (inp = in.dbp - (cnt = in.dbrcnt); cnt--; ++inp)
+ *inp = t[*inp];
+
+ /* Make the output buffer look right. */
+ out.dbp = in.dbp;
+ out.dbcnt = in.dbcnt;
+
+ if (in.dbcnt >= out.dbsz) {
+ /* If the output buffer is full, write it. */
+ dd_out(0);
+
+ /*
+ * Ddout copies the leftover output to the beginning of
+ * the buffer and resets the output buffer. Reset the
+ * input buffer to match it.
+ */
+ in.dbp = out.dbp;
+ in.dbcnt = out.dbcnt;
+ }
+}
+
+void
+def_close(void)
+{
+
+ /* Just update the count, everything is already in the buffer. */
+ if (in.dbcnt)
+ out.dbcnt = in.dbcnt;
+}
+
+#ifdef NO_CONV
+/* Build a smaller version (i.e. for a miniroot) */
+/* These can not be called, but just in case... */
+static const char no_block[] = "unblock and -DNO_CONV?";
+void block(void) { errx(EXIT_FAILURE, "%s", no_block + 2); }
+void block_close(void) { errx(EXIT_FAILURE, "%s", no_block + 2); }
+void unblock(void) { errx(EXIT_FAILURE, "%s", no_block); }
+void unblock_close(void) { errx(EXIT_FAILURE, "%s", no_block); }
+#else /* NO_CONV */
+
+/*
+ * Copy variable length newline terminated records with a max size cbsz
+ * bytes to output. Records less than cbs are padded with spaces.
+ *
+ * max in buffer: MAX(ibs, cbsz)
+ * max out buffer: obs + cbsz
+ */
+void
+block(void)
+{
+ static int intrunc;
+ int ch = 0; /* pacify gcc */
+ uint64_t cnt, maxlen;
+ u_char *inp, *outp;
+ const u_char *t;
+
+ /*
+ * Record truncation can cross block boundaries. If currently in a
+ * truncation state, keep tossing characters until reach a newline.
+ * Start at the beginning of the buffer, as the input buffer is always
+ * left empty.
+ */
+ if (intrunc) {
+ for (inp = in.db, cnt = in.dbrcnt;
+ cnt && *inp++ != '\n'; --cnt);
+ if (!cnt) {
+ in.dbcnt = 0;
+ in.dbp = in.db;
+ return;
+ }
+ intrunc = 0;
+ /* Adjust the input buffer numbers. */
+ in.dbcnt = cnt - 1;
+ in.dbp = inp + cnt - 1;
+ }
+
+ /*
+ * Copy records (max cbsz size chunks) into the output buffer. The
+ * translation is done as we copy into the output buffer.
+ */
+ for (inp = in.dbp - in.dbcnt, outp = out.dbp; in.dbcnt;) {
+ maxlen = MIN(cbsz, in.dbcnt);
+ if ((t = ctab) != NULL)
+ for (cnt = 0;
+ cnt < maxlen && (ch = *inp++) != '\n'; ++cnt)
+ *outp++ = t[ch];
+ else
+ for (cnt = 0;
+ cnt < maxlen && (ch = *inp++) != '\n'; ++cnt)
+ *outp++ = ch;
+ /*
+ * Check for short record without a newline. Reassemble the
+ * input block.
+ */
+ if (ch != '\n' && in.dbcnt < cbsz) {
+ (void)memmove(in.db, in.dbp - in.dbcnt, in.dbcnt);
+ break;
+ }
+
+ /* Adjust the input buffer numbers. */
+ in.dbcnt -= cnt;
+ if (ch == '\n')
+ --in.dbcnt;
+
+ /* Pad short records with spaces. */
+ if (cnt < cbsz)
+ (void)memset(outp, ctab ? ctab[' '] : ' ', cbsz - cnt);
+ else {
+ /*
+ * If the next character wouldn't have ended the
+ * block, it's a truncation.
+ */
+ if (!in.dbcnt || *inp != '\n')
+ ++st.trunc;
+
+ /* Toss characters to a newline. */
+ for (; in.dbcnt && *inp++ != '\n'; --in.dbcnt);
+ if (!in.dbcnt)
+ intrunc = 1;
+ else
+ --in.dbcnt;
+ }
+
+ /* Adjust output buffer numbers. */
+ out.dbp += cbsz;
+ if ((out.dbcnt += cbsz) >= out.dbsz)
+ dd_out(0);
+ outp = out.dbp;
+ }
+ in.dbp = in.db + in.dbcnt;
+}
+
+void
+block_close(void)
+{
+
+ /*
+ * Copy any remaining data into the output buffer and pad to a record.
+ * Don't worry about truncation or translation, the input buffer is
+ * always empty when truncating, and no characters have been added for
+ * translation. The bottom line is that anything left in the input
+ * buffer is a truncated record. Anything left in the output buffer
+ * just wasn't big enough.
+ */
+ if (in.dbcnt) {
+ ++st.trunc;
+ (void)memmove(out.dbp, in.dbp - in.dbcnt, in.dbcnt);
+ (void)memset(out.dbp + in.dbcnt,
+ ctab ? ctab[' '] : ' ', cbsz - in.dbcnt);
+ out.dbcnt += cbsz;
+ }
+}
+
+/*
+ * Convert fixed length (cbsz) records to variable length. Deletes any
+ * trailing blanks and appends a newline.
+ *
+ * max in buffer: MAX(ibs, cbsz) + cbsz
+ * max out buffer: obs + cbsz
+ */
+void
+unblock(void)
+{
+ uint64_t cnt;
+ u_char *inp;
+ const u_char *t;
+
+ /* Translation and case conversion. */
+ if ((t = ctab) != NULL)
+ for (cnt = in.dbrcnt, inp = in.dbp - 1; cnt--; inp--)
+ *inp = t[*inp];
+ /*
+ * Copy records (max cbsz size chunks) into the output buffer. The
+ * translation has to already be done or we might not recognize the
+ * spaces.
+ */
+ for (inp = in.db; in.dbcnt >= cbsz; inp += cbsz, in.dbcnt -= cbsz) {
+ for (t = inp + cbsz - 1; t >= inp && *t == ' '; --t);
+ if (t >= inp) {
+ cnt = t - inp + 1;
+ (void)memmove(out.dbp, inp, cnt);
+ out.dbp += cnt;
+ out.dbcnt += cnt;
+ }
+ ++out.dbcnt;
+ *out.dbp++ = '\n';
+ if (out.dbcnt >= out.dbsz)
+ dd_out(0);
+ }
+ if (in.dbcnt)
+ (void)memmove(in.db, in.dbp - in.dbcnt, in.dbcnt);
+ in.dbp = in.db + in.dbcnt;
+}
+
+void
+unblock_close(void)
+{
+ uint64_t cnt;
+ u_char *t;
+
+ if (in.dbcnt) {
+ warnx("%s: short input record", in.name);
+ for (t = in.db + in.dbcnt - 1; t >= in.db && *t == ' '; --t);
+ if (t >= in.db) {
+ cnt = t - in.db + 1;
+ (void)memmove(out.dbp, in.db, cnt);
+ out.dbp += cnt;
+ out.dbcnt += cnt;
+ }
+ ++out.dbcnt;
+ *out.dbp++ = '\n';
+ }
+}
+
+#endif /* NO_CONV */
diff --git a/bin/dd/conv_tab.c b/bin/dd/conv_tab.c
new file mode 100644
index 0000000..89d32da
--- /dev/null
+++ b/bin/dd/conv_tab.c
@@ -0,0 +1,287 @@
+/* $NetBSD: conv_tab.c,v 1.9 2003/08/07 09:05:10 agc Exp $ */
+
+/*-
+ * Copyright (c) 1991, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Keith Muller of the University of California, San Diego and Lance
+ * Visser of Convex Computer Corporation.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+#ifndef lint
+#if 0
+static char sccsid[] = "@(#)conv_tab.c 8.1 (Berkeley) 5/31/93";
+#else
+__RCSID("$NetBSD: conv_tab.c,v 1.9 2003/08/07 09:05:10 agc Exp $");
+#endif
+#endif /* not lint */
+
+#include <sys/types.h>
+
+/*
+ * There are currently six tables:
+ *
+ * ebcdic -> ascii 32V conv=oldascii
+ * ascii -> ebcdic 32V conv=oldebcdic
+ * ascii -> ibm ebcdic 32V conv=oldibm
+ *
+ * ebcdic -> ascii POSIX/S5 conv=ascii
+ * ascii -> ebcdic POSIX/S5 conv=ebcdic
+ * ascii -> ibm ebcdic POSIX/S5 conv=ibm
+ *
+ * Other tables are built from these if multiple conversions are being
+ * done.
+ *
+ * Tables used for conversions to/from IBM and EBCDIC to support an extension
+ * to POSIX P1003.2/D11. The tables referencing POSIX contain data extracted
+ * from tables 4-3 and 4-4 in P1003.2/Draft 11. The historic tables were
+ * constructed by running against a file with all possible byte values.
+ *
+ * More information can be obtained in "Correspondences of 8-Bit and Hollerith
+ * Codes for Computer Environments-A USASI Tutorial", Communications of the
+ * ACM, Volume 11, Number 11, November 1968, pp. 783-789.
+ */
+
+u_char casetab[256];
+
+/* EBCDIC to ASCII -- 32V compatible. */
+const u_char e2a_32V[] = {
+ 0000, 0001, 0002, 0003, 0234, 0011, 0206, 0177, /* 0000 */
+ 0227, 0215, 0216, 0013, 0014, 0015, 0016, 0017, /* 0010 */
+ 0020, 0021, 0022, 0023, 0235, 0205, 0010, 0207, /* 0020 */
+ 0030, 0031, 0222, 0217, 0034, 0035, 0036, 0037, /* 0030 */
+ 0200, 0201, 0202, 0203, 0204, 0012, 0027, 0033, /* 0040 */
+ 0210, 0211, 0212, 0213, 0214, 0005, 0006, 0007, /* 0050 */
+ 0220, 0221, 0026, 0223, 0224, 0225, 0226, 0004, /* 0060 */
+ 0230, 0231, 0232, 0233, 0024, 0025, 0236, 0032, /* 0070 */
+ 0040, 0240, 0241, 0242, 0243, 0244, 0245, 0246, /* 0100 */
+ 0247, 0250, 0133, 0056, 0074, 0050, 0053, 0041, /* 0110 */
+ 0046, 0251, 0252, 0253, 0254, 0255, 0256, 0257, /* 0120 */
+ 0260, 0261, 0135, 0044, 0052, 0051, 0073, 0136, /* 0130 */
+ 0055, 0057, 0262, 0263, 0264, 0265, 0266, 0267, /* 0140 */
+ 0270, 0271, 0174, 0054, 0045, 0137, 0076, 0077, /* 0150 */
+ 0272, 0273, 0274, 0275, 0276, 0277, 0300, 0301, /* 0160 */
+ 0302, 0140, 0072, 0043, 0100, 0047, 0075, 0042, /* 0170 */
+ 0303, 0141, 0142, 0143, 0144, 0145, 0146, 0147, /* 0200 */
+ 0150, 0151, 0304, 0305, 0306, 0307, 0310, 0311, /* 0210 */
+ 0312, 0152, 0153, 0154, 0155, 0156, 0157, 0160, /* 0220 */
+ 0161, 0162, 0313, 0314, 0315, 0316, 0317, 0320, /* 0230 */
+ 0321, 0176, 0163, 0164, 0165, 0166, 0167, 0170, /* 0240 */
+ 0171, 0172, 0322, 0323, 0324, 0325, 0326, 0327, /* 0250 */
+ 0330, 0331, 0332, 0333, 0334, 0335, 0336, 0337, /* 0260 */
+ 0340, 0341, 0342, 0343, 0344, 0345, 0346, 0347, /* 0270 */
+ 0173, 0101, 0102, 0103, 0104, 0105, 0106, 0107, /* 0300 */
+ 0110, 0111, 0350, 0351, 0352, 0353, 0354, 0355, /* 0310 */
+ 0175, 0112, 0113, 0114, 0115, 0116, 0117, 0120, /* 0320 */
+ 0121, 0122, 0356, 0357, 0360, 0361, 0362, 0363, /* 0330 */
+ 0134, 0237, 0123, 0124, 0125, 0126, 0127, 0130, /* 0340 */
+ 0131, 0132, 0364, 0365, 0366, 0367, 0370, 0371, /* 0350 */
+ 0060, 0061, 0062, 0063, 0064, 0065, 0066, 0067, /* 0360 */
+ 0070, 0071, 0372, 0373, 0374, 0375, 0376, 0377, /* 0370 */
+};
+
+/* ASCII to EBCDIC -- 32V compatible. */
+const u_char a2e_32V[] = {
+ 0000, 0001, 0002, 0003, 0067, 0055, 0056, 0057, /* 0000 */
+ 0026, 0005, 0045, 0013, 0014, 0015, 0016, 0017, /* 0010 */
+ 0020, 0021, 0022, 0023, 0074, 0075, 0062, 0046, /* 0020 */
+ 0030, 0031, 0077, 0047, 0034, 0035, 0036, 0037, /* 0030 */
+ 0100, 0117, 0177, 0173, 0133, 0154, 0120, 0175, /* 0040 */
+ 0115, 0135, 0134, 0116, 0153, 0140, 0113, 0141, /* 0050 */
+ 0360, 0361, 0362, 0363, 0364, 0365, 0366, 0367, /* 0060 */
+ 0370, 0371, 0172, 0136, 0114, 0176, 0156, 0157, /* 0070 */
+ 0174, 0301, 0302, 0303, 0304, 0305, 0306, 0307, /* 0100 */
+ 0310, 0311, 0321, 0322, 0323, 0324, 0325, 0326, /* 0110 */
+ 0327, 0330, 0331, 0342, 0343, 0344, 0345, 0346, /* 0120 */
+ 0347, 0350, 0351, 0112, 0340, 0132, 0137, 0155, /* 0130 */
+ 0171, 0201, 0202, 0203, 0204, 0205, 0206, 0207, /* 0140 */
+ 0210, 0211, 0221, 0222, 0223, 0224, 0225, 0226, /* 0150 */
+ 0227, 0230, 0231, 0242, 0243, 0244, 0245, 0246, /* 0160 */
+ 0247, 0250, 0251, 0300, 0152, 0320, 0241, 0007, /* 0170 */
+ 0040, 0041, 0042, 0043, 0044, 0025, 0006, 0027, /* 0200 */
+ 0050, 0051, 0052, 0053, 0054, 0011, 0012, 0033, /* 0210 */
+ 0060, 0061, 0032, 0063, 0064, 0065, 0066, 0010, /* 0220 */
+ 0070, 0071, 0072, 0073, 0004, 0024, 0076, 0341, /* 0230 */
+ 0101, 0102, 0103, 0104, 0105, 0106, 0107, 0110, /* 0240 */
+ 0111, 0121, 0122, 0123, 0124, 0125, 0126, 0127, /* 0250 */
+ 0130, 0131, 0142, 0143, 0144, 0145, 0146, 0147, /* 0260 */
+ 0150, 0151, 0160, 0161, 0162, 0163, 0164, 0165, /* 0270 */
+ 0166, 0167, 0170, 0200, 0212, 0213, 0214, 0215, /* 0300 */
+ 0216, 0217, 0220, 0232, 0233, 0234, 0235, 0236, /* 0310 */
+ 0237, 0240, 0252, 0253, 0254, 0255, 0256, 0257, /* 0320 */
+ 0260, 0261, 0262, 0263, 0264, 0265, 0266, 0267, /* 0330 */
+ 0270, 0271, 0272, 0273, 0274, 0275, 0276, 0277, /* 0340 */
+ 0312, 0313, 0314, 0315, 0316, 0317, 0332, 0333, /* 0350 */
+ 0334, 0335, 0336, 0337, 0352, 0353, 0354, 0355, /* 0360 */
+ 0356, 0357, 0372, 0373, 0374, 0375, 0376, 0377, /* 0370 */
+};
+
+/* ASCII to IBM EBCDIC -- 32V compatible. */
+const u_char a2ibm_32V[] = {
+ 0000, 0001, 0002, 0003, 0067, 0055, 0056, 0057, /* 0000 */
+ 0026, 0005, 0045, 0013, 0014, 0015, 0016, 0017, /* 0010 */
+ 0020, 0021, 0022, 0023, 0074, 0075, 0062, 0046, /* 0020 */
+ 0030, 0031, 0077, 0047, 0034, 0035, 0036, 0037, /* 0030 */
+ 0100, 0132, 0177, 0173, 0133, 0154, 0120, 0175, /* 0040 */
+ 0115, 0135, 0134, 0116, 0153, 0140, 0113, 0141, /* 0050 */
+ 0360, 0361, 0362, 0363, 0364, 0365, 0366, 0367, /* 0060 */
+ 0370, 0371, 0172, 0136, 0114, 0176, 0156, 0157, /* 0070 */
+ 0174, 0301, 0302, 0303, 0304, 0305, 0306, 0307, /* 0100 */
+ 0310, 0311, 0321, 0322, 0323, 0324, 0325, 0326, /* 0110 */
+ 0327, 0330, 0331, 0342, 0343, 0344, 0345, 0346, /* 0120 */
+ 0347, 0350, 0351, 0255, 0340, 0275, 0137, 0155, /* 0130 */
+ 0171, 0201, 0202, 0203, 0204, 0205, 0206, 0207, /* 0140 */
+ 0210, 0211, 0221, 0222, 0223, 0224, 0225, 0226, /* 0150 */
+ 0227, 0230, 0231, 0242, 0243, 0244, 0245, 0246, /* 0160 */
+ 0247, 0250, 0251, 0300, 0117, 0320, 0241, 0007, /* 0170 */
+ 0040, 0041, 0042, 0043, 0044, 0025, 0006, 0027, /* 0200 */
+ 0050, 0051, 0052, 0053, 0054, 0011, 0012, 0033, /* 0210 */
+ 0060, 0061, 0032, 0063, 0064, 0065, 0066, 0010, /* 0220 */
+ 0070, 0071, 0072, 0073, 0004, 0024, 0076, 0341, /* 0230 */
+ 0101, 0102, 0103, 0104, 0105, 0106, 0107, 0110, /* 0240 */
+ 0111, 0121, 0122, 0123, 0124, 0125, 0126, 0127, /* 0250 */
+ 0130, 0131, 0142, 0143, 0144, 0145, 0146, 0147, /* 0260 */
+ 0150, 0151, 0160, 0161, 0162, 0163, 0164, 0165, /* 0270 */
+ 0166, 0167, 0170, 0200, 0212, 0213, 0214, 0215, /* 0300 */
+ 0216, 0217, 0220, 0232, 0233, 0234, 0235, 0236, /* 0310 */
+ 0237, 0240, 0252, 0253, 0254, 0255, 0256, 0257, /* 0320 */
+ 0260, 0261, 0262, 0263, 0264, 0265, 0266, 0267, /* 0330 */
+ 0270, 0271, 0272, 0273, 0274, 0275, 0276, 0277, /* 0340 */
+ 0312, 0313, 0314, 0315, 0316, 0317, 0332, 0333, /* 0350 */
+ 0334, 0335, 0336, 0337, 0352, 0353, 0354, 0355, /* 0360 */
+ 0356, 0357, 0372, 0373, 0374, 0375, 0376, 0377, /* 0370 */
+};
+
+/* EBCDIC to ASCII -- POSIX and System V compatible. */
+const u_char e2a_POSIX[] = {
+ 0000, 0001, 0002, 0003, 0234, 0011, 0206, 0177, /* 0000 */
+ 0227, 0215, 0216, 0013, 0014, 0015, 0016, 0017, /* 0010 */
+ 0020, 0021, 0022, 0023, 0235, 0205, 0010, 0207, /* 0020 */
+ 0030, 0031, 0222, 0217, 0034, 0035, 0036, 0037, /* 0030 */
+ 0200, 0201, 0202, 0203, 0204, 0012, 0027, 0033, /* 0040 */
+ 0210, 0211, 0212, 0213, 0214, 0005, 0006, 0007, /* 0050 */
+ 0220, 0221, 0026, 0223, 0224, 0225, 0226, 0004, /* 0060 */
+ 0230, 0231, 0232, 0233, 0024, 0025, 0236, 0032, /* 0070 */
+ 0040, 0240, 0241, 0242, 0243, 0244, 0245, 0246, /* 0100 */
+ 0247, 0250, 0325, 0056, 0074, 0050, 0053, 0174, /* 0110 */
+ 0046, 0251, 0252, 0253, 0254, 0255, 0256, 0257, /* 0120 */
+ 0260, 0261, 0041, 0044, 0052, 0051, 0073, 0176, /* 0130 */
+ 0055, 0057, 0262, 0263, 0264, 0265, 0266, 0267, /* 0140 */
+ 0270, 0271, 0313, 0054, 0045, 0137, 0076, 0077, /* 0150 */
+ 0272, 0273, 0274, 0275, 0276, 0277, 0300, 0301, /* 0160 */
+ 0302, 0140, 0072, 0043, 0100, 0047, 0075, 0042, /* 0170 */
+ 0303, 0141, 0142, 0143, 0144, 0145, 0146, 0147, /* 0200 */
+ 0150, 0151, 0304, 0305, 0306, 0307, 0310, 0311, /* 0210 */
+ 0312, 0152, 0153, 0154, 0155, 0156, 0157, 0160, /* 0220 */
+ 0161, 0162, 0136, 0314, 0315, 0316, 0317, 0320, /* 0230 */
+ 0321, 0345, 0163, 0164, 0165, 0166, 0167, 0170, /* 0240 */
+ 0171, 0172, 0322, 0323, 0324, 0133, 0326, 0327, /* 0250 */
+ 0330, 0331, 0332, 0333, 0334, 0335, 0336, 0337, /* 0260 */
+ 0340, 0341, 0342, 0343, 0344, 0135, 0346, 0347, /* 0270 */
+ 0173, 0101, 0102, 0103, 0104, 0105, 0106, 0107, /* 0300 */
+ 0110, 0111, 0350, 0351, 0352, 0353, 0354, 0355, /* 0310 */
+ 0175, 0112, 0113, 0114, 0115, 0116, 0117, 0120, /* 0320 */
+ 0121, 0122, 0356, 0357, 0360, 0361, 0362, 0363, /* 0330 */
+ 0134, 0237, 0123, 0124, 0125, 0126, 0127, 0130, /* 0340 */
+ 0131, 0132, 0364, 0365, 0366, 0367, 0370, 0371, /* 0350 */
+ 0060, 0061, 0062, 0063, 0064, 0065, 0066, 0067, /* 0360 */
+ 0070, 0071, 0372, 0373, 0374, 0375, 0376, 0377, /* 0370 */
+};
+
+/* ASCII to EBCDIC -- POSIX and System V compatible. */
+const u_char a2e_POSIX[] = {
+ 0000, 0001, 0002, 0003, 0067, 0055, 0056, 0057, /* 0000 */
+ 0026, 0005, 0045, 0013, 0014, 0015, 0016, 0017, /* 0010 */
+ 0020, 0021, 0022, 0023, 0074, 0075, 0062, 0046, /* 0020 */
+ 0030, 0031, 0077, 0047, 0034, 0035, 0036, 0037, /* 0030 */
+ 0100, 0132, 0177, 0173, 0133, 0154, 0120, 0175, /* 0040 */
+ 0115, 0135, 0134, 0116, 0153, 0140, 0113, 0141, /* 0050 */
+ 0360, 0361, 0362, 0363, 0364, 0365, 0366, 0367, /* 0060 */
+ 0370, 0371, 0172, 0136, 0114, 0176, 0156, 0157, /* 0070 */
+ 0174, 0301, 0302, 0303, 0304, 0305, 0306, 0307, /* 0100 */
+ 0310, 0311, 0321, 0322, 0323, 0324, 0325, 0326, /* 0110 */
+ 0327, 0330, 0331, 0342, 0343, 0344, 0345, 0346, /* 0120 */
+ 0347, 0350, 0351, 0255, 0340, 0275, 0232, 0155, /* 0130 */
+ 0171, 0201, 0202, 0203, 0204, 0205, 0206, 0207, /* 0140 */
+ 0210, 0211, 0221, 0222, 0223, 0224, 0225, 0226, /* 0150 */
+ 0227, 0230, 0231, 0242, 0243, 0244, 0245, 0246, /* 0160 */
+ 0247, 0250, 0251, 0300, 0117, 0320, 0137, 0007, /* 0170 */
+ 0040, 0041, 0042, 0043, 0044, 0025, 0006, 0027, /* 0200 */
+ 0050, 0051, 0052, 0053, 0054, 0011, 0012, 0033, /* 0210 */
+ 0060, 0061, 0032, 0063, 0064, 0065, 0066, 0010, /* 0220 */
+ 0070, 0071, 0072, 0073, 0004, 0024, 0076, 0341, /* 0230 */
+ 0101, 0102, 0103, 0104, 0105, 0106, 0107, 0110, /* 0240 */
+ 0111, 0121, 0122, 0123, 0124, 0125, 0126, 0127, /* 0250 */
+ 0130, 0131, 0142, 0143, 0144, 0145, 0146, 0147, /* 0260 */
+ 0150, 0151, 0160, 0161, 0162, 0163, 0164, 0165, /* 0270 */
+ 0166, 0167, 0170, 0200, 0212, 0213, 0214, 0215, /* 0300 */
+ 0216, 0217, 0220, 0152, 0233, 0234, 0235, 0236, /* 0310 */
+ 0237, 0240, 0252, 0253, 0254, 0112, 0256, 0257, /* 0320 */
+ 0260, 0261, 0262, 0263, 0264, 0265, 0266, 0267, /* 0330 */
+ 0270, 0271, 0272, 0273, 0274, 0241, 0276, 0277, /* 0340 */
+ 0312, 0313, 0314, 0315, 0316, 0317, 0332, 0333, /* 0350 */
+ 0334, 0335, 0336, 0337, 0352, 0353, 0354, 0355, /* 0360 */
+ 0356, 0357, 0372, 0373, 0374, 0375, 0376, 0377, /* 0370 */
+};
+
+/* ASCII to IBM EBCDIC -- POSIX and System V compatible. */
+const u_char a2ibm_POSIX[] = {
+ 0000, 0001, 0002, 0003, 0067, 0055, 0056, 0057, /* 0000 */
+ 0026, 0005, 0045, 0013, 0014, 0015, 0016, 0017, /* 0010 */
+ 0020, 0021, 0022, 0023, 0074, 0075, 0062, 0046, /* 0020 */
+ 0030, 0031, 0077, 0047, 0034, 0035, 0036, 0037, /* 0030 */
+ 0100, 0132, 0177, 0173, 0133, 0154, 0120, 0175, /* 0040 */
+ 0115, 0135, 0134, 0116, 0153, 0140, 0113, 0141, /* 0050 */
+ 0360, 0361, 0362, 0363, 0364, 0365, 0366, 0367, /* 0060 */
+ 0370, 0371, 0172, 0136, 0114, 0176, 0156, 0157, /* 0070 */
+ 0174, 0301, 0302, 0303, 0304, 0305, 0306, 0307, /* 0100 */
+ 0310, 0311, 0321, 0322, 0323, 0324, 0325, 0326, /* 0110 */
+ 0327, 0330, 0331, 0342, 0343, 0344, 0345, 0346, /* 0120 */
+ 0347, 0350, 0351, 0255, 0340, 0275, 0137, 0155, /* 0130 */
+ 0171, 0201, 0202, 0203, 0204, 0205, 0206, 0207, /* 0140 */
+ 0210, 0211, 0221, 0222, 0223, 0224, 0225, 0226, /* 0150 */
+ 0227, 0230, 0231, 0242, 0243, 0244, 0245, 0246, /* 0160 */
+ 0247, 0250, 0251, 0300, 0117, 0320, 0241, 0007, /* 0170 */
+ 0040, 0041, 0042, 0043, 0044, 0025, 0006, 0027, /* 0200 */
+ 0050, 0051, 0052, 0053, 0054, 0011, 0012, 0033, /* 0210 */
+ 0060, 0061, 0032, 0063, 0064, 0065, 0066, 0010, /* 0220 */
+ 0070, 0071, 0072, 0073, 0004, 0024, 0076, 0341, /* 0230 */
+ 0101, 0102, 0103, 0104, 0105, 0106, 0107, 0110, /* 0240 */
+ 0111, 0121, 0122, 0123, 0124, 0125, 0126, 0127, /* 0250 */
+ 0130, 0131, 0142, 0143, 0144, 0145, 0146, 0147, /* 0260 */
+ 0150, 0151, 0160, 0161, 0162, 0163, 0164, 0165, /* 0270 */
+ 0166, 0167, 0170, 0200, 0212, 0213, 0214, 0215, /* 0300 */
+ 0216, 0217, 0220, 0232, 0233, 0234, 0235, 0236, /* 0310 */
+ 0237, 0240, 0252, 0253, 0254, 0255, 0256, 0257, /* 0320 */
+ 0260, 0261, 0262, 0263, 0264, 0265, 0266, 0267, /* 0330 */
+ 0270, 0271, 0272, 0273, 0274, 0275, 0276, 0277, /* 0340 */
+ 0312, 0313, 0314, 0315, 0316, 0317, 0332, 0333, /* 0350 */
+ 0334, 0335, 0336, 0337, 0352, 0353, 0354, 0355, /* 0360 */
+ 0356, 0357, 0372, 0373, 0374, 0375, 0376, 0377, /* 0370 */
+};
diff --git a/bin/dd/dd.1 b/bin/dd/dd.1
new file mode 100644
index 0000000..44ea896
--- /dev/null
+++ b/bin/dd/dd.1
@@ -0,0 +1,588 @@
+.\" $NetBSD: dd.1,v 1.36 2019/01/30 10:28:50 wiz Exp $
+.\"
+.\" Copyright (c) 1990, 1993
+.\" The Regents of the University of California. All rights reserved.
+.\"
+.\" This code is derived from software contributed to Berkeley by
+.\" Keith Muller of the University of California, San Diego.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\" notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must reproduce the above copyright
+.\" notice, this list of conditions and the following disclaimer in the
+.\" documentation and/or other materials provided with the distribution.
+.\" 3. Neither the name of the University nor the names of its contributors
+.\" may be used to endorse or promote products derived from this software
+.\" without specific prior written permission.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+.\" ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+.\" SUCH DAMAGE.
+.\"
+.\" @(#)dd.1 8.2 (Berkeley) 1/13/94
+.\"
+.Dd January 29, 2019
+.Dt DD 1
+.Os
+.Sh NAME
+.Nm dd
+.Nd convert and copy a file
+.Sh SYNOPSIS
+.Nm
+.Op operand ...
+.Sh DESCRIPTION
+The
+.Nm
+utility copies the standard input to the standard output.
+Input data is read and written in 512-byte blocks.
+If input reads are short, input from multiple reads are aggregated
+to form the output block.
+When finished,
+.Nm
+displays the number of complete and partial input and output blocks
+and truncated input records to the standard error output.
+.Pp
+The following operands are available:
+.Bl -tag -width of=file
+.It Cm bs= Ns Ar n
+Set both input and output block size, superseding the
+.Cm ibs
+and
+.Cm obs
+operands.
+If no conversion values other than
+.Cm noerror ,
+.Cm notrunc
+or
+.Cm sync
+are specified, then each input block is copied to the output as a
+single block without any aggregation of short blocks.
+.It Cm cbs= Ns Ar n
+Set the conversion record size to
+.Va n
+bytes.
+The conversion record size is required by the record oriented conversion
+values.
+.It Cm count= Ns Ar n
+Copy only
+.Va n
+input blocks.
+.It Cm files= Ns Ar n
+Copy
+.Va n
+input files before terminating.
+This operand is only applicable when the input device is a tape.
+.It Cm ibs= Ns Ar n
+Set the input block size to
+.Va n
+bytes instead of the default 512.
+.It Cm if= Ns Ar file
+Read input from
+.Ar file
+instead of the standard input.
+.It Cm iflag= Ns Ar flags
+Use comma-separated
+.Ar flags
+when calling
+.Xr open 2
+for the input file.
+See the
+.Sx INPUT AND OUTPUT FLAGS
+section for details.
+Default value is
+.Va rdonly .
+.It Cm iseek= Ns Ar n
+Seek on the input file
+.Ar n
+blocks.
+This is synonymous with
+.Cm skip= Ns Ar n .
+.It Cm msgfmt= Ns Ar fmt
+Specify the message format
+.Ar fmt
+to be used when writing information to standard output.
+Possible values are:
+.Bl -tag -width xxxxx -offset indent -compact
+.It quiet
+turns off information summary report except for errors and
+.Cm progress .
+.It posix
+default information summary report as specified by POSIX.
+.It human
+default information summary report extended with human-readable
+values.
+.El
+.Pp
+When
+.Ar fmt
+does not correspond to any value given above,
+it contains a string that will be used as format specifier
+for the information summary output.
+Each conversion specification is introduced by the character
+.Cm % .
+The following ones are available:
+.Bl -tag -width xx -offset indent -compact
+.It b
+total number of bytes transferred
+.It B
+total number of bytes transferred in
+.Xr humanize_number 3
+format
+.It e
+speed transfer
+.It E
+speed transfer in
+.Xr humanize_number 3
+format
+.It i
+number of partial input block(s)
+.It I
+number of full input block(s)
+.It o
+number of partial output block(s)
+.It O
+number of full output block(s)
+.It s
+time elapsed since the beginning in
+.Do seconds.ms Dc
+format
+.It p
+number of sparse output blocks
+.It t
+number of truncated blocks
+.It w
+number of odd-length swab blocks
+.It P
+singular/plural of
+.Do block Dc
+depending on number of sparse blocks
+.It T
+singular/plural of
+.Do block Dc
+depending on number of truncated blocks
+.It W
+singular/plural of
+.Do block Dc
+depending on number of swab blocks
+.El
+.It Cm obs= Ns Ar n
+Set the output block size to
+.Va n
+bytes instead of the default 512.
+.It Cm of= Ns Ar file
+Write output to
+.Ar file
+instead of the standard output.
+Any regular output file is truncated unless the
+.Cm notrunc
+conversion value is specified.
+If an initial portion of the output file is skipped (see the
+.Cm seek
+operand)
+the output file is truncated at that point.
+.It Cm oflag= Ns Ar flags
+Same as
+.Cm iflag
+but for the call to
+.Xr open 2
+on the output file.
+The default value is
+.Va creat ,
+which must be explicitly added in
+.Cm oflag
+if this option is used in order to output to a nonexistent file.
+The default or specified value is or'ed with
+.Va rdwr
+for a first
+.Xr open 2
+attempt, then on failure with
+.Va wronly
+on a second attempt.
+In both cases,
+.Va trunc
+is automatically added if none of
+.Cm oseek ,
+.Cm seek ,
+or
+.Cm conv=notrunc
+operands are used.
+See the
+.Sx INPUT AND OUTPUT FLAGS
+section for details.
+.It Cm oseek= Ns Ar n
+Seek on the output file
+.Ar n
+blocks.
+This is synonymous with
+.Cm seek= Ns Ar n .
+.It Cm seek= Ns Ar n
+Seek
+.Va n
+blocks from the beginning of the output before copying.
+On non-tape devices, an
+.Xr lseek 2
+operation is used.
+Otherwise, existing blocks are read and the data discarded.
+If the user does not have read permission for the tape, it is positioned
+using the tape
+.Xr ioctl 2
+function calls.
+If the seek operation is past the end of file, space from the current
+end of file to the specified offset is filled with blocks of
+.Tn NUL
+bytes.
+.It Cm skip= Ns Ar n
+Skip
+.Va n
+blocks from the beginning of the input before copying.
+On input which supports seeks, an
+.Xr lseek 2
+operation is used.
+Otherwise, input data is read and discarded.
+For pipes, the correct number of bytes is read.
+For all other devices, the correct number of blocks is read without
+distinguishing between a partial or complete block being read.
+.It Cm progress= Ns Ar n
+Switch on display of progress if
+.Va n
+is set to any non-zero value.
+This will cause a
+.Dq \&.
+to be printed (to the standard error output) for every
+.Va n
+full or partial blocks written to the output file.
+.Sm off
+.It Cm conv= Cm value Op \&, Cm value \&...
+.Sm on
+Where
+.Cm value
+is one of the symbols from the following list.
+.Bl -tag -width unblock
+.It Cm ascii , oldascii
+The same as the
+.Cm unblock
+value except that characters are translated from
+.Tn EBCDIC
+to
+.Tn ASCII
+before the
+records are converted.
+(These values imply
+.Cm unblock
+if the operand
+.Cm cbs
+is also specified.)
+There are two conversion maps for
+.Tn ASCII .
+The value
+.Cm ascii
+specifies the recommended one which is compatible with
+.At V .
+The value
+.Cm oldascii
+specifies the one used in historic
+.Tn AT&T
+and pre-
+.Bx 4.3 Reno
+systems.
+.It Cm block
+Treats the input as a sequence of newline or end-of-file terminated variable
+length records independent of input and output block boundaries.
+Any trailing newline character is discarded.
+Each input record is converted to a fixed length output record where the
+length is specified by the
+.Cm cbs
+operand.
+Input records shorter than the conversion record size are padded with spaces.
+Input records longer than the conversion record size are truncated.
+The number of truncated input records, if any, are reported to the standard
+error output at the completion of the copy.
+.It Cm ebcdic , ibm , oldebcdic , oldibm
+The same as the
+.Cm block
+value except that characters are translated from
+.Tn ASCII
+to
+.Tn EBCDIC
+after the
+records are converted.
+(These values imply
+.Cm block
+if the operand
+.Cm cbs
+is also specified.)
+There are four conversion maps for
+.Tn EBCDIC .
+The value
+.Cm ebcdic
+specifies the recommended one which is compatible with
+.At V .
+The value
+.Cm ibm
+is a slightly different mapping, which is compatible with the
+.At V
+.Cm ibm
+value.
+The values
+.Cm oldebcdic
+and
+.Cm oldibm
+are maps used in historic
+.Tn AT&T
+and pre
+.Bx 4.3 Reno
+systems.
+.It Cm lcase
+Transform uppercase characters into lowercase characters.
+.It Cm noerror
+Do not stop processing on an input error.
+When an input error occurs, a diagnostic message followed by the current
+input and output block counts will be written to the standard error output
+in the same format as the standard completion message.
+If the
+.Cm sync
+conversion is also specified, any missing input data will be replaced
+with
+.Tn NUL
+bytes (or with spaces if a block oriented conversion value was
+specified) and processed as a normal input buffer.
+If the
+.Cm sync
+conversion is not specified, the input block is omitted from the output.
+On input files which are not tapes or pipes, the file offset
+will be positioned past the block in which the error occurred using
+.Xr lseek 2 .
+.It Cm notrunc
+Do not truncate the output file.
+This will preserve any blocks in the output file not explicitly written
+by
+.Nm .
+The
+.Cm notrunc
+value is not supported for tapes.
+.It Cm osync
+Pad the final output block to the full output block size.
+If the input file is not a multiple of the output block size
+after conversion, this conversion forces the final output block
+to be the same size as preceding blocks for use on devices that require
+regularly sized blocks to be written.
+This option is incompatible with use of the
+.Cm bs= Ns Ar n
+block size specification.
+.It Cm sparse
+If one or more non-final output blocks would consist solely of
+.Dv NUL
+bytes, try to seek the output file by the required space instead of
+filling them with
+.Dv NUL Ns s .
+This results in a sparse file on some file systems.
+.It Cm swab
+Swap every pair of input bytes.
+If an input buffer has an odd number of bytes, the last byte will be
+ignored during swapping.
+.It Cm sync
+Pad every input block to the input buffer size.
+Spaces are used for pad bytes if a block oriented conversion value is
+specified, otherwise
+.Tn NUL
+bytes are used.
+.It Cm ucase
+Transform lowercase characters into uppercase characters.
+.It Cm unblock
+Treats the input as a sequence of fixed length records independent of input
+and output block boundaries.
+The length of the input records is specified by the
+.Cm cbs
+operand.
+Any trailing space characters are discarded and a newline character is
+appended.
+.El
+.El
+.Pp
+Where sizes are specified, a decimal number of bytes is expected.
+Two or more numbers may be separated by an
+.Dq x
+to indicate a product.
+Each number may have one of the following optional suffixes:
+.Bl -tag -width 3n -offset indent -compact
+.It b
+Block; multiply by 512
+.It k
+Kibi; multiply by 1024 (1 KiB)
+.It m
+Mebi; multiply by 1048576 (1 MiB)
+.It g
+Gibi; multiply by 1073741824 (1 GiB)
+.It t
+Tebi; multiply by 1099511627776 (1 TiB)
+.It w
+Word; multiply by the number of bytes in an integer
+.El
+.Pp
+When finished,
+.Nm
+displays the number of complete and partial input and output blocks,
+truncated input records and odd-length byte-swapping blocks to the
+standard error output.
+A partial input block is one where less than the input block size
+was read.
+A partial output block is one where less than the output block size
+was written.
+Partial output blocks to tape devices are considered fatal errors.
+Otherwise, the rest of the block will be written.
+Partial output blocks to character devices will produce a warning message.
+A truncated input block is one where a variable length record oriented
+conversion value was specified and the input line was too long to
+fit in the conversion record or was not newline terminated.
+.Pp
+Normally, data resulting from input or conversion or both are aggregated
+into output blocks of the specified size.
+After the end of input is reached, any remaining output is written as
+a block.
+This means that the final output block may be shorter than the output
+block size.
+.Pp
+If
+.Nm
+receives a
+.Dv SIGINFO
+signal
+(see the
+.Ic status
+argument for
+.Xr stty 1 ) ,
+the current input and output block counts will
+be written to the standard error output
+in the same format as the standard completion message.
+If
+.Nm
+receives a
+.Dv SIGINT
+signal, the current input and output block counts will
+be written to the standard error output
+in the same format as the standard completion message and
+.Nm
+will exit.
+.Sh INPUT AND OUTPUT FLAGS
+There are flags valid for input only, for output only, or for either.
+.Pp
+The flags that apply to both input and output are:
+.Bl -tag -width directory
+.It Cm alt_io
+Use Alternative I/O.
+.It Cm async
+Use
+.Dv SIGIO
+signaling for I/O.
+.It Cm cloexec
+Set the close-on-exec flag.
+.It Cm direct
+Directly access the data, skipping any caches.
+.It Cm directory
+Not available for
+.Nm .
+.It Cm exlock
+Atomically obtain an exclusive lock.
+.It Cm noctty
+Do not consider the file as a potential controlling tty.
+.It Cm nofollow
+Do not follow symbolic links.
+.It Cm nonblock
+Do not block on open or I/O requests.
+.It Cm nosigpipe
+Return
+.Er EPIPE
+instead of raising
+.Dv SIGPIPE .
+.It Cm shlock
+Atomically obtain a shared lock.
+.It Cm sync
+All I/O will be performed with full synchronization.
+.El
+.Pp
+The flags that apply to only input are:
+.Bl -tag -width directory
+.It Cm rdonly
+Set the read-only flag.
+.It Cm rdwr
+Set the read and write flags.
+.It Cm rsync
+Enable read synchronization, if the
+.Cm sync
+option is also set.
+.El
+.Pp
+The flags that apply to only output are:
+.Bl -tag -width directory
+.It Cm append
+Append to the output by default.
+.It Cm creat
+Create the output file.
+.It Cm dsync
+Wait for all data to be synchronously written.
+.It Cm excl
+Ensure that output is to a new file.
+.It Cm trunc
+Truncate the output file before writing.
+.It Cm wronly
+Set the write-only flag.
+.El
+See
+.Xr open 2
+and
+.Xr ioctl 2
+for more details.
+.Sh EXIT STATUS
+.Ex -std dd
+.Sh EXAMPLES
+To print summary information in human-readable form:
+.Pp
+.Dl dd if=/dev/zero of=/dev/null count=1 msgfmt=human
+.Pp
+To customize the information summary output and print it through
+.Xr unvis 3 :
+.Bd -literal -offset indent
+dd if=/dev/zero of=/dev/null count=1 \e
+ msgfmt='speed:%E, in %s seconds\en' 2>&1 | unvis
+.Ed
+.Sh SEE ALSO
+.Xr cp 1 ,
+.Xr mt 1 ,
+.Xr tr 1
+.Sh STANDARDS
+The
+.Nm
+utility is expected to be a superset of the
+.St -p1003.2
+standard.
+The
+.Cm files
+and
+.Cm msgfmt
+operands and the
+.Cm ascii ,
+.Cm ebcdic ,
+.Cm ibm ,
+.Cm oldascii ,
+.Cm oldebcdic
+and
+.Cm oldibm
+values are extensions to the
+.Tn POSIX
+standard.
+.Sh HISTORY
+A
+.Nm
+utility appeared in
+.At v5 .
diff --git a/bin/dd/dd.c b/bin/dd/dd.c
new file mode 100644
index 0000000..c4fb1e8
--- /dev/null
+++ b/bin/dd/dd.c
@@ -0,0 +1,616 @@
+/* $NetBSD: dd.c,v 1.51 2016/09/05 01:00:07 sevan Exp $ */
+
+/*-
+ * Copyright (c) 1991, 1993, 1994
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Keith Muller of the University of California, San Diego and Lance
+ * Visser of Convex Computer Corporation.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+#ifndef lint
+__COPYRIGHT("@(#) Copyright (c) 1991, 1993, 1994\
+ The Regents of the University of California. All rights reserved.");
+#endif /* not lint */
+
+#ifndef lint
+#if 0
+static char sccsid[] = "@(#)dd.c 8.5 (Berkeley) 4/2/94";
+#else
+__RCSID("$NetBSD: dd.c,v 1.51 2016/09/05 01:00:07 sevan Exp $");
+#endif
+#endif /* not lint */
+
+#include <sys/param.h>
+#include <sys/stat.h>
+#include <sys/ioctl.h>
+#include <sys/mtio.h>
+#include <sys/time.h>
+
+#include <ctype.h>
+#include <err.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <locale.h>
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "dd.h"
+#include "extern.h"
+
+static void dd_close(void);
+static void dd_in(void);
+static void getfdtype(IO *);
+static void redup_clean_fd(IO *);
+static void setup(void);
+
+IO in, out; /* input/output state */
+STAT st; /* statistics */
+void (*cfunc)(void); /* conversion function */
+uint64_t cpy_cnt; /* # of blocks to copy */
+static off_t pending = 0; /* pending seek if sparse */
+u_int ddflags; /* conversion options */
+#ifdef NO_IOFLAG
+#define iflag O_RDONLY
+#define oflag O_CREAT
+#else
+u_int iflag = O_RDONLY; /* open(2) flags for input file */
+u_int oflag = O_CREAT; /* open(2) flags for output file */
+#endif /* NO_IOFLAG */
+uint64_t cbsz; /* conversion block size */
+u_int files_cnt = 1; /* # of files to copy */
+uint64_t progress = 0; /* display sign of life */
+const u_char *ctab; /* conversion table */
+sigset_t infoset; /* a set blocking SIGINFO */
+const char *msgfmt = "posix"; /* default summary() message format */
+
+/*
+ * Ops for stdin/stdout and crunch'd dd. These are always host ops.
+ */
+static const struct ddfops ddfops_stdfd = {
+ .op_open = open,
+ .op_close = close,
+ .op_fcntl = fcntl,
+ .op_ioctl = ioctl,
+ .op_fstat = fstat,
+ .op_fsync = fsync,
+ .op_ftruncate = ftruncate,
+ .op_lseek = lseek,
+ .op_read = read,
+ .op_write = write,
+};
+extern const struct ddfops ddfops_prog;
+
+int
+main(int argc, char *argv[])
+{
+ int ch;
+
+ setprogname(argv[0]);
+ (void)setlocale(LC_ALL, "");
+
+ while ((ch = getopt(argc, argv, "")) != -1) {
+ switch (ch) {
+ default:
+ errx(EXIT_FAILURE, "usage: dd [operand ...]");
+ /* NOTREACHED */
+ }
+ }
+ argc -= (optind - 1);
+ argv += (optind - 1);
+
+ jcl(argv);
+#ifndef CRUNCHOPS
+ if (ddfops_prog.op_init && ddfops_prog.op_init() == -1)
+ err(1, "prog init");
+#endif
+ setup();
+
+ (void)signal(SIGINFO, summaryx);
+ (void)signal(SIGINT, terminate);
+ (void)sigemptyset(&infoset);
+ (void)sigaddset(&infoset, SIGINFO);
+
+ (void)atexit(summary);
+
+ while (files_cnt--)
+ dd_in();
+
+ dd_close();
+ exit(0);
+ /* NOTREACHED */
+}
+
+static void
+setup(void)
+{
+#ifdef CRUNCHOPS
+ const struct ddfops *prog_ops = &ddfops_stdfd;
+#else
+ const struct ddfops *prog_ops = &ddfops_prog;
+#endif
+
+ if (in.name == NULL) {
+ in.name = "stdin";
+ in.fd = STDIN_FILENO;
+ in.ops = &ddfops_stdfd;
+ } else {
+ in.ops = prog_ops;
+ in.fd = ddop_open(in, in.name, iflag, 0);
+ if (in.fd < 0)
+ err(EXIT_FAILURE, "%s", in.name);
+ /* NOTREACHED */
+
+ /* Ensure in.fd is outside the stdio descriptor range */
+ redup_clean_fd(&in);
+ }
+
+ getfdtype(&in);
+
+ if (files_cnt > 1 && !(in.flags & ISTAPE)) {
+ errx(EXIT_FAILURE, "files is not supported for non-tape devices");
+ /* NOTREACHED */
+ }
+
+ if (out.name == NULL) {
+ /* No way to check for read access here. */
+ out.fd = STDOUT_FILENO;
+ out.name = "stdout";
+ out.ops = &ddfops_stdfd;
+ } else {
+ out.ops = prog_ops;
+
+#ifndef NO_IOFLAG
+ if ((oflag & O_TRUNC) && (ddflags & C_SEEK)) {
+ errx(EXIT_FAILURE, "oflag=trunc is incompatible "
+ "with seek or oseek operands, giving up.");
+ /* NOTREACHED */
+ }
+ if ((oflag & O_TRUNC) && (ddflags & C_NOTRUNC)) {
+ errx(EXIT_FAILURE, "oflag=trunc is incompatible "
+ "with conv=notrunc operand, giving up.");
+ /* NOTREACHED */
+ }
+#endif /* NO_IOFLAG */
+#define OFLAGS \
+ (oflag | (ddflags & (C_SEEK | C_NOTRUNC) ? 0 : O_TRUNC))
+ out.fd = ddop_open(out, out.name, O_RDWR | OFLAGS, DEFFILEMODE);
+ /*
+ * May not have read access, so try again with write only.
+ * Without read we may have a problem if output also does
+ * not support seeks.
+ */
+ if (out.fd < 0) {
+ out.fd = ddop_open(out, out.name, O_WRONLY | OFLAGS,
+ DEFFILEMODE);
+ out.flags |= NOREAD;
+ }
+ if (out.fd < 0) {
+ err(EXIT_FAILURE, "%s", out.name);
+ /* NOTREACHED */
+ }
+
+ /* Ensure out.fd is outside the stdio descriptor range */
+ redup_clean_fd(&out);
+ }
+
+ getfdtype(&out);
+
+ /*
+ * Allocate space for the input and output buffers. If not doing
+ * record oriented I/O, only need a single buffer.
+ */
+ if (!(ddflags & (C_BLOCK|C_UNBLOCK))) {
+ size_t dbsz = out.dbsz;
+ if (!(ddflags & C_BS))
+ dbsz += in.dbsz - 1;
+ if ((in.db = malloc(dbsz)) == NULL) {
+ err(EXIT_FAILURE, NULL);
+ /* NOTREACHED */
+ }
+ out.db = in.db;
+ } else if ((in.db =
+ malloc((u_int)(MAX(in.dbsz, cbsz) + cbsz))) == NULL ||
+ (out.db = malloc((u_int)(out.dbsz + cbsz))) == NULL) {
+ err(EXIT_FAILURE, NULL);
+ /* NOTREACHED */
+ }
+ in.dbp = in.db;
+ out.dbp = out.db;
+
+ /* Position the input/output streams. */
+ if (in.offset)
+ pos_in();
+ if (out.offset)
+ pos_out();
+
+ /*
+ * Truncate the output file; ignore errors because it fails on some
+ * kinds of output files, tapes, for example.
+ */
+ if ((ddflags & (C_OF | C_SEEK | C_NOTRUNC)) == (C_OF | C_SEEK))
+ (void)ddop_ftruncate(out, out.fd, (off_t)out.offset * out.dbsz);
+
+ /*
+ * If converting case at the same time as another conversion, build a
+ * table that does both at once. If just converting case, use the
+ * built-in tables.
+ */
+ if (ddflags & (C_LCASE|C_UCASE)) {
+#ifdef NO_CONV
+ /* Should not get here, but just in case... */
+ errx(EXIT_FAILURE, "case conv and -DNO_CONV");
+ /* NOTREACHED */
+#else /* NO_CONV */
+ u_int cnt;
+
+ if (ddflags & C_ASCII || ddflags & C_EBCDIC) {
+ if (ddflags & C_LCASE) {
+ for (cnt = 0; cnt < 256; ++cnt)
+ casetab[cnt] = tolower(ctab[cnt]);
+ } else {
+ for (cnt = 0; cnt < 256; ++cnt)
+ casetab[cnt] = toupper(ctab[cnt]);
+ }
+ } else {
+ if (ddflags & C_LCASE) {
+ for (cnt = 0; cnt < 256; ++cnt)
+ casetab[cnt] = tolower(cnt);
+ } else {
+ for (cnt = 0; cnt < 256; ++cnt)
+ casetab[cnt] = toupper(cnt);
+ }
+ }
+
+ ctab = casetab;
+#endif /* NO_CONV */
+ }
+
+ (void)gettimeofday(&st.start, NULL); /* Statistics timestamp. */
+}
+
+static void
+getfdtype(IO *io)
+{
+ struct mtget mt;
+ struct stat sb;
+
+ if (io->ops->op_fstat(io->fd, &sb)) {
+ err(EXIT_FAILURE, "%s", io->name);
+ /* NOTREACHED */
+ }
+ if (S_ISCHR(sb.st_mode))
+ io->flags |= io->ops->op_ioctl(io->fd, MTIOCGET, &mt)
+ ? ISCHR : ISTAPE;
+ else if (io->ops->op_lseek(io->fd, (off_t)0, SEEK_CUR) == -1
+ && errno == ESPIPE)
+ io->flags |= ISPIPE; /* XXX fixed in 4.4BSD */
+}
+
+/*
+ * Move the parameter file descriptor to a descriptor that is outside the
+ * stdio descriptor range, if necessary. This is required to avoid
+ * accidentally outputting completion or error messages into the
+ * output file that were intended for the tty.
+ */
+static void
+redup_clean_fd(IO *io)
+{
+ int fd = io->fd;
+ int newfd;
+
+ if (fd != STDIN_FILENO && fd != STDOUT_FILENO &&
+ fd != STDERR_FILENO)
+ /* File descriptor is ok, return immediately. */
+ return;
+
+ /*
+ * 3 is the first descriptor greater than STD*_FILENO. Any
+ * free descriptor valued 3 or above is acceptable...
+ */
+ newfd = io->ops->op_fcntl(fd, F_DUPFD, 3);
+ if (newfd < 0) {
+ err(EXIT_FAILURE, "dupfd IO");
+ /* NOTREACHED */
+ }
+
+ io->ops->op_close(fd);
+ io->fd = newfd;
+}
+
+static void
+dd_in(void)
+{
+ int flags;
+ int64_t n;
+
+ for (flags = ddflags;;) {
+ if (cpy_cnt && (st.in_full + st.in_part) >= cpy_cnt)
+ return;
+
+ /*
+ * Clear the buffer first if doing "sync" on input.
+ * If doing block operations use spaces. This will
+ * affect not only the C_NOERROR case, but also the
+ * last partial input block which should be padded
+ * with zero and not garbage.
+ */
+ if (flags & C_SYNC) {
+ if (flags & (C_BLOCK|C_UNBLOCK))
+ (void)memset(in.dbp, ' ', in.dbsz);
+ else
+ (void)memset(in.dbp, 0, in.dbsz);
+ }
+
+ n = ddop_read(in, in.fd, in.dbp, in.dbsz);
+ if (n == 0) {
+ in.dbrcnt = 0;
+ return;
+ }
+
+ /* Read error. */
+ if (n < 0) {
+
+ /*
+ * If noerror not specified, die. POSIX requires that
+ * the warning message be followed by an I/O display.
+ */
+ if (!(flags & C_NOERROR)) {
+ err(EXIT_FAILURE, "%s", in.name);
+ /* NOTREACHED */
+ }
+ warn("%s", in.name);
+ summary();
+
+ /*
+ * If it's not a tape drive or a pipe, seek past the
+ * error. If your OS doesn't do the right thing for
+ * raw disks this section should be modified to re-read
+ * in sector size chunks.
+ */
+ if (!(in.flags & (ISPIPE|ISTAPE)) &&
+ ddop_lseek(in, in.fd, (off_t)in.dbsz, SEEK_CUR))
+ warn("%s", in.name);
+
+ /* If sync not specified, omit block and continue. */
+ if (!(ddflags & C_SYNC))
+ continue;
+
+ /* Read errors count as full blocks. */
+ in.dbcnt += in.dbrcnt = in.dbsz;
+ ++st.in_full;
+
+ /* Handle full input blocks. */
+ } else if ((uint64_t)n == in.dbsz) {
+ in.dbcnt += in.dbrcnt = n;
+ ++st.in_full;
+
+ /* Handle partial input blocks. */
+ } else {
+ /* If sync, use the entire block. */
+ if (ddflags & C_SYNC)
+ in.dbcnt += in.dbrcnt = in.dbsz;
+ else
+ in.dbcnt += in.dbrcnt = n;
+ ++st.in_part;
+ }
+
+ /*
+ * POSIX states that if bs is set and no other conversions
+ * than noerror, notrunc or sync are specified, the block
+ * is output without buffering as it is read.
+ */
+ if (ddflags & C_BS) {
+ out.dbcnt = in.dbcnt;
+ dd_out(1);
+ in.dbcnt = 0;
+ continue;
+ }
+
+ if (ddflags & C_SWAB) {
+ if ((n = in.dbrcnt) & 1) {
+ ++st.swab;
+ --n;
+ }
+ swab(in.dbp, in.dbp, n);
+ }
+
+ in.dbp += in.dbrcnt;
+ (*cfunc)();
+ }
+}
+
+/*
+ * Cleanup any remaining I/O and flush output. If necessary, output file
+ * is truncated.
+ */
+static void
+dd_close(void)
+{
+
+ if (cfunc == def)
+ def_close();
+ else if (cfunc == block)
+ block_close();
+ else if (cfunc == unblock)
+ unblock_close();
+ if (ddflags & C_OSYNC && out.dbcnt < out.dbsz) {
+ (void)memset(out.dbp, 0, out.dbsz - out.dbcnt);
+ out.dbcnt = out.dbsz;
+ }
+ /* If there are pending sparse blocks, make sure
+ * to write out the final block un-sparse
+ */
+ if ((out.dbcnt == 0) && pending) {
+ memset(out.db, 0, out.dbsz);
+ out.dbcnt = out.dbsz;
+ out.dbp = out.db + out.dbcnt;
+ pending -= out.dbsz;
+ }
+ if (out.dbcnt)
+ dd_out(1);
+
+ /*
+ * Reporting nfs write error may be deferred until next
+ * write(2) or close(2) system call. So, we need to do an
+ * extra check. If an output is stdout, the file structure
+ * may be shared with other processes and close(2) just
+ * decreases the reference count.
+ */
+ if (out.fd == STDOUT_FILENO && ddop_fsync(out, out.fd) == -1
+ && errno != EINVAL) {
+ err(EXIT_FAILURE, "fsync stdout");
+ /* NOTREACHED */
+ }
+ if (ddop_close(out, out.fd) == -1) {
+ err(EXIT_FAILURE, "close");
+ /* NOTREACHED */
+ }
+}
+
+void
+dd_out(int force)
+{
+ static int warned;
+ int64_t cnt, n, nw;
+ u_char *outp;
+
+ /*
+ * Write one or more blocks out. The common case is writing a full
+ * output block in a single write; increment the full block stats.
+ * Otherwise, we're into partial block writes. If a partial write,
+ * and it's a character device, just warn. If a tape device, quit.
+ *
+ * The partial writes represent two cases. 1: Where the input block
+ * was less than expected so the output block was less than expected.
+ * 2: Where the input block was the right size but we were forced to
+ * write the block in multiple chunks. The original versions of dd(1)
+ * never wrote a block in more than a single write, so the latter case
+ * never happened.
+ *
+ * One special case is if we're forced to do the write -- in that case
+ * we play games with the buffer size, and it's usually a partial write.
+ */
+ outp = out.db;
+ for (n = force ? out.dbcnt : out.dbsz;; n = out.dbsz) {
+ for (cnt = n;; cnt -= nw) {
+
+ if (!force && ddflags & C_SPARSE) {
+ int sparse, i;
+ sparse = 1; /* Is buffer sparse? */
+ for (i = 0; i < cnt; i++)
+ if (outp[i] != 0) {
+ sparse = 0;
+ break;
+ }
+ if (sparse) {
+ pending += cnt;
+ outp += cnt;
+ nw = 0;
+ break;
+ }
+ }
+ if (pending != 0) {
+ if (ddop_lseek(out,
+ out.fd, pending, SEEK_CUR) == -1)
+ err(EXIT_FAILURE, "%s: seek error creating sparse file",
+ out.name);
+ }
+ nw = bwrite(&out, outp, cnt);
+ if (nw <= 0) {
+ if (nw == 0)
+ errx(EXIT_FAILURE,
+ "%s: end of device", out.name);
+ /* NOTREACHED */
+ if (errno != EINTR)
+ err(EXIT_FAILURE, "%s", out.name);
+ /* NOTREACHED */
+ nw = 0;
+ }
+ if (pending) {
+ st.bytes += pending;
+ st.sparse += pending/out.dbsz;
+ st.out_full += pending/out.dbsz;
+ pending = 0;
+ }
+ outp += nw;
+ st.bytes += nw;
+ if (nw == n) {
+ if ((uint64_t)n != out.dbsz)
+ ++st.out_part;
+ else
+ ++st.out_full;
+ break;
+ }
+ ++st.out_part;
+ if (nw == cnt)
+ break;
+ if (out.flags & ISCHR && !warned) {
+ warned = 1;
+ warnx("%s: short write on character device", out.name);
+ }
+ if (out.flags & ISTAPE)
+ errx(EXIT_FAILURE,
+ "%s: short write on tape device", out.name);
+ /* NOTREACHED */
+
+ }
+ if ((out.dbcnt -= n) < out.dbsz)
+ break;
+ }
+
+ /* Reassemble the output block. */
+ if (out.dbcnt)
+ (void)memmove(out.db, out.dbp - out.dbcnt, out.dbcnt);
+ out.dbp = out.db + out.dbcnt;
+
+ if (progress && (st.out_full + st.out_part) % progress == 0)
+ (void)write(STDERR_FILENO, ".", 1);
+}
+
+/*
+ * A protected against SIGINFO write
+ */
+ssize_t
+bwrite(IO *io, const void *buf, size_t len)
+{
+ sigset_t oset;
+ ssize_t rv;
+ int oerrno;
+
+ (void)sigprocmask(SIG_BLOCK, &infoset, &oset);
+ rv = io->ops->op_write(io->fd, buf, len);
+ oerrno = errno;
+ (void)sigprocmask(SIG_SETMASK, &oset, NULL);
+ errno = oerrno;
+ return (rv);
+}
diff --git a/bin/dd/dd.h b/bin/dd/dd.h
new file mode 100644
index 0000000..2b2712d
--- /dev/null
+++ b/bin/dd/dd.h
@@ -0,0 +1,126 @@
+/* $NetBSD: dd.h,v 1.16 2015/03/18 13:23:49 manu Exp $ */
+
+/*-
+ * Copyright (c) 1991, 1993, 1994
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Keith Muller of the University of California, San Diego and Lance
+ * Visser of Convex Computer Corporation.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * @(#)dd.h 8.3 (Berkeley) 4/2/94
+ */
+
+#include <sys/stat.h>
+
+struct ddfops {
+ int (*op_init)(void);
+
+ int (*op_open)(const char *, int, ...);
+ int (*op_close)(int);
+
+ int (*op_fcntl)(int, int, ...);
+ int (*op_ioctl)(int, unsigned long, ...);
+
+ int (*op_fstat)(int, struct stat *);
+ int (*op_fsync)(int);
+ int (*op_ftruncate)(int, off_t);
+
+ off_t (*op_lseek)(int, off_t, int);
+
+ ssize_t (*op_read)(int, void *, size_t);
+ ssize_t (*op_write)(int, const void *, size_t);
+};
+
+#define ddop_open(dir, a1, a2, ...) dir.ops->op_open(a1, a2, __VA_ARGS__)
+#define ddop_close(dir, a1) dir.ops->op_close(a1)
+#define ddop_fcntl(dir, a1, a2, ...) dir.ops->op_fcntl(a1, a2, __VA_ARGS__)
+#define ddop_ioctl(dir, a1, a2, ...) dir.ops->op_ioctl(a1, a2, __VA_ARGS__)
+#define ddop_fsync(dir, a1) dir.ops->op_fsync(a1)
+#define ddop_ftruncate(dir, a1, a2) dir.ops->op_ftruncate(a1, a2)
+#define ddop_lseek(dir, a1, a2, a3) dir.ops->op_lseek(a1, a2, a3)
+#define ddop_read(dir, a1, a2, a3) dir.ops->op_read(a1, a2, a3)
+#define ddop_write(dir, a1, a2, a3) dir.ops->op_write(a1, a2, a3)
+
+/* Input/output stream state. */
+typedef struct {
+ u_char *db; /* buffer address */
+ u_char *dbp; /* current buffer I/O address */
+ uint64_t dbcnt; /* current buffer byte count */
+ int64_t dbrcnt; /* last read byte count */
+ uint64_t dbsz; /* buffer size */
+
+#define ISCHR 0x01 /* character device (warn on short) */
+#define ISPIPE 0x02 /* pipe (not truncatable) */
+#define ISTAPE 0x04 /* tape (not seekable) */
+#define NOREAD 0x08 /* not readable */
+ u_int flags;
+
+ const char *name; /* name */
+ int fd; /* file descriptor */
+ uint64_t offset; /* # of blocks to skip */
+ struct ddfops const *ops; /* ops to use with fd */
+} IO;
+
+typedef struct {
+ uint64_t in_full; /* # of full input blocks */
+ uint64_t in_part; /* # of partial input blocks */
+ uint64_t out_full; /* # of full output blocks */
+ uint64_t out_part; /* # of partial output blocks */
+ uint64_t trunc; /* # of truncated records */
+ uint64_t swab; /* # of odd-length swab blocks */
+ uint64_t sparse; /* # of sparse output blocks */
+ uint64_t bytes; /* # of bytes written */
+ struct timeval start; /* start time of dd */
+} STAT;
+
+/* Flags (in ddflags, iflag and oflag). */
+#define C_NONE 0x00000
+#define C_ASCII 0x00001
+#define C_BLOCK 0x00002
+#define C_BS 0x00004
+#define C_CBS 0x00008
+#define C_COUNT 0x00010
+#define C_EBCDIC 0x00020
+#define C_FILES 0x00040
+#define C_IBS 0x00080
+#define C_IF 0x00100
+#define C_LCASE 0x00200
+#define C_NOERROR 0x00400
+#define C_NOTRUNC 0x00800
+#define C_OBS 0x01000
+#define C_OF 0x02000
+#define C_SEEK 0x04000
+#define C_SKIP 0x08000
+#define C_SWAB 0x10000
+#define C_SYNC 0x20000
+#define C_UCASE 0x40000
+#define C_UNBLOCK 0x80000
+#define C_OSYNC 0x100000
+#define C_SPARSE 0x200000
+#define C_IFLAG 0x400000
+#define C_OFLAG 0x800000
diff --git a/bin/dd/dd_hostops.c b/bin/dd/dd_hostops.c
new file mode 100644
index 0000000..d6e7a89
--- /dev/null
+++ b/bin/dd/dd_hostops.c
@@ -0,0 +1,53 @@
+/* $NetBSD: dd_hostops.c,v 1.1 2011/02/04 19:42:12 pooka Exp $ */
+
+/*-
+ * Copyright (c) 2010 The NetBSD Foundation, Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
+ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+#ifndef lint
+__RCSID("$NetBSD: dd_hostops.c,v 1.1 2011/02/04 19:42:12 pooka Exp $");
+#endif /* !lint */
+
+#include <sys/types.h>
+#include <sys/ioctl.h>
+
+#include <fcntl.h>
+#include <unistd.h>
+
+#include "dd.h"
+
+const struct ddfops ddfops_prog = {
+ .op_open = open,
+ .op_close = close,
+ .op_fcntl = fcntl,
+ .op_ioctl = ioctl,
+ .op_fstat = fstat,
+ .op_fsync = fsync,
+ .op_ftruncate = ftruncate,
+ .op_lseek = lseek,
+ .op_read = read,
+ .op_write = write,
+};
diff --git a/bin/dd/dd_rumpops.c b/bin/dd/dd_rumpops.c
new file mode 100644
index 0000000..71f7db4
--- /dev/null
+++ b/bin/dd/dd_rumpops.c
@@ -0,0 +1,52 @@
+/* $NetBSD: dd_rumpops.c,v 1.1 2011/02/04 19:42:12 pooka Exp $ */
+
+/*-
+ * Copyright (c) 2010 The NetBSD Foundation, Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
+ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+#ifndef lint
+__RCSID("$NetBSD: dd_rumpops.c,v 1.1 2011/02/04 19:42:12 pooka Exp $");
+#endif /* !lint */
+
+#include <rump/rump_syscalls.h>
+#include <rump/rumpclient.h>
+
+#include "dd.h"
+
+const struct ddfops ddfops_prog = {
+ .op_init = rumpclient_init,
+
+ .op_open = rump_sys_open,
+ .op_close = rump_sys_close,
+ .op_fcntl = rump_sys_fcntl,
+ .op_ioctl = rump_sys_ioctl,
+ .op_fstat = rump_sys_fstat,
+ .op_fsync = rump_sys_fsync,
+ .op_ftruncate = rump_sys_ftruncate,
+ .op_lseek = rump_sys_lseek,
+ .op_read = rump_sys_read,
+ .op_write = rump_sys_write,
+};
diff --git a/bin/dd/extern.h b/bin/dd/extern.h
new file mode 100644
index 0000000..27b51a0
--- /dev/null
+++ b/bin/dd/extern.h
@@ -0,0 +1,86 @@
+/* $NetBSD: extern.h,v 1.23 2015/03/18 13:23:49 manu Exp $ */
+
+/*-
+ * Copyright (c) 1991, 1993, 1994
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Keith Muller of the University of California, San Diego and Lance
+ * Visser of Convex Computer Corporation.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * @(#)extern.h 8.3 (Berkeley) 4/2/94
+ */
+
+#include <sys/cdefs.h>
+
+#ifdef NO_CONV
+__dead void block(void);
+__dead void block_close(void);
+__dead void unblock(void);
+__dead void unblock_close(void);
+#else
+void block(void);
+void block_close(void);
+void unblock(void);
+void unblock_close(void);
+#endif
+
+#ifndef NO_MSGFMT
+int dd_write_msg(const char *, int);
+#endif
+
+void dd_out(int);
+void def(void);
+void def_close(void);
+void jcl(char **);
+void pos_in(void);
+void pos_out(void);
+void summary(void);
+void summaryx(int);
+__dead void terminate(int);
+void unblock(void);
+void unblock_close(void);
+ssize_t bwrite(IO *, const void *, size_t);
+
+extern IO in, out;
+extern STAT st;
+extern void (*cfunc)(void);
+extern uint64_t cpy_cnt;
+extern uint64_t cbsz;
+extern u_int ddflags;
+#ifndef NO_IOFLAG
+extern u_int iflag;
+extern u_int oflag;
+#endif /* NO_IOFLAG */
+extern u_int files_cnt;
+extern uint64_t progress;
+extern const u_char *ctab;
+extern const u_char a2e_32V[], a2e_POSIX[];
+extern const u_char e2a_32V[], e2a_POSIX[];
+extern const u_char a2ibm_32V[], a2ibm_POSIX[];
+extern u_char casetab[];
+extern const char *msgfmt;
diff --git a/bin/dd/misc.c b/bin/dd/misc.c
new file mode 100644
index 0000000..0fac98b
--- /dev/null
+++ b/bin/dd/misc.c
@@ -0,0 +1,342 @@
+/* $NetBSD: misc.c,v 1.23 2011/11/07 22:24:23 jym Exp $ */
+
+/*-
+ * Copyright (c) 1991, 1993, 1994
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Keith Muller of the University of California, San Diego and Lance
+ * Visser of Convex Computer Corporation.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+#ifndef lint
+#if 0
+static char sccsid[] = "@(#)misc.c 8.3 (Berkeley) 4/2/94";
+#else
+__RCSID("$NetBSD: misc.c,v 1.23 2011/11/07 22:24:23 jym Exp $");
+#endif
+#endif /* not lint */
+
+#include <sys/param.h>
+#include <sys/types.h>
+#include <sys/time.h>
+
+#include <err.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <util.h>
+#include <inttypes.h>
+
+#include "dd.h"
+#include "extern.h"
+
+#define tv2mS(tv) ((tv).tv_sec * 1000LL + ((tv).tv_usec + 500) / 1000)
+
+static void posix_summary(void);
+#ifndef NO_MSGFMT
+static void custom_summary(void);
+static void human_summary(void);
+static void quiet_summary(void);
+
+static void buffer_write(const char *, size_t, int);
+#endif /* NO_MSGFMT */
+
+void
+summary(void)
+{
+
+ if (progress)
+ (void)write(STDERR_FILENO, "\n", 1);
+
+#ifdef NO_MSGFMT
+ return posix_summary();
+#else /* NO_MSGFMT */
+ if (strncmp(msgfmt, "human", sizeof("human")) == 0)
+ return human_summary();
+
+ if (strncmp(msgfmt, "posix", sizeof("posix")) == 0)
+ return posix_summary();
+
+ if (strncmp(msgfmt, "quiet", sizeof("quiet")) == 0)
+ return quiet_summary();
+
+ return custom_summary();
+#endif /* NO_MSGFMT */
+}
+
+static void
+posix_summary(void)
+{
+ char buf[100];
+ int64_t mS;
+ struct timeval tv;
+
+ if (progress)
+ (void)write(STDERR_FILENO, "\n", 1);
+
+ (void)gettimeofday(&tv, NULL);
+ mS = tv2mS(tv) - tv2mS(st.start);
+ if (mS == 0)
+ mS = 1;
+
+ /* Use snprintf(3) so that we don't reenter stdio(3). */
+ (void)snprintf(buf, sizeof(buf),
+ "%llu+%llu records in\n%llu+%llu records out\n",
+ (unsigned long long)st.in_full, (unsigned long long)st.in_part,
+ (unsigned long long)st.out_full, (unsigned long long)st.out_part);
+ (void)write(STDERR_FILENO, buf, strlen(buf));
+ if (st.swab) {
+ (void)snprintf(buf, sizeof(buf), "%llu odd length swab %s\n",
+ (unsigned long long)st.swab,
+ (st.swab == 1) ? "block" : "blocks");
+ (void)write(STDERR_FILENO, buf, strlen(buf));
+ }
+ if (st.trunc) {
+ (void)snprintf(buf, sizeof(buf), "%llu truncated %s\n",
+ (unsigned long long)st.trunc,
+ (st.trunc == 1) ? "block" : "blocks");
+ (void)write(STDERR_FILENO, buf, strlen(buf));
+ }
+ if (st.sparse) {
+ (void)snprintf(buf, sizeof(buf), "%llu sparse output %s\n",
+ (unsigned long long)st.sparse,
+ (st.sparse == 1) ? "block" : "blocks");
+ (void)write(STDERR_FILENO, buf, strlen(buf));
+ }
+ (void)snprintf(buf, sizeof(buf),
+ "%llu bytes transferred in %lu.%03d secs (%llu bytes/sec)\n",
+ (unsigned long long) st.bytes,
+ (long) (mS / 1000),
+ (int) (mS % 1000),
+ (unsigned long long) (st.bytes * 1000LL / mS));
+ (void)write(STDERR_FILENO, buf, strlen(buf));
+}
+
+/* ARGSUSED */
+void
+summaryx(int notused)
+{
+
+ summary();
+}
+
+/* ARGSUSED */
+void
+terminate(int signo)
+{
+
+ summary();
+ (void)raise_default_signal(signo);
+ _exit(127);
+}
+
+#ifndef NO_MSGFMT
+/*
+ * Buffer write(2) calls
+ */
+static void
+buffer_write(const char *str, size_t size, int flush)
+{
+ static char wbuf[128];
+ static size_t cnt = 0; /* Internal counter to allow wbuf to wrap */
+
+ unsigned int i;
+
+ for (i = 0; i < size; i++) {
+ if (str != NULL) {
+ wbuf[cnt++] = str[i];
+ }
+ if (cnt >= sizeof(wbuf)) {
+ (void)write(STDERR_FILENO, wbuf, cnt);
+ cnt = 0;
+ }
+ }
+
+ if (flush != 0) {
+ (void)write(STDERR_FILENO, wbuf, cnt);
+ cnt = 0;
+ }
+}
+
+/*
+ * Write summary to stderr according to format 'fmt'. If 'enable' is 0, it
+ * will not attempt to write anything. Can be used to validate the
+ * correctness of the 'fmt' string.
+ */
+int
+dd_write_msg(const char *fmt, int enable)
+{
+ char hbuf[7], nbuf[32];
+ const char *ptr;
+ int64_t mS;
+ struct timeval tv;
+
+ (void)gettimeofday(&tv, NULL);
+ mS = tv2mS(tv) - tv2mS(st.start);
+ if (mS == 0)
+ mS = 1;
+
+#define ADDC(c) do { if (enable != 0) buffer_write(&c, 1, 0); } \
+ while (/*CONSTCOND*/0)
+#define ADDS(p) do { if (enable != 0) buffer_write(p, strlen(p), 0); } \
+ while (/*CONSTCOND*/0)
+
+ for (ptr = fmt; *ptr; ptr++) {
+ if (*ptr != '%') {
+ ADDC(*ptr);
+ continue;
+ }
+
+ switch (*++ptr) {
+ case 'b':
+ (void)snprintf(nbuf, sizeof(nbuf), "%llu",
+ (unsigned long long)st.bytes);
+ ADDS(nbuf);
+ break;
+ case 'B':
+ if (humanize_number(hbuf, sizeof(hbuf),
+ st.bytes, "B",
+ HN_AUTOSCALE, HN_DECIMAL) == -1)
+ warnx("humanize_number (bytes transferred)");
+ ADDS(hbuf);
+ break;
+ case 'e':
+ (void)snprintf(nbuf, sizeof(nbuf), "%llu",
+ (unsigned long long) (st.bytes * 1000LL / mS));
+ ADDS(nbuf);
+ break;
+ case 'E':
+ if (humanize_number(hbuf, sizeof(hbuf),
+ st.bytes * 1000LL / mS, "B",
+ HN_AUTOSCALE, HN_DECIMAL) == -1)
+ warnx("humanize_number (bytes per second)");
+ ADDS(hbuf); ADDS("/sec");
+ break;
+ case 'i':
+ (void)snprintf(nbuf, sizeof(nbuf), "%llu",
+ (unsigned long long)st.in_part);
+ ADDS(nbuf);
+ break;
+ case 'I':
+ (void)snprintf(nbuf, sizeof(nbuf), "%llu",
+ (unsigned long long)st.in_full);
+ ADDS(nbuf);
+ break;
+ case 'o':
+ (void)snprintf(nbuf, sizeof(nbuf), "%llu",
+ (unsigned long long)st.out_part);
+ ADDS(nbuf);
+ break;
+ case 'O':
+ (void)snprintf(nbuf, sizeof(nbuf), "%llu",
+ (unsigned long long)st.out_full);
+ ADDS(nbuf);
+ break;
+ case 's':
+ (void)snprintf(nbuf, sizeof(nbuf), "%li.%03d",
+ (long) (mS / 1000), (int) (mS % 1000));
+ ADDS(nbuf);
+ break;
+ case 'p':
+ (void)snprintf(nbuf, sizeof(nbuf), "%llu",
+ (unsigned long long)st.sparse);
+ ADDS(nbuf);
+ break;
+ case 't':
+ (void)snprintf(nbuf, sizeof(nbuf), "%llu",
+ (unsigned long long)st.trunc);
+ ADDS(nbuf);
+ break;
+ case 'w':
+ (void)snprintf(nbuf, sizeof(nbuf), "%llu",
+ (unsigned long long)st.swab);
+ ADDS(nbuf);
+ break;
+ case 'P':
+ ADDS("block");
+ if (st.sparse != 1) ADDS("s");
+ break;
+ case 'T':
+ ADDS("block");
+ if (st.trunc != 1) ADDS("s");
+ break;
+ case 'W':
+ ADDS("block");
+ if (st.swab != 1) ADDS("s");
+ break;
+ case '%':
+ ADDC(*ptr);
+ break;
+ default:
+ if (*ptr == '\0')
+ goto done;
+ errx(EXIT_FAILURE, "unknown specifier '%c' in "
+ "msgfmt string", *ptr);
+ /* NOTREACHED */
+ }
+ }
+
+done:
+ /* flush buffer */
+ buffer_write(NULL, 0, 1);
+ return 0;
+}
+
+static void
+custom_summary(void)
+{
+
+ dd_write_msg(msgfmt, 1);
+}
+
+static void
+human_summary(void)
+{
+ (void)dd_write_msg("%I+%i records in\n%O+%o records out\n", 1);
+ if (st.swab) {
+ (void)dd_write_msg("%w odd length swab %W\n", 1);
+ }
+ if (st.trunc) {
+ (void)dd_write_msg("%t truncated %T\n", 1);
+ }
+ if (st.sparse) {
+ (void)dd_write_msg("%p sparse output %P\n", 1);
+ }
+ (void)dd_write_msg("%b bytes (%B) transferred in %s secs "
+ "(%e bytes/sec - %E)\n", 1);
+}
+
+static void
+quiet_summary(void)
+{
+
+ /* stay quiet */
+}
+#endif /* NO_MSGFMT */
diff --git a/bin/dd/position.c b/bin/dd/position.c
new file mode 100644
index 0000000..36dd580
--- /dev/null
+++ b/bin/dd/position.c
@@ -0,0 +1,185 @@
+/* $NetBSD: position.c,v 1.18 2010/11/22 21:04:28 pooka Exp $ */
+
+/*-
+ * Copyright (c) 1991, 1993, 1994
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Keith Muller of the University of California, San Diego and Lance
+ * Visser of Convex Computer Corporation.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+#ifndef lint
+#if 0
+static char sccsid[] = "@(#)position.c 8.3 (Berkeley) 4/2/94";
+#else
+__RCSID("$NetBSD: position.c,v 1.18 2010/11/22 21:04:28 pooka Exp $");
+#endif
+#endif /* not lint */
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/ioctl.h>
+#include <sys/mtio.h>
+#include <sys/time.h>
+
+#include <err.h>
+#include <errno.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "dd.h"
+#include "extern.h"
+
+/*
+ * Position input/output data streams before starting the copy. Device type
+ * dependent. Seekable devices use lseek, and the rest position by reading.
+ * Seeking past the end of file can cause null blocks to be written to the
+ * output.
+ */
+void
+pos_in(void)
+{
+ int bcnt, cnt, nr, warned;
+
+ /* If not a pipe or tape device, try to seek on it. */
+ if (!(in.flags & (ISPIPE|ISTAPE))) {
+ if (ddop_lseek(in, in.fd,
+ (off_t)in.offset * (off_t)in.dbsz, SEEK_CUR) == -1) {
+ err(EXIT_FAILURE, "%s", in.name);
+ /* NOTREACHED */
+ }
+ return;
+ /* NOTREACHED */
+ }
+
+ /*
+ * Read the data. If a pipe, read until satisfy the number of bytes
+ * being skipped. No differentiation for reading complete and partial
+ * blocks for other devices.
+ */
+ for (bcnt = in.dbsz, cnt = in.offset, warned = 0; cnt;) {
+ if ((nr = ddop_read(in, in.fd, in.db, bcnt)) > 0) {
+ if (in.flags & ISPIPE) {
+ if (!(bcnt -= nr)) {
+ bcnt = in.dbsz;
+ --cnt;
+ }
+ } else
+ --cnt;
+ continue;
+ }
+
+ if (nr == 0) {
+ if (files_cnt > 1) {
+ --files_cnt;
+ continue;
+ }
+ errx(EXIT_FAILURE, "skip reached end of input");
+ /* NOTREACHED */
+ }
+
+ /*
+ * Input error -- either EOF with no more files, or I/O error.
+ * If noerror not set die. POSIX requires that the warning
+ * message be followed by an I/O display.
+ */
+ if (ddflags & C_NOERROR) {
+ if (!warned) {
+
+ warn("%s", in.name);
+ warned = 1;
+ summary();
+ }
+ continue;
+ }
+ err(EXIT_FAILURE, "%s", in.name);
+ /* NOTREACHED */
+ }
+}
+
+void
+pos_out(void)
+{
+ struct mtop t_op;
+ int n;
+ uint64_t cnt;
+
+ /*
+ * If not a tape, try seeking on the file. Seeking on a pipe is
+ * going to fail, but don't protect the user -- they shouldn't
+ * have specified the seek operand.
+ */
+ if (!(out.flags & ISTAPE)) {
+ if (ddop_lseek(out, out.fd,
+ (off_t)out.offset * (off_t)out.dbsz, SEEK_SET) == -1)
+ err(EXIT_FAILURE, "%s", out.name);
+ /* NOTREACHED */
+ return;
+ }
+
+ /* If no read access, try using mtio. */
+ if (out.flags & NOREAD) {
+ t_op.mt_op = MTFSR;
+ t_op.mt_count = out.offset;
+
+ if (ddop_ioctl(out, out.fd, MTIOCTOP, &t_op) < 0)
+ err(EXIT_FAILURE, "%s", out.name);
+ /* NOTREACHED */
+ return;
+ }
+
+ /* Read it. */
+ for (cnt = 0; cnt < out.offset; ++cnt) {
+ if ((n = ddop_read(out, out.fd, out.db, out.dbsz)) > 0)
+ continue;
+
+ if (n < 0)
+ err(EXIT_FAILURE, "%s", out.name);
+ /* NOTREACHED */
+
+ /*
+ * If reach EOF, fill with NUL characters; first, back up over
+ * the EOF mark. Note, cnt has not yet been incremented, so
+ * the EOF read does not count as a seek'd block.
+ */
+ t_op.mt_op = MTBSR;
+ t_op.mt_count = 1;
+ if (ddop_ioctl(out, out.fd, MTIOCTOP, &t_op) == -1)
+ err(EXIT_FAILURE, "%s", out.name);
+ /* NOTREACHED */
+
+ while (cnt++ < out.offset)
+ if ((uint64_t)(n = bwrite(&out,
+ out.db, out.dbsz)) != out.dbsz)
+ err(EXIT_FAILURE, "%s", out.name);
+ /* NOTREACHED */
+ break;
+ }
+}
diff --git a/bin/df/df.1 b/bin/df/df.1
new file mode 100644
index 0000000..6ac5ed0
--- /dev/null
+++ b/bin/df/df.1
@@ -0,0 +1,206 @@
+.\" $NetBSD: df.1,v 1.49 2018/08/26 23:34:52 sevan Exp $
+.\"
+.\" Copyright (c) 1989, 1990, 1993
+.\" The Regents of the University of California. All rights reserved.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\" notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must reproduce the above copyright
+.\" notice, this list of conditions and the following disclaimer in the
+.\" documentation and/or other materials provided with the distribution.
+.\" 3. Neither the name of the University nor the names of its contributors
+.\" may be used to endorse or promote products derived from this software
+.\" without specific prior written permission.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+.\" ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+.\" SUCH DAMAGE.
+.\"
+.\" @(#)df.1 8.2 (Berkeley) 1/13/92
+.\"
+.Dd August 27, 2018
+.Dt DF 1
+.Os
+.Sh NAME
+.Nm df
+.Nd display free disk space
+.Sh SYNOPSIS
+.Nm
+.Op Fl agln
+.Op Fl Ghkm | Fl ihkm | Fl Pk
+.Op Fl t Ar type
+.Op Ar file | Ar file_system ...
+.Sh DESCRIPTION
+.Nm
+displays statistics about the amount of free disk space on the specified
+.Ar file_system
+or on the file system of which
+.Ar file
+is a part.
+By default, all sizes are reported in 512-byte block counts.
+If neither a file or a
+.Ar file_system
+operand is specified,
+statistics for all mounted file systems are displayed
+(subject to the
+.Fl l
+and
+.Fl t
+options below).
+.Pp
+Note that the printed count of available blocks takes
+.Va minfree
+into account, and thus will be negative when the number of free blocks
+on the filesystem is less than
+.Va minfree .
+.Pp
+The following options are available:
+.Bl -tag -width Ds
+.It Fl a
+Show all mount points,
+including those that were mounted with the
+.Dv MNT_IGNORE
+flag.
+.It Fl G
+Display all the fields of the structure(s) returned by
+.Xr statvfs 2 .
+This option cannot be used with the
+.Fl i
+or
+.Fl P
+options, and it is modelled after the Solaris
+.Fl g
+option.
+This option will override the
+.Fl g ,
+.Fl h ,
+.Fl k ,
+and
+.Fl m
+options, as well as any setting of
+.Ev BLOCKSIZE .
+.It Fl g
+The
+.Fl g
+option causes the numbers to be reported in gigabytes (1024*1024*1024
+bytes).
+.It Fl h
+"Human-readable" output.
+Use unit suffixes: Byte, Kilobyte, Megabyte,
+Gigabyte, Terabyte, Petabyte, Exabyte in order to reduce the number of
+digits to four or less.
+.It Fl i
+Include statistics on the number of free inodes.
+.It Fl k
+By default, all sizes are reported in 512-byte block counts.
+The
+.Fl k
+option causes the numbers to be reported in kilobytes (1024 bytes).
+.It Fl l
+Display statistics only about mounted file systems with the
+.Dv MNT_LOCAL
+flag set.
+If a non-local file system is given as an argument, a
+warning is issued and no information is given on that file system.
+.It Fl m
+The
+.Fl m
+option causes the numbers to be reported in megabytes (1024*1024 bytes).
+.It Fl n
+Print out the previously obtained statistics from the file systems.
+This option should be used if it is possible that one or more
+file systems are in a state such that they will not be able to provide
+statistics without a long delay.
+When this option is specified,
+.Nm
+will not request new statistics from the file systems, but will respond
+with the possibly stale statistics that were previously obtained.
+.It Fl P
+Produce output in the following portable format:
+.Pp
+If both the
+.Fl P
+and
+.Fl k
+option are specified, the output will be preceded by the following header
+line, formatted to match the data following it:
+.Bd -literal
+"Filesystem 1024-blocks Used Available Capacity Mounted on\en"
+.Ed
+.Pp
+If the
+.Fl P
+option is specified without the
+.Fl k
+options, the output will be preceded by the following header line,
+formatted to match the data following it:
+.Bd -literal
+"Filesystem <blksize>-blocks Used Available Capacity Mounted on\en"
+.Ed
+.Pp
+The header line is followed by data formatted as follows:
+.Bd -literal
+"%s %d %d %d %d%% %s\en", <file system name>, <total space>,
+ <space used>, <space free>, <percentage used>,
+ <file system root>
+.Ed
+.Pp
+Note that the
+.Fl i
+option may not be specified with
+.Fl P .
+.It Fl t Ar type
+Is used to indicate the actions should only be taken on
+filesystems of the specified type.
+More than one type may be specified in a comma-separated list.
+The list of filesystem types can be prefixed with
+.Dq no
+to specify the filesystem types for which action should
+.Em not
+be taken.
+If a file system is given on the command line that is not of
+the specified type, a warning is issued and no information is given on
+that file system.
+.El
+.Sh ENVIRONMENT
+.Bl -tag -width BLOCKSIZE
+.It Ev BLOCKSIZE
+If the environment variable
+.Ev BLOCKSIZE
+is set, and the
+.Fl g ,
+.Fl h ,
+.Fl k
+and
+.Fl m
+options are not specified, the block counts will be displayed in units of that
+size block.
+.El
+.Sh SEE ALSO
+.Xr quota 1 ,
+.Xr fstatvfs 2 ,
+.Xr getvfsstat 2 ,
+.Xr statvfs 2 ,
+.Xr getbsize 3 ,
+.Xr getmntinfo 3 ,
+.Xr fs 5 ,
+.Xr fstab 5 ,
+.Xr mount 8 ,
+.Xr quot 8 ,
+.Xr tunefs 8
+.Sh HISTORY
+A
+.Nm
+utility appeared in
+.At v1 .
diff --git a/bin/df/df.c b/bin/df/df.c
new file mode 100644
index 0000000..9392d0c
--- /dev/null
+++ b/bin/df/df.c
@@ -0,0 +1,520 @@
+/* $NetBSD: df.c,v 1.93 2018/08/26 23:34:52 sevan Exp $ */
+
+/*
+ * Copyright (c) 1980, 1990, 1993, 1994
+ * The Regents of the University of California. All rights reserved.
+ * (c) UNIX System Laboratories, Inc.
+ * All or some portions of this file are derived from material licensed
+ * to the University of California by American Telephone and Telegraph
+ * Co. or Unix System Laboratories, Inc. and are reproduced herein with
+ * the permission of UNIX System Laboratories, Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+#ifndef lint
+__COPYRIGHT(
+"@(#) Copyright (c) 1980, 1990, 1993, 1994\
+ The Regents of the University of California. All rights reserved.");
+#endif /* not lint */
+
+#ifndef lint
+#if 0
+static char sccsid[] = "@(#)df.c 8.7 (Berkeley) 4/2/94";
+#else
+__RCSID("$NetBSD: df.c,v 1.93 2018/08/26 23:34:52 sevan Exp $");
+#endif
+#endif /* not lint */
+
+#include <sys/param.h>
+#include <sys/stat.h>
+#include <sys/mount.h>
+
+#include <assert.h>
+#include <err.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <locale.h>
+#include <util.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <util.h>
+
+static char *getmntpt(const char *);
+static void prtstat(struct statvfs *, int);
+static int selected(const char *, size_t);
+static void maketypelist(char *);
+static size_t regetmntinfo(struct statvfs **, size_t);
+__dead static void usage(void);
+static void prthumanval(int64_t, const char *);
+static void prthuman(struct statvfs *, int64_t, int64_t);
+
+static int aflag, gflag, hflag, iflag, lflag, nflag, Pflag;
+static long usize;
+static char **typelist;
+
+int
+main(int argc, char *argv[])
+{
+ struct stat stbuf;
+ struct statvfs *mntbuf;
+ long mntsize;
+ int ch, i, maxwidth, width;
+ char *mntpt;
+
+ setprogname(argv[0]);
+ (void)setlocale(LC_ALL, "");
+
+ while ((ch = getopt(argc, argv, "aGghiklmnPt:")) != -1)
+ switch (ch) {
+ case 'a':
+ aflag = 1;
+ break;
+ case 'g':
+ hflag = 0;
+ usize = 1024 * 1024 * 1024;
+ break;
+ case 'G':
+ gflag = 1;
+ break;
+ case 'h':
+ hflag = 1;
+ usize = 0;
+ break;
+ case 'i':
+ iflag = 1;
+ break;
+ case 'k':
+ hflag = 0;
+ usize = 1024;
+ break;
+ case 'l':
+ lflag = 1;
+ break;
+ case 'm':
+ hflag = 0;
+ usize = 1024 * 1024;
+ break;
+ case 'n':
+ nflag = 1;
+ break;
+ case 'P':
+ Pflag = 1;
+ break;
+ case 't':
+ if (typelist != NULL)
+ errx(EXIT_FAILURE,
+ "only one -t option may be specified.");
+ maketypelist(optarg);
+ break;
+ case '?':
+ default:
+ usage();
+ }
+
+ if (gflag && (Pflag || iflag))
+ errx(EXIT_FAILURE,
+ "only one of -G and -P or -i may be specified");
+ if (Pflag && iflag)
+ errx(EXIT_FAILURE,
+ "only one of -P and -i may be specified");
+#if 0
+ /*
+ * The block size cannot be checked until after getbsize() is called.
+ */
+ if (Pflag && (hflag || (usize != 1024 && usize != 512)))
+ errx(EXIT_FAILURE,
+ "non-standard block size incompatible with -P");
+#endif
+ argc -= optind;
+ argv += optind;
+
+ mntsize = getmntinfo(&mntbuf, MNT_NOWAIT);
+ if (mntsize == 0)
+ err(EXIT_FAILURE,
+ "retrieving information on mounted file systems");
+
+ if (*argv == NULL) {
+ mntsize = regetmntinfo(&mntbuf, mntsize);
+ } else {
+ if ((mntbuf = malloc(argc * sizeof(*mntbuf))) == NULL)
+ err(EXIT_FAILURE, "can't allocate statvfs array");
+ mntsize = 0;
+ for (/*EMPTY*/; *argv != NULL; argv++) {
+ if (stat(*argv, &stbuf) < 0) {
+ if ((mntpt = getmntpt(*argv)) == 0) {
+ warn("%s", *argv);
+ continue;
+ }
+ } else if (S_ISBLK(stbuf.st_mode)) {
+ if ((mntpt = getmntpt(*argv)) == 0)
+ mntpt = *argv;
+ } else
+ mntpt = *argv;
+ /*
+ * Statfs does not take a `wait' flag, so we cannot
+ * implement nflag here.
+ */
+ if (!statvfs(mntpt, &mntbuf[mntsize]))
+ if (lflag &&
+ (mntbuf[mntsize].f_flag & MNT_LOCAL) == 0)
+ warnx("Warning: %s is not a local %s",
+ *argv, "file system");
+ else if
+ (!selected(mntbuf[mntsize].f_fstypename,
+ sizeof(mntbuf[mntsize].f_fstypename)))
+ warnx("Warning: %s mounted as a %s %s",
+ *argv,
+ mntbuf[mntsize].f_fstypename,
+ "file system");
+ else
+ ++mntsize;
+ else
+ warn("%s", *argv);
+ }
+ }
+
+ maxwidth = 0;
+ for (i = 0; i < mntsize; i++) {
+ width = (int)strlen(mntbuf[i].f_mntfromname);
+ if (width > maxwidth)
+ maxwidth = width;
+ }
+ for (i = 0; i < mntsize; i++)
+ prtstat(&mntbuf[i], maxwidth);
+ return 0;
+}
+
+static char *
+getmntpt(const char *name)
+{
+ size_t mntsize, i;
+ struct statvfs *mntbuf;
+
+ mntsize = getmntinfo(&mntbuf, MNT_NOWAIT);
+ if (mntsize == 0)
+ err(EXIT_FAILURE, "Can't get mount information");
+ for (i = 0; i < mntsize; i++) {
+ if (!strcmp(mntbuf[i].f_mntfromname, name))
+ return mntbuf[i].f_mntonname;
+ }
+ return 0;
+}
+
+static enum { IN_LIST, NOT_IN_LIST } which;
+
+static int
+selected(const char *type, size_t len)
+{
+ char **av;
+
+ /* If no type specified, it's always selected. */
+ if (typelist == NULL)
+ return 1;
+ for (av = typelist; *av != NULL; ++av)
+ if (!strncmp(type, *av, len))
+ return which == IN_LIST ? 1 : 0;
+ return which == IN_LIST ? 0 : 1;
+}
+
+static void
+maketypelist(char *fslist)
+{
+ size_t i;
+ char *nextcp, **av;
+
+ if ((fslist == NULL) || (fslist[0] == '\0'))
+ errx(EXIT_FAILURE, "empty type list");
+
+ /*
+ * XXX
+ * Note: the syntax is "noxxx,yyy" for no xxx's and
+ * no yyy's, not the more intuitive "noyyy,noyyy".
+ */
+ if (fslist[0] == 'n' && fslist[1] == 'o') {
+ fslist += 2;
+ which = NOT_IN_LIST;
+ } else
+ which = IN_LIST;
+
+ /* Count the number of types. */
+ for (i = 1, nextcp = fslist;
+ (nextcp = strchr(nextcp, ',')) != NULL; i++)
+ ++nextcp;
+
+ /* Build an array of that many types. */
+ if ((av = typelist = malloc((i + 1) * sizeof(*av))) == NULL)
+ err(EXIT_FAILURE, "can't allocate type array");
+ av[0] = fslist;
+ for (i = 1, nextcp = fslist;
+ (nextcp = strchr(nextcp, ',')) != NULL; i++) {
+ *nextcp = '\0';
+ av[i] = ++nextcp;
+ }
+ /* Terminate the array. */
+ av[i] = NULL;
+}
+
+/*
+ * Make a pass over the filesystem info in ``mntbuf'' filtering out
+ * filesystem types not in ``fsmask'' and possibly re-stating to get
+ * current (not cached) info. Returns the new count of valid statvfs bufs.
+ */
+static size_t
+regetmntinfo(struct statvfs **mntbufp, size_t mntsize)
+{
+ size_t i, j;
+ struct statvfs *mntbuf;
+
+ if (!lflag && typelist == NULL && aflag)
+ return nflag ? mntsize : (size_t)getmntinfo(mntbufp, MNT_WAIT);
+
+ mntbuf = *mntbufp;
+ j = 0;
+ for (i = 0; i < mntsize; i++) {
+ if (!aflag && (mntbuf[i].f_flag & MNT_IGNORE) != 0)
+ continue;
+ if (lflag && (mntbuf[i].f_flag & MNT_LOCAL) == 0)
+ continue;
+ if (!selected(mntbuf[i].f_fstypename,
+ sizeof(mntbuf[i].f_fstypename)))
+ continue;
+ if (nflag)
+ mntbuf[j] = mntbuf[i];
+ else {
+ struct statvfs layerbuf = mntbuf[i];
+ (void)statvfs(mntbuf[i].f_mntonname, &mntbuf[j]);
+ /*
+ * If the FS name changed, then new data is for
+ * a different layer and we don't want it.
+ */
+ if (memcmp(layerbuf.f_mntfromname,
+ mntbuf[j].f_mntfromname, MNAMELEN))
+ mntbuf[j] = layerbuf;
+ }
+ j++;
+ }
+ return j;
+}
+
+static void
+prthumanval(int64_t bytes, const char *pad)
+{
+ char buf[6];
+
+ (void)humanize_number(buf, sizeof(buf) - (bytes < 0 ? 0 : 1),
+ bytes, "", HN_AUTOSCALE,
+ HN_B | HN_NOSPACE | HN_DECIMAL);
+
+ (void)printf("%s %6s", pad, buf);
+}
+
+static void
+prthuman(struct statvfs *sfsp, int64_t used, int64_t bavail)
+{
+
+ prthumanval((int64_t)(sfsp->f_blocks * sfsp->f_frsize), " ");
+ prthumanval((int64_t)(used * sfsp->f_frsize), " ");
+ prthumanval((int64_t)(bavail * sfsp->f_frsize), " ");
+}
+
+/*
+ * Convert statvfs returned filesystem size into BLOCKSIZE units.
+ * Attempts to avoid overflow for large filesystems.
+ */
+#define fsbtoblk(num, fsbs, bs) \
+ (((fsbs) != 0 && (uint64_t)(fsbs) < (uint64_t)(bs)) ? \
+ (int64_t)(num) / (int64_t)((bs) / (fsbs)) : \
+ (int64_t)(num) * (int64_t)((fsbs) / (bs)))
+
+/*
+ * Print out status about a filesystem.
+ */
+static void
+prtstat(struct statvfs *sfsp, int maxwidth)
+{
+ static long blocksize;
+ static int headerlen, timesthrough;
+ static const char *header;
+ static const char full[] = "100";
+ static const char empty[] = " 0";
+ int64_t used, availblks, inodes;
+ int64_t bavail;
+ char pb[64];
+
+ if (gflag) {
+ /*
+ * From SunOS-5.6:
+ *
+ * /var (/dev/dsk/c0t0d0s3 ): 8192 block size 1024 frag size
+ * 984242 total blocks 860692 free blocks 859708 available 249984 total files
+ * 248691 free files 8388611 filesys id
+ * ufs fstype 0x00000004 flag 255 filename length
+ *
+ */
+ (void)printf("%10s (%-12s): %7ld block size %12ld frag size\n",
+ sfsp->f_mntonname, sfsp->f_mntfromname,
+ sfsp->f_bsize, /* On UFS/FFS systems this is
+ * also called the "optimal
+ * transfer block size" but it
+ * is of course the file
+ * system's block size too.
+ */
+ sfsp->f_frsize); /* not so surprisingly the
+ * "fundamental file system
+ * block size" is the frag
+ * size.
+ */
+ (void)printf("%10" PRId64 " total blocks %10" PRId64
+ " free blocks %10" PRId64 " available\n",
+ (uint64_t)sfsp->f_blocks, (uint64_t)sfsp->f_bfree,
+ (uint64_t)sfsp->f_bavail);
+ (void)printf("%10" PRId64 " total files %10" PRId64
+ " free files %12lx filesys id\n",
+ (uint64_t)sfsp->f_ffree, (uint64_t)sfsp->f_files,
+ sfsp->f_fsid);
+ (void)printf("%10s fstype %#15lx flag %17ld filename "
+ "length\n", sfsp->f_fstypename, sfsp->f_flag,
+ sfsp->f_namemax);
+ (void)printf("%10lu owner %17" PRId64 " syncwrites %12" PRId64
+ " asyncwrites\n\n", (unsigned long)sfsp->f_owner,
+ sfsp->f_syncwrites, sfsp->f_asyncwrites);
+
+ /*
+ * a concession by the structured programming police to the
+ * indentation police....
+ */
+ return;
+ }
+ if (maxwidth < 12)
+ maxwidth = 12;
+ if (++timesthrough == 1) {
+ switch (blocksize = usize) {
+ case 1024:
+ header = Pflag ? "1024-blocks" : "1K-blocks";
+ headerlen = (int)strlen(header);
+ break;
+ case 1024 * 1024:
+ header = "1M-blocks";
+ headerlen = (int)strlen(header);
+ break;
+ case 1024 * 1024 * 1024:
+ header = "1G-blocks";
+ headerlen = (int)strlen(header);
+ break;
+ default:
+ if (hflag) {
+ header = "Size";
+ headerlen = (int)strlen(header);
+ } else
+ header = getbsize(&headerlen, &blocksize);
+ break;
+ }
+ if (Pflag) {
+ /*
+ * either:
+ * "Filesystem 1024-blocks Used Available Capacity Mounted on\n"
+ * or:
+ * "Filesystem 512-blocks Used Available Capacity Mounted on\n"
+ */
+ if (blocksize != 1024 && blocksize != 512)
+ errx(EXIT_FAILURE,
+ "non-standard block size incompatible with -P");
+ (void)printf("Filesystem %s Used Available Capacity "
+ "Mounted on\n", header);
+ } else {
+ (void)printf("%-*.*s %s Used Avail %%Cap",
+ maxwidth - (headerlen - 10),
+ maxwidth - (headerlen - 10),
+ "Filesystem", header);
+ if (iflag)
+ (void)printf(" iUsed iAvail %%iCap");
+ (void)printf(" Mounted on\n");
+ }
+ }
+ used = sfsp->f_blocks - sfsp->f_bfree;
+ bavail = sfsp->f_bfree - sfsp->f_bresvd;
+ availblks = bavail + used;
+ if (Pflag) {
+ assert(hflag == 0);
+ assert(blocksize > 0);
+ /*
+ * "%s %d %d %d %s %s\n", <file system name>, <total space>,
+ * <space used>, <space free>, <percentage used>,
+ * <file system root>
+ */
+ (void)printf("%s %" PRId64 " %" PRId64 " %" PRId64 " %s%% %s\n",
+ sfsp->f_mntfromname,
+ fsbtoblk(sfsp->f_blocks, sfsp->f_frsize, blocksize),
+ fsbtoblk(used, sfsp->f_frsize, blocksize),
+ fsbtoblk(bavail, sfsp->f_frsize, blocksize),
+ availblks == 0 ? full : strspct(pb, sizeof(pb), used,
+ availblks, 0), sfsp->f_mntonname);
+ /*
+ * another concession by the structured programming police to
+ * the indentation police....
+ *
+ * Note iflag cannot be set when Pflag is set.
+ */
+ return;
+ }
+
+ (void)printf("%-*.*s ", maxwidth, maxwidth, sfsp->f_mntfromname);
+
+ if (hflag)
+ prthuman(sfsp, used, bavail);
+ else
+ (void)printf("%10" PRId64 " %10" PRId64 " %10" PRId64,
+ fsbtoblk(sfsp->f_blocks, sfsp->f_frsize, blocksize),
+ fsbtoblk(used, sfsp->f_frsize, blocksize),
+ fsbtoblk(bavail, sfsp->f_frsize, blocksize));
+ (void)printf(" %3s%%",
+ availblks == 0 ? full :
+ strspct(pb, sizeof(pb), used, availblks, 0));
+ if (iflag) {
+ inodes = sfsp->f_files;
+ used = inodes - sfsp->f_ffree;
+ (void)printf(" %8jd %8jd %4s%%",
+ (intmax_t)used, (intmax_t)sfsp->f_ffree,
+ inodes == 0 ? (used == 0 ? empty : full) :
+ strspct(pb, sizeof(pb), used, inodes, 0));
+ }
+ (void)printf(" %s\n", sfsp->f_mntonname);
+}
+
+static void
+usage(void)
+{
+
+ (void)fprintf(stderr,
+ "Usage: %s [-agln] [-Ghkm|-ihkm|-Pk] [-t type] [file | "
+ "file_system ...]\n",
+ getprogname());
+ exit(1);
+ /* NOTREACHED */
+}
diff --git a/bin/echo/echo.1 b/bin/echo/echo.1
new file mode 100644
index 0000000..a7e374b
--- /dev/null
+++ b/bin/echo/echo.1
@@ -0,0 +1,71 @@
+.\" $NetBSD: echo.1,v 1.17 2017/07/04 06:48:41 wiz Exp $
+.\"
+.\" Copyright (c) 1990, 1993
+.\" The Regents of the University of California. All rights reserved.
+.\"
+.\" This code is derived from software contributed to Berkeley by
+.\" the Institute of Electrical and Electronics Engineers, Inc.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\" notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must reproduce the above copyright
+.\" notice, this list of conditions and the following disclaimer in the
+.\" documentation and/or other materials provided with the distribution.
+.\" 3. Neither the name of the University nor the names of its contributors
+.\" may be used to endorse or promote products derived from this software
+.\" without specific prior written permission.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+.\" ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+.\" SUCH DAMAGE.
+.\"
+.\" @(#)echo.1 8.1 (Berkeley) 7/22/93
+.\"
+.Dd August 14, 2016
+.Dt ECHO 1
+.Os
+.Sh NAME
+.Nm echo
+.Nd write arguments to the standard output
+.Sh SYNOPSIS
+.Nm
+.Op Fl n
+.Op Ar string ...
+.Sh DESCRIPTION
+The
+.Nm
+utility writes any specified operands, separated by single blank (`` '')
+characters and followed by a newline (``\en'') character, to the standard
+output.
+.Pp
+The following option is available:
+.Bl -tag -width flag
+.It Fl n
+Do not print the trailing newline character.
+.El
+.Sh EXIT STATUS
+.Ex -std echo
+.Sh SEE ALSO
+.Xr printf 1
+.Sh STANDARDS
+The
+.Nm
+utility is expected to be
+.St -p1003.2
+compatible.
+.Sh HISTORY
+An
+.Nm
+utility appeared in
+.At v2 .
diff --git a/bin/echo/echo.c b/bin/echo/echo.c
new file mode 100644
index 0000000..2e59363
--- /dev/null
+++ b/bin/echo/echo.c
@@ -0,0 +1,81 @@
+/* $NetBSD: echo.c,v 1.19 2016/09/05 01:00:07 sevan Exp $ */
+
+/*
+ * Copyright (c) 1989, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+#ifndef lint
+__COPYRIGHT(
+"@(#) Copyright (c) 1989, 1993\
+ The Regents of the University of California. All rights reserved.");
+#endif /* not lint */
+
+#ifndef lint
+#if 0
+static char sccsid[] = "@(#)echo.c 8.1 (Berkeley) 5/31/93";
+#else
+__RCSID("$NetBSD: echo.c,v 1.19 2016/09/05 01:00:07 sevan Exp $");
+#endif
+#endif /* not lint */
+
+#include <locale.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+/* ARGSUSED */
+int
+main(int argc, char *argv[])
+{
+ int nflag;
+
+ setprogname(argv[0]);
+ (void)setlocale(LC_ALL, "");
+
+ /* This utility may NOT do getopt(3) option parsing. */
+ if (*++argv && !strcmp(*argv, "-n")) {
+ ++argv;
+ nflag = 1;
+ }
+ else
+ nflag = 0;
+
+ while (*argv) {
+ (void)printf("%s", *argv);
+ if (*++argv)
+ (void)putchar(' ');
+ }
+ if (nflag == 0)
+ (void)putchar('\n');
+ fflush(stdout);
+ if (ferror(stdout))
+ exit(1);
+ exit(0);
+ /* NOTREACHED */
+}
diff --git a/bin/ed/POSIX b/bin/ed/POSIX
new file mode 100644
index 0000000..6bea801
--- /dev/null
+++ b/bin/ed/POSIX
@@ -0,0 +1,103 @@
+$NetBSD: POSIX,v 1.10 1999/11/18 19:16:34 kristerw Exp $
+
+This version of ed(1) is not strictly POSIX compliant, as described in
+the POSIX 1003.2 document. The following is a summary of the omissions,
+extensions and possible deviations from POSIX 1003.2.
+
+OMISSIONS
+---------
+1) Locale(3) is not supported yet.
+
+2) For backwards compatibility, the POSIX rule that says a range of
+ addresses cannot be used where only a single address is expected has
+ been relaxed.
+
+3) To support the BSD `s' command (see extension [1] below),
+ substitution patterns cannot be delimited by numbers or the characters
+ `r', `g' and `p'. In contrast, POSIX specifies any character expect
+ space or newline can used as a delimiter.
+
+EXTENSIONS
+----------
+1) BSD commands have been implemented wherever they do not conflict with
+ the POSIX standard. The BSD-ism's included are:
+ i) `s' (i.e., s[n][rgp]*) to repeat a previous substitution,
+ ii) `W' for appending text to an existing file,
+ iii) `wq' for exiting after a write,
+ iv) `z' for scrolling through the buffer, and
+ v) BSD line addressing syntax (i.e., `^' and `%') is recognized.
+
+2) If crypt(3) is available, files can be read and written using DES
+ encryption. The `x' command prompts the user to enter a key used for
+ encrypting/ decrypting subsequent reads and writes. If only a newline
+ is entered as the key, then encryption is disabled. Otherwise, a key
+ is read in the same manner as a password entry. The key remains in
+ effect until encryption is disabled. For more information on the
+ encryption algorithm, see the bdes(1) man page. Encryption/decryption
+ should be fully compatible with SunOS des(1).
+
+3) The POSIX interactive global commands `G' and `V' are extended to
+ support multiple commands, including `a', `i' and `c'. The command
+ format is the same as for the global commands `g' and `v', i.e., one
+ command per line with each line, except for the last, ending in a
+ backslash (\).
+
+4) An extension to the POSIX file commands `E', `e', `r', `W' and `w' is
+ that <file> arguments are processed for backslash escapes, i.e., any
+ character preceded by a backslash is interpreted literally. If the
+ first unescaped character of a <file> argument is a bang (!), then the
+ rest of the line is interpreted as a shell command, and no escape
+ processing is performed by ed.
+
+5) For SunOS ed(1) compatibility, ed runs in restricted mode if invoked
+ as red. This limits editing of files in the local directory only and
+ prohibits shell commands.
+
+DEVIATIONS
+----------
+1) Though ed is not a stream editor, it can be used to edit binary files.
+ To assist in binary editing, when a file containing at least one ASCII
+ NUL character is written, a newline is not appended if it did not
+ already contain one upon reading. In particular, reading /dev/null
+ prior to writing prevents appending a newline to a binary file.
+
+ For example, to create a file with ed containing a single NUL character:
+ $ ed file
+ a
+ ^@
+ .
+ r /dev/null
+ wq
+
+ Similarly, to remove a newline from the end of binary `file':
+ $ ed file
+ r /dev/null
+ wq
+
+2) Since the behavior of `u' (undo) within a `g' (global) command list is
+ not specified by POSIX, it follows the behavior of the SunOS ed:
+ undo forces a global command list to be executed only once, rather than
+ for each line matching a global pattern. In addtion, each instance of
+ `u' within a global command undoes all previous commands (including
+ undo's) in the command list. This seems the best way, since the
+ alternatives are either too complicated to implement or too confusing
+ to use.
+
+ The global/undo combination is useful for masking errors that
+ would otherwise cause a script to fail. For instance, an ed script
+ to remove any occurrences of either `censor1' or `censor2' might be
+ written as:
+ ed - file <<EOF
+ 1g/.*/u\
+ ,s/censor1//g\
+ ,s/censor2//g
+ ...
+
+3) The `m' (move) command within a `g' command list also follows the SunOS
+ ed implementation: any moved lines are removed from the global command's
+ `active' list.
+
+4) If ed is invoked with a name argument prefixed by a bang (!), then the
+ remainder of the argument is interpreted as a shell command. To invoke
+ ed on a file whose name starts with bang, prefix the name with a
+ backslash.
diff --git a/bin/ed/README b/bin/ed/README
new file mode 100644
index 0000000..474672b
--- /dev/null
+++ b/bin/ed/README
@@ -0,0 +1,23 @@
+$NetBSD: README,v 1.10 2019/01/04 19:13:58 maya Exp $
+
+ed is an 8-bit-clean, POSIX-compliant line editor. It should work with
+any regular expression package that conforms to the POSIX interface
+standard, such as GNU regex(3).
+
+If reliable signals are supported (e.g., POSIX sigaction(2)), it should
+compile with little trouble. Otherwise, the macros SPL1() and SPL0()
+should be redefined to disable interrupts.
+
+The following compiler directives are recognized:
+DES - to add encryption support (requires crypt(3))
+BACKWARDS - for backwards compatibility
+NEED_INSQUE - if insque(3) is missing
+
+The file `POSIX' describes extensions to and deviations from the POSIX
+standard.
+
+The ./test directory contains regression tests for ed. The README
+file in that directory explains how to run these.
+
+For a description of the ed algorithm, see Kernighan and Plauger's book
+"Software Tools in Pascal," Addison-Wesley, 1981.
diff --git a/bin/ed/buf.c b/bin/ed/buf.c
new file mode 100644
index 0000000..f824f75
--- /dev/null
+++ b/bin/ed/buf.c
@@ -0,0 +1,319 @@
+/* $NetBSD: buf.c,v 1.27 2014/03/23 05:06:42 dholland Exp $ */
+
+/* buf.c: This file contains the scratch-file buffer routines for the
+ ed line editor. */
+/*-
+ * Copyright (c) 1993 Andrew Moore, Talke Studio.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+#ifndef lint
+#if 0
+static char *rcsid = "@(#)buf.c,v 1.4 1994/02/01 00:34:35 alm Exp";
+#else
+__RCSID("$NetBSD: buf.c,v 1.27 2014/03/23 05:06:42 dholland Exp $");
+#endif
+#endif /* not lint */
+
+#include <sys/file.h>
+#include <sys/stat.h>
+
+#include <paths.h>
+#include <stdio.h>
+#include <err.h>
+
+#include "ed.h"
+
+
+FILE *sfp; /* scratch file pointer */
+off_t sfseek; /* scratch file position */
+int seek_write; /* seek before writing */
+line_t buffer_head; /* incore buffer */
+
+/* get_sbuf_line: get a line of text from the scratch file; return pointer
+ to the text */
+char *
+get_sbuf_line(line_t *lp)
+{
+ static char *sfbuf = NULL; /* buffer */
+ static int sfbufsz = 0; /* buffer size */
+
+ int len, ct;
+
+ if (lp == &buffer_head)
+ return NULL;
+ seek_write = 1; /* force seek on write */
+ /* out of position */
+ if (sfseek != lp->seek) {
+ sfseek = lp->seek;
+ if (fseek(sfp, sfseek, SEEK_SET) < 0) {
+ fprintf(stderr, "%s\n", strerror(errno));
+ seterrmsg("cannot seek temp file");
+ return NULL;
+ }
+ }
+ len = lp->len;
+ REALLOC(sfbuf, sfbufsz, len + 1, NULL);
+ if ((ct = fread(sfbuf, sizeof(char), len, sfp)) < 0 || ct != len) {
+ fprintf(stderr, "%s\n", strerror(errno));
+ seterrmsg("cannot read temp file");
+ return NULL;
+ }
+ sfseek += len; /* update file position */
+ sfbuf[len] = '\0';
+ return sfbuf;
+}
+
+
+/* put_sbuf_line: write a line of text to the scratch file and add a line node
+ to the editor buffer; return a pointer to the end of the text */
+char *
+put_sbuf_line(char *cs)
+{
+ line_t *lp;
+ int len, ct;
+ char *s;
+
+ if ((lp = (line_t *) malloc(sizeof(line_t))) == NULL) {
+ fprintf(stderr, "%s\n", strerror(errno));
+ seterrmsg("out of memory");
+ return NULL;
+ }
+ /* assert: cs is '\n' terminated */
+ for (s = cs; *s != '\n'; s++)
+ ;
+ if (s - cs >= LINECHARS) {
+ seterrmsg("line too long");
+ free(lp);
+ return NULL;
+ }
+ len = s - cs;
+ /* out of position */
+ if (seek_write) {
+ if (fseek(sfp, 0L, SEEK_END) < 0) {
+ fprintf(stderr, "%s\n", strerror(errno));
+ seterrmsg("cannot seek temp file");
+ free(lp);
+ return NULL;
+ }
+ sfseek = ftell(sfp);
+ seek_write = 0;
+ }
+ /* assert: SPL1() */
+ if ((ct = fwrite(cs, sizeof(char), len, sfp)) < 0 || ct != len) {
+ sfseek = -1;
+ fprintf(stderr, "%s\n", strerror(errno));
+ seterrmsg("cannot write temp file");
+ free(lp);
+ return NULL;
+ }
+ lp->len = len;
+ lp->seek = sfseek;
+ add_line_node(lp);
+ sfseek += len; /* update file position */
+ return ++s;
+}
+
+
+/* add_line_node: add a line node in the editor buffer after the current line */
+void
+add_line_node(line_t *lp)
+{
+ line_t *cp;
+
+ cp = get_addressed_line_node(current_addr); /* this get_addressed_line_node last! */
+ INSQUE(lp, cp);
+ addr_last++;
+ current_addr++;
+}
+
+
+/* get_line_node_addr: return line number of pointer */
+long
+get_line_node_addr(line_t *lp)
+{
+ line_t *cp = &buffer_head;
+ long n = 0;
+
+ while (cp != lp && (cp = cp->q_forw) != &buffer_head)
+ n++;
+ if (n && cp == &buffer_head) {
+ seterrmsg("invalid address");
+ return ERR;
+ }
+ return n;
+}
+
+
+/* get_addressed_line_node: return pointer to a line node in the editor buffer */
+line_t *
+get_addressed_line_node(long n)
+{
+ static line_t *lp = &buffer_head;
+ static long on = 0;
+
+ SPL1();
+ if (n > on) {
+ if (n <= (on + addr_last) >> 1) {
+ for (; on < n; on++)
+ lp = lp->q_forw;
+ } else {
+ lp = buffer_head.q_back;
+ for (on = addr_last; on > n; on--)
+ lp = lp->q_back;
+ }
+ } else {
+ if (n >= on >> 1) {
+ for (; on > n; on--)
+ lp = lp->q_back;
+ } else {
+ lp = &buffer_head;
+ for (on = 0; on < n; on++)
+ lp = lp->q_forw;
+ }
+ }
+ SPL0();
+ return lp;
+}
+
+
+char *sfn = NULL; /* scratch file name */
+
+/* open_sbuf: open scratch file */
+int
+open_sbuf(void)
+{
+ int u, fd;
+ const char *tmp;
+ size_t s;
+
+ isbinary = newline_added = 0;
+ fd = -1;
+ u = umask(077);
+
+ if ((tmp = getenv("TMPDIR")) == NULL)
+ tmp = _PATH_TMP;
+
+ if ((s = strlen(tmp)) == 0 || tmp[s - 1] == '/')
+ (void)asprintf(&sfn, "%sed.XXXXXX", tmp);
+ else
+ (void)asprintf(&sfn, "%s/ed.XXXXXX", tmp);
+ if (sfn == NULL) {
+ warn(NULL);
+ seterrmsg("could not allocate memory");
+ umask(u);
+ return ERR;
+ }
+
+
+ if ((fd = mkstemp(sfn)) == -1 || (sfp = fdopen(fd, "w+")) == NULL) {
+ if (fd != -1)
+ close(fd);
+ warn("%s", sfn);
+ seterrmsg("cannot open temp file");
+ umask(u);
+ return ERR;
+ }
+ umask(u);
+ return 0;
+}
+
+
+/* close_sbuf: close scratch file */
+int
+close_sbuf(void)
+{
+ if (sfp) {
+ if (fclose(sfp) < 0) {
+ fprintf(stderr, "%s: %s\n", sfn, strerror(errno));
+ seterrmsg("cannot close temp file");
+ return ERR;
+ }
+ sfp = NULL;
+ if (sfn) {
+ unlink(sfn);
+ free(sfn);
+ sfn = NULL;
+ }
+ }
+ sfseek = seek_write = 0;
+ return 0;
+}
+
+
+/* quit: remove_lines scratch file and exit */
+void
+quit(int n)
+{
+ if (sfp) {
+ fclose(sfp);
+ if (sfn) {
+ unlink(sfn);
+ free(sfn);
+ sfn = NULL;
+ }
+ }
+ exit(n);
+ /* NOTREACHED */
+}
+
+
+unsigned char ctab[256]; /* character translation table */
+
+/* init_buffers: open scratch buffer; initialize line queue */
+void
+init_buffers(void)
+{
+ int i = 0;
+
+ /* Read stdin one character at a time to avoid i/o contention
+ with shell escapes invoked by nonterminal input, e.g.,
+ ed - <<EOF
+ !cat
+ hello, world
+ EOF */
+ setbuffer(stdin, stdinbuf, 1);
+ if (open_sbuf() < 0)
+ quit(2);
+ REQUE(&buffer_head, &buffer_head);
+ for (i = 0; i < 256; i++)
+ ctab[i] = i;
+}
+
+
+/* translit_text: translate characters in a string */
+char *
+translit_text(char *s, int len, int from, int to)
+{
+ static int i = 0;
+
+ unsigned char *us;
+
+ ctab[i] = i; /* restore table to initial state */
+ ctab[i = from] = to;
+ for (us = (unsigned char *) s; len-- > 0; us++)
+ *us = ctab[*us];
+ return s;
+}
diff --git a/bin/ed/cbc.c b/bin/ed/cbc.c
new file mode 100644
index 0000000..7d20c6c
--- /dev/null
+++ b/bin/ed/cbc.c
@@ -0,0 +1,460 @@
+/* $NetBSD: cbc.c,v 1.25 2018/02/08 09:05:16 dholland Exp $ */
+
+/* cbc.c: This file contains the encryption routines for the ed line editor */
+/*-
+ * Copyright (c) 1993 The Regents of the University of California.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * from: @(#)bdes.c 5.5 (Berkeley) 6/27/91
+ */
+
+/*-
+ * Copyright (c) 1993 Andrew Moore, Talke Studio.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. All advertising materials mentioning features or use of this software
+ * must display the following acknowledgement:
+ * This product includes software developed by the University of
+ * California, Berkeley and its contributors.
+ * 4. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * from: @(#)bdes.c 5.5 (Berkeley) 6/27/91
+ */
+
+#include <sys/cdefs.h>
+#ifndef lint
+#if 0
+static char *rcsid = "@(#)cbc.c,v 1.2 1994/02/01 00:34:36 alm Exp";
+#else
+__RCSID("$NetBSD: cbc.c,v 1.25 2018/02/08 09:05:16 dholland Exp $");
+#endif
+#endif /* not lint */
+
+#include <sys/types.h>
+#include <ctype.h>
+#include <errno.h>
+#include <pwd.h>
+#ifdef DES
+#include <time.h>
+#endif
+
+#include "ed.h"
+
+
+/*
+ * Define a divisor for rand() that yields a uniform distribution in the
+ * range 0-255.
+ */
+#define RAND_DIV (((unsigned) RAND_MAX + 1) >> 8)
+
+/*
+ * BSD and System V systems offer special library calls that do
+ * block move_liness and fills, so if possible we take advantage of them
+ */
+#define MEMCPY(dest,src,len) memcpy((dest),(src),(len))
+#define MEMZERO(dest,len) memset((dest), 0, (len))
+
+/* Hide the calls to the primitive encryption routines. */
+#define DES_KEY(buf) \
+ if (des_setkey(buf)) \
+ des_error("des_setkey");
+#define DES_XFORM(buf) \
+ if (des_cipher(buf, buf, 0L, (inverse ? -1 : 1))) \
+ des_error("des_cipher");
+
+/*
+ * read/write - no error checking
+ */
+#define READ(buf, n, fp) fread(buf, sizeof(char), n, fp)
+#define WRITE(buf, n, fp) fwrite(buf, sizeof(char), n, fp)
+
+/*
+ * some things to make references easier
+ */
+typedef char Desbuf[8];
+#define CHAR(x,i) (x[i])
+#define UCHAR(x,i) (x[i])
+#define BUFFER(x) (x)
+#define UBUFFER(x) (x)
+
+#ifdef DES
+/*
+ * global variables and related macros
+ */
+
+static Desbuf ivec; /* initialization vector */
+static Desbuf pvec; /* padding vector */
+static char bits[] = { /* used to extract bits from a char */
+ '\200', '\100', '\040', '\020', '\010', '\004', '\002', '\001'
+};
+static int pflag; /* 1 to preserve parity bits */
+
+static char des_buf[8]; /* shared buffer for get_des_char/put_des_char */
+static int des_ct = 0; /* count for get_des_char/put_des_char */
+static int des_n = 0; /* index for put_des_char/get_des_char */
+#endif
+
+
+#ifdef DES
+static void des_error(const char *);
+static int hex_to_binary(int, int);
+static void expand_des_key(char *, char *);
+static void set_des_key(char *);
+static int cbc_decode(char *, FILE *);
+static int cbc_encode(char *, int, FILE *);
+#endif
+
+
+/* init_des_cipher: initialize DES */
+void
+init_des_cipher(void)
+{
+#ifdef DES
+ int i;
+
+ des_ct = des_n = 0;
+
+ /* initialize the initialization vector */
+ MEMZERO(ivec, 8);
+
+ /* initialize the padding vector */
+ srand((unsigned) time((time_t *) 0));
+ for (i = 0; i < 8; i++)
+ CHAR(pvec, i) = (char) (rand()/RAND_DIV);
+#endif
+}
+
+
+/* get_des_char: return next char in an encrypted file */
+int
+get_des_char(FILE *fp)
+{
+#ifdef DES
+ if (des_n >= des_ct) {
+ des_n = 0;
+ des_ct = cbc_decode(des_buf, fp);
+ }
+ return (des_ct > 0) ? (unsigned char) des_buf[des_n++] : EOF;
+#else
+ return EOF;
+#endif
+}
+
+
+/* put_des_char: write a char to an encrypted file; return char written */
+int
+put_des_char(int c, FILE *fp)
+{
+#ifdef DES
+ if (des_n == sizeof des_buf) {
+ des_ct = cbc_encode(des_buf, des_n, fp);
+ des_n = 0;
+ }
+ return (des_ct >= 0) ? (unsigned char) (des_buf[des_n++] = c) : EOF;
+#else
+ return EOF;
+#endif
+}
+
+
+/* flush_des_file: flush an encrypted file's output; return status */
+int
+flush_des_file(FILE *fp)
+{
+#ifdef DES
+ if (des_n == sizeof des_buf) {
+ des_ct = cbc_encode(des_buf, des_n, fp);
+ des_n = 0;
+ }
+ return (des_ct >= 0 && cbc_encode(des_buf, des_n, fp) >= 0) ? 0 : EOF;
+#else
+ return EOF;
+#endif
+}
+
+#ifdef DES
+/*
+ * get keyword from tty or stdin
+ */
+int
+get_keyword(void)
+{
+ char *p; /* used to obtain the key */
+ Desbuf msgbuf; /* I/O buffer */
+
+ /*
+ * get the key
+ */
+ if (*(p = getpass("Enter key: "))) {
+
+ /*
+ * copy it, nul-padded, into the key area
+ */
+ expand_des_key(BUFFER(msgbuf), p);
+ MEMZERO(p, _PASSWORD_LEN);
+ set_des_key(msgbuf);
+ MEMZERO(msgbuf, sizeof msgbuf);
+ return 1;
+ }
+ return 0;
+}
+
+
+/*
+ * print a warning message and, possibly, terminate
+ */
+static void
+des_error(const char *s /* the message */)
+{
+ seterrmsg("%s", s ? s : strerror(errno));
+}
+
+/*
+ * map a hex character to an integer
+ */
+static int
+hex_to_binary(int c /* char to be converted */,
+ int radix /* base (2 to 16) */)
+{
+ switch(c) {
+ case '0': return(0x0);
+ case '1': return(0x1);
+ case '2': return(radix > 2 ? 0x2 : -1);
+ case '3': return(radix > 3 ? 0x3 : -1);
+ case '4': return(radix > 4 ? 0x4 : -1);
+ case '5': return(radix > 5 ? 0x5 : -1);
+ case '6': return(radix > 6 ? 0x6 : -1);
+ case '7': return(radix > 7 ? 0x7 : -1);
+ case '8': return(radix > 8 ? 0x8 : -1);
+ case '9': return(radix > 9 ? 0x9 : -1);
+ case 'A': case 'a': return(radix > 10 ? 0xa : -1);
+ case 'B': case 'b': return(radix > 11 ? 0xb : -1);
+ case 'C': case 'c': return(radix > 12 ? 0xc : -1);
+ case 'D': case 'd': return(radix > 13 ? 0xd : -1);
+ case 'E': case 'e': return(radix > 14 ? 0xe : -1);
+ case 'F': case 'f': return(radix > 15 ? 0xf : -1);
+ }
+ /*
+ * invalid character
+ */
+ return(-1);
+}
+
+/*
+ * convert the key to a bit pattern
+ */
+static void
+expand_des_key(char *obuf /* bit pattern */, char *inbuf /* the key itself */)
+{
+ int i, j; /* counter in a for loop */
+ int nbuf[64]; /* used for hex/key translation */
+
+ /*
+ * leading '0x' or '0X' == hex key
+ */
+ if (inbuf[0] == '0' && (inbuf[1] == 'x' || inbuf[1] == 'X')) {
+ inbuf = &inbuf[2];
+ /*
+ * now translate it, bombing on any illegal hex digit
+ */
+ for (i = 0; i < 16 && inbuf[i]; i++)
+ if ((nbuf[i] = hex_to_binary((int) inbuf[i], 16)) == -1)
+ des_error("bad hex digit in key");
+ while (i < 16)
+ nbuf[i++] = 0;
+ for (i = 0; i < 8; i++)
+ obuf[i] =
+ ((nbuf[2*i]&0xf)<<4) | (nbuf[2*i+1]&0xf);
+ /* preserve parity bits */
+ pflag = 1;
+ return;
+ }
+ /*
+ * leading '0b' or '0B' == binary key
+ */
+ if (inbuf[0] == '0' && (inbuf[1] == 'b' || inbuf[1] == 'B')) {
+ inbuf = &inbuf[2];
+ /*
+ * now translate it, bombing on any illegal binary digit
+ */
+ for (i = 0; i < 16 && inbuf[i]; i++)
+ if ((nbuf[i] = hex_to_binary((int) inbuf[i], 2)) == -1)
+ des_error("bad binary digit in key");
+ while (i < 64)
+ nbuf[i++] = 0;
+ for (i = 0; i < 8; i++)
+ for (j = 0; j < 8; j++)
+ obuf[i] = (obuf[i]<<1)|nbuf[8*i+j];
+ /* preserve parity bits */
+ pflag = 1;
+ return;
+ }
+ /*
+ * no special leader -- ASCII
+ */
+ (void)strncpy(obuf, inbuf, 8);
+}
+
+/*****************
+ * DES FUNCTIONS *
+ *****************/
+/*
+ * This sets the DES key and (if you're using the deszip version)
+ * the direction of the transformation. This uses the Sun
+ * to map the 64-bit key onto the 56 bits that the key schedule
+ * generation routines use: the old way, which just uses the user-
+ * supplied 64 bits as is, and the new way, which resets the parity
+ * bit to be the same as the low-order bit in each character. The
+ * new way generates a greater variety of key schedules, since many
+ * systems set the parity (high) bit of each character to 0, and the
+ * DES ignores the low order bit of each character.
+ */
+static void
+set_des_key(Desbuf buf /* key block */)
+{
+ int i, j; /* counter in a for loop */
+ int par; /* parity counter */
+
+ /*
+ * if the parity is not preserved, flip it
+ */
+ if (!pflag) {
+ for (i = 0; i < 8; i++) {
+ par = 0;
+ for (j = 1; j < 8; j++)
+ if ((bits[j]&UCHAR(buf, i)) != 0)
+ par++;
+ if ((par&01) == 01)
+ UCHAR(buf, i) = UCHAR(buf, i)&0177;
+ else
+ UCHAR(buf, i) = (UCHAR(buf, i)&0177)|0200;
+ }
+ }
+
+ DES_KEY(UBUFFER(buf));
+}
+
+
+/*
+ * This encrypts using the Cipher Block Chaining mode of DES
+ */
+static int
+cbc_encode(char *msgbuf, int n, FILE *fp)
+{
+ int inverse = 0; /* 0 to encrypt, 1 to decrypt */
+
+ /*
+ * do the transformation
+ */
+ if (n == 8) {
+ for (n = 0; n < 8; n++)
+ CHAR(msgbuf, n) ^= CHAR(ivec, n);
+ DES_XFORM(UBUFFER(msgbuf));
+ MEMCPY(BUFFER(ivec), BUFFER(msgbuf), 8);
+ return WRITE(BUFFER(msgbuf), 8, fp);
+ }
+ /*
+ * at EOF or last block -- in either case, the last byte contains
+ * the character representation of the number of bytes in it
+ */
+/*
+ MEMZERO(msgbuf + n, 8 - n);
+*/
+ /*
+ * Pad the last block randomly
+ */
+ (void)MEMCPY(BUFFER(msgbuf + n), BUFFER(pvec), 8 - n);
+ CHAR(msgbuf, 7) = n;
+ for (n = 0; n < 8; n++)
+ CHAR(msgbuf, n) ^= CHAR(ivec, n);
+ DES_XFORM(UBUFFER(msgbuf));
+ return WRITE(BUFFER(msgbuf), 8, fp);
+}
+
+/*
+ * This decrypts using the Cipher Block Chaining mode of DES
+ */
+static int
+cbc_decode(char *msgbuf /* I/O buffer */,
+ FILE *fp /* input file descriptor */)
+{
+ Desbuf inbuf; /* temp buffer for initialization vector */
+ int n; /* number of bytes actually read */
+ int c; /* used to test for EOF */
+ int inverse = 1; /* 0 to encrypt, 1 to decrypt */
+
+ if ((n = READ(BUFFER(msgbuf), 8, fp)) == 8) {
+ /*
+ * do the transformation
+ */
+ MEMCPY(BUFFER(inbuf), BUFFER(msgbuf), 8);
+ DES_XFORM(UBUFFER(msgbuf));
+ for (c = 0; c < 8; c++)
+ UCHAR(msgbuf, c) ^= UCHAR(ivec, c);
+ MEMCPY(BUFFER(ivec), BUFFER(inbuf), 8);
+ /*
+ * if the last one, handle it specially
+ */
+ if ((c = fgetc(fp)) == EOF) {
+ n = CHAR(msgbuf, 7);
+ if (n < 0 || n > 7) {
+ des_error("decryption failed (block corrupted)");
+ return EOF;
+ }
+ } else
+ (void)ungetc(c, fp);
+ return n;
+ }
+ if (n > 0)
+ des_error("decryption failed (incomplete block)");
+ else if (n < 0)
+ des_error("cannot read file");
+ return EOF;
+}
+#endif /* DES */
diff --git a/bin/ed/ed.1 b/bin/ed/ed.1
new file mode 100644
index 0000000..025dab4
--- /dev/null
+++ b/bin/ed/ed.1
@@ -0,0 +1,979 @@
+.\" $NetBSD: ed.1,v 1.35 2018/04/09 06:57:01 wiz Exp $
+.\" $OpenBSD: ed.1,v 1.42 2003/07/27 13:25:43 jmc Exp $
+.\"
+.\" Copyright (c) 1993 Andrew Moore, Talke Studio.
+.\" All rights reserved.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\" notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must reproduce the above copyright
+.\" notice, this list of conditions and the following disclaimer in the
+.\" documentation and/or other materials provided with the distribution.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+.\" ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+.\" SUCH DAMAGE.
+.\"
+.Dd April 5, 2018
+.Dt ED 1
+.Os
+.Sh NAME
+.Nm ed
+.Nd text editor
+.Sh SYNOPSIS
+.Nm
+.Op Fl
+.Op Fl ESsx
+.Op Fl p Ar string
+.Op Ar file
+.Sh DESCRIPTION
+.Nm
+is a line-oriented text editor.
+It is used to create, display, modify, and otherwise manipulate text files.
+If invoked with a
+.Ar file
+argument, then a copy of
+.Ar file
+is read into the editor's buffer.
+Changes are made to this copy and not directly to
+.Ar file
+itself.
+Upon quitting
+.Nm ,
+any changes not explicitly saved with a
+.Ic w
+command are lost.
+.Pp
+Editing is done in two distinct modes:
+.Em command
+and
+.Em input .
+When first invoked,
+.Nm
+is in command mode.
+In this mode, commands are read from the standard input and
+executed to manipulate the contents of the editor buffer.
+.Pp
+A typical command might look like:
+.Bd -literal -offset indent
+,s/old/new/g
+.Ed
+.Pp
+which replaces all occurrences of the string
+.Pa old
+with
+.Pa new .
+.Pp
+When an input command, such as
+.Ic a
+(append),
+.Ic i
+(insert),
+or
+.Ic c
+(change) is given,
+.Nm
+enters input mode.
+This is the primary means of adding text to a file.
+In this mode, no commands are available;
+instead, the standard input is written directly to the editor buffer.
+Lines consist of text up to and including a newline character.
+Input mode is terminated by entering a single period
+.Pq Ql \&.
+on a line.
+.Pp
+All
+.Nm
+commands operate on whole lines or ranges of lines; e.g.,
+the
+.Ic d
+command deletes lines; the
+.Ic m
+command moves lines, and so on.
+It is possible to modify only a portion of a line by means of replacement,
+as in the example above.
+However, even here, the
+.Ic s
+command is applied to whole lines at a time.
+.Pp
+In general,
+.Nm
+commands consist of zero or more line addresses, followed by a single
+character command and possibly additional parameters; i.e.,
+commands have the structure:
+.Bd -literal -offset indent
+[address [,address]]command[parameters]
+.Ed
+.Pp
+The address(es) indicate the line or range of lines to be affected by the
+command.
+If fewer addresses are given than the command accepts, then
+default addresses are supplied.
+.Pp
+The options are as follows:
+.Bl -tag -width Ds
+.It Fl
+Same as the
+.Fl s
+option (deprecated).
+.It Fl E
+Enables the use of extended regular expressions instead of the basic
+regular expressions that are normally used.
+.It Fl p Ar string
+Specifies a command prompt.
+This may be toggled on and off with the
+.Ic P
+command.
+.It Fl S
+Disables using of the
+.Ic \&!
+command (executing a subshell).
+Intended to be used by batch jobs like
+.Xr patch 1 .
+.It Fl s
+Suppress diagnostics.
+This should be used if
+.Nm
+standard input is from a script.
+.It Fl x
+Prompt for an encryption key to be used in subsequent reads and writes
+(see the
+.Ic x
+command).
+.It Ar file
+Specifies the name of a file to read.
+If
+.Ar file
+is prefixed with a
+bang
+.Pq Ql \&! ,
+then it is interpreted as a shell command.
+In this case, what is read is the standard output of
+.Ar file
+executed via
+.Xr sh 1 .
+To read a file whose name begins with a bang, prefix the
+name with a backslash
+.Pq Ql \e .
+The default filename is set to
+.Ar file
+only if it is not prefixed with a bang.
+.El
+.Ss LINE ADDRESSING
+An address represents the number of a line in the buffer.
+.Nm
+maintains a
+.Em current address
+which is typically supplied to commands as the default address
+when none is specified.
+When a file is first read, the current address is set to the last line
+of the file.
+In general, the current address is set to the last line affected by a command.
+.Pp
+A line address is
+constructed from one of the bases in the list below, optionally followed
+by a numeric offset.
+The offset may include any combination of digits, operators (i.e.,
+.Sq + ,
+.Sq - ,
+and
+.Sq ^ ) ,
+and whitespace.
+Addresses are read from left to right, and their values are computed
+relative to the current address.
+.Pp
+One exception to the rule that addresses represent line numbers is the
+address
+.Em 0
+(zero).
+This means
+.Dq before the first line ,
+and is legal wherever it makes sense.
+.Pp
+An address range is two addresses separated either by a comma or semi-colon.
+The value of the first address in a range cannot exceed the
+value of the second.
+If only one address is given in a range,
+then the second address is set to the given address.
+If an
+.Em n Ns No -tuple
+of addresses is given where
+.Em n > 2 ,
+then the corresponding range is determined by the last two addresses in the
+.Em n Ns No -tuple.
+If only one address is expected, then the last address is used.
+.Pp
+Each address in a comma-delimited range is interpreted relative to the
+current address.
+In a semi-colon-delimited range, the first address is
+used to set the current address, and the second address is interpreted
+relative to the first.
+.Pp
+The following address symbols are recognized:
+.Bl -tag -width Ds
+.It Em \&.
+The current line (address) in the buffer.
+.It Em $
+The last line in the buffer.
+.It Em n
+The
+.Em n Ns No th
+line in the buffer where
+.Em n
+is a number in the range
+.Em [0,$] .
+.It Em - No or Em ^
+The previous line.
+This is equivalent to
+.Em -1
+and may be repeated with cumulative effect.
+.It Em -n No or Em ^n
+The
+.Em n Ns No th
+previous line, where
+.Em n
+is a non-negative number.
+.It Em +
+The next line.
+This is equivalent to
+.Em +1
+and may be repeated with cumulative effect.
+.It Em +n
+The
+.Em n Ns No th
+next line, where
+.Em n
+is a non-negative number.
+.It Em whitespace Em n
+.Em whitespace
+followed by a number
+.Em n
+is interpreted as
+.Sq Em +n .
+.It Em \&, No or Em %
+The first through last lines in the buffer.
+This is equivalent to the address range
+.Em 1,$ .
+.It Em \&;
+The current through last lines in the buffer.
+This is equivalent to the address range
+.Em .,$ .
+.It Em / Ns Ar re Ns Em /
+The next line containing the regular expression
+.Ar re .
+The search wraps to the beginning of the buffer and continues down to the
+current line, if necessary.
+.Em //
+repeats the last search.
+.It Em \&? Ns Ar re Ns Em \&?
+The previous line containing the regular expression
+.Ar re .
+The search wraps to the end of the buffer and continues up to the
+current line, if necessary.
+.Em ??
+repeats the last search.
+.It Em \&\' Ns Ar lc
+The line previously marked by a
+.Ic k
+(mark) command, where
+.Ar lc
+is a lower case letter.
+.El
+.Ss REGULAR EXPRESSIONS
+Regular expressions are patterns used in selecting text.
+For example, the
+.Nm
+command
+.Bd -literal -offset indent
+g/string/
+.Ed
+.Pp
+prints all lines containing
+.Em string .
+Regular expressions are also used by the
+.Ic s
+command for selecting old text to be replaced with new.
+.Pp
+In addition to specifying string literals, regular expressions can
+represent classes of strings.
+Strings thus represented are said to be matched by the
+corresponding regular expression.
+If it is possible for a regular expression to match several strings in
+a line, then the leftmost longest match is the one selected.
+.Pp
+The following symbols are used in constructing regular expressions:
+.Bl -tag -width Dsasdfsd
+.It Em c
+Any character
+.Em c
+not listed below, including
+.Sq { ,
+.Sq } ,
+.Sq \&( ,
+.Sq \&) ,
+.Sq < ,
+and
+.Sq >
+matches itself.
+.It Em \ec
+Any backslash-escaped character
+.Em c ,
+except for
+.Sq { ,
+.Sq } ,
+.Sq \&( ,
+.Sq \&) ,
+.Sq < ,
+and
+.Sq >
+matches itself.
+.It Em \&.
+Matches any single character.
+.It Em [char-class]
+Matches any single character in the character class
+.Em char-class .
+See
+.Sx CHARACTER CLASSES
+below for further information.
+.It Em [^char-class]
+Matches any single character, other than newline, not in the
+character class
+.Em char-class .
+.It Em ^
+If
+.Em ^
+is the first character of a regular expression, then it
+anchors the regular expression to the beginning of a line.
+Otherwise, it matches itself.
+.It Em $
+If
+.Em $
+is the last character of a regular expression,
+it anchors the regular expression to the end of a line.
+Otherwise, it matches itself.
+.It Em \e<
+Anchors the single character regular expression or subexpression
+immediately following it to the beginning of a word.
+(This may not be available.)
+.It Em \e>
+Anchors the single character regular expression or subexpression
+immediately following it to the end of a word.
+(This may not be available.)
+.It Em \e( Ns Ar re Ns Em \e)
+Defines a subexpression
+.Ar re .
+Subexpressions may be nested.
+A subsequent backreference of the form
+.Em \en ,
+where
+.Em n
+is a number in the range [1,9], expands to the text matched by the
+.Em n Ns No th
+subexpression.
+For example, the regular expression
+.Em \e(.*\e)\e1
+matches any string consisting of identical adjacent substrings.
+Subexpressions are ordered relative to their left delimiter.
+.It Em *
+Matches the single character regular expression or subexpression
+immediately preceding it zero or more times.
+If
+.Em *
+is the first character of a regular expression or subexpression,
+then it matches itself.
+The
+.Em *
+operator sometimes yields unexpected results.
+For example, the regular expression
+.Em b*
+matches the beginning of the string
+.Em abbb
+(as opposed to the substring
+.Em bbb ) ,
+since a null match is the only leftmost match.
+.Sm off
+.It Em \e{ No n,m Em \e}\ \e{ No n, Em \e}\ \& Em \e{ No n Em \e}
+.Sm on
+Matches the single character regular expression or subexpression
+immediately preceding it at least
+.Em n
+and at most
+.Em m
+times.
+If
+.Em m
+is omitted, then it matches at least
+.Em n
+times.
+If the comma is also omitted, then it matches exactly
+.Em n
+times.
+.El
+.Pp
+Additional regular expression operators may be defined depending on the
+particular
+.Xr regex 3
+implementation.
+.Ss CHARACTER CLASSES
+A character class specifies a set of characters.
+It is written within square brackets
+.Pq []
+and in its most basic form contains just the characters in the set.
+.Pp
+To include a
+.Sq \&]
+in a character class, it must be the first character.
+A range of characters may be specified by separating the end characters
+of the range with a
+.Sq \&- ,
+e.g.,
+.Sq a-z
+specifies the lower case characters.
+.Pp
+The following literals can also be used within character classes as
+shorthand for particular sets of characters:
+.Bl -tag -offset indent -compact -width [:blahblah:]
+.It [:alnum:]
+Alphanumeric characters.
+.It [:cntrl:]
+Control characters.
+.It [:lower:]
+Lowercase alphabetic characters.
+.It [:space:]
+Whitespace (space, tab, newline, form feed, etc.)
+.It [:alpha:]
+Alphabetic characters.
+.It [:digit:]
+Numeric characters (digits).
+.It [:print:]
+Printable characters.
+.It [:upper:]
+Uppercase alphabetic characters.
+.It [:blank:]
+Blank characters (space and tab).
+.It [:graph:]
+Graphical characters (printing nonblank characters).
+.It [:punct:]
+Punctuation characters.
+.It [:xdigit:]
+Hexadecimal digits.
+.El
+If
+.Sq \&-
+appears as the first or last character of a character class, then
+it matches itself.
+All other characters in a character class match themselves.
+.Pp
+Patterns in
+a character class
+of the form
+.Em [.col-elm.]
+or
+.Em [=col-elm=]
+where
+.Em col-elm
+is a
+.Em collating element
+are interpreted according to the locale
+.\" .Xr locale 5
+(not currently supported).
+See
+.Xr regex 3
+for an explanation of these constructs.
+.Ss COMMANDS
+All
+.Nm
+commands are single characters, though some require additional parameters.
+If a command's parameters extend over several lines, then
+each line except for the last must be terminated with a backslash
+.Pq Ql \e .
+.Pp
+In general, at most one command is allowed per line.
+However, most commands accept a print suffix, which is any of
+.Ic p
+(print),
+.Ic l
+(list),
+or
+.Ic n
+(enumerate), to print the last line affected by the command.
+.Pp
+An interrupt (typically ^C) has the effect of aborting the current command
+and returning the editor to command mode.
+.Pp
+.Nm
+recognizes the following commands.
+The commands are shown together with
+the default address or address range supplied if none is
+specified (in parentheses), and other possible arguments on the right.
+.Bl -tag -width Dxxs
+.It (.) Ns Ic a
+Appends text to the buffer after the addressed line.
+Text is entered in input mode.
+The current address is set to last line entered.
+.It (.,.) Ns Ic c
+Changes lines in the buffer.
+The addressed lines are deleted from the buffer,
+and text is appended in their place.
+Text is entered in input mode.
+The current address is set to last line entered.
+.It (.,.) Ns Ic d
+Deletes the addressed lines from the buffer.
+If there is a line after the deleted range, then the current address is set
+to this line.
+Otherwise the current address is set to the line before the deleted range.
+.It Ic e Ar file
+Edits
+.Ar file ,
+and sets the default filename.
+If
+.Ar file
+is not specified, then the default filename is used.
+Any lines in the buffer are deleted before the new file is read.
+The current address is set to the last line read.
+.It Ic e Ar !command
+Edits the standard output of
+.Ar command ,
+(see
+.Ic \&! Ar command
+below).
+The default filename is unchanged.
+Any lines in the buffer are deleted before the output of
+.Em command
+is read.
+The current address is set to the last line read.
+.It Ic E Ar file
+Edits
+.Ar file
+unconditionally.
+This is similar to the
+.Ic e
+command, except that unwritten changes are discarded without warning.
+The current address is set to the last line read.
+.It Ic f Ar file
+Sets the default filename to
+.Ar file .
+If
+.Ar file
+is not specified, then the default unescaped filename is printed.
+.It (1,$) Ns Ic g Ns Ar /re/command-list
+Applies
+.Ar command-list
+to each of the addressed lines matching a regular expression
+.Ar re .
+The current address is set to the line currently matched before
+.Ar command-list
+is executed.
+At the end of the
+.Ic g
+command, the current address is set to the last line affected by
+.Ar command-list .
+.Pp
+Each command in
+.Ar command-list
+must be on a separate line,
+and every line except for the last must be terminated by a backslash
+.Pq Sq \e .
+Any commands are allowed, except for
+.Ic g ,
+.Ic G ,
+.Ic v ,
+and
+.Ic V .
+A newline alone in
+.Ar command-list
+is equivalent to a
+.Ic p
+command.
+.It (1,$) Ns Ic G Ns Ar /re/
+Interactively edits the addressed lines matching a regular expression
+.Ar re .
+For each matching line, the line is printed, the current address is set,
+and the user is prompted to enter a
+.Ar command-list .
+At the end of the
+.Ic G
+command, the current address is set to the last line affected by (the last)
+.Ar command-list .
+.Pp
+The format of
+.Ar command-list
+is the same as that of the
+.Ic g
+command.
+A newline alone acts as a null command list.
+A single
+.Sq &
+repeats the last non-null command list.
+.It Ic H
+Toggles the printing of error explanations.
+By default, explanations are not printed.
+It is recommended that
+.Nm
+scripts begin with this command to aid in debugging.
+.It Ic h
+Prints an explanation of the last error.
+.It (.) Ns Ic i
+Inserts text in the buffer before the current line.
+Text is entered in input mode.
+The current address is set to the last line entered.
+.It (.,.+1) Ns Ic j
+Joins the addressed lines.
+The addressed lines are deleted from the buffer and replaced by a single
+line containing their joined text.
+The current address is set to the resultant line.
+.It (.) Ns Ic k Ns Ar lc
+Marks a line with a lower case letter
+.Ar lc .
+The line can then be addressed as
+.Ar \&'lc
+(i.e., a single quote followed by
+.Ar lc )
+in subsequent commands.
+The mark is not cleared until the line is deleted or otherwise modified.
+.It (.,.) Ns Ic l
+Prints the addressed lines unambiguously.
+If a single line fills more than one screen (as might be the case
+when viewing a binary file, for instance), a
+.Dq --More--
+prompt is printed on the last line.
+.Nm
+waits until the RETURN key is pressed before displaying the next screen.
+The current address is set to the last line printed.
+.It (.,.) Ns Ic m Ns No (.)
+Moves lines in the buffer.
+The addressed lines are moved to after the
+right-hand destination address, which may be the address
+.Em 0
+(zero).
+The current address is set to the last line moved.
+.It (.,.) Ns Ic n
+Prints the addressed lines along with their line numbers.
+The current address is set to the last line printed.
+.It (.,.) Ns Ic p
+Prints the addressed lines.
+The current address is set to the last line printed.
+.It Ic P
+Toggles the command prompt on and off.
+Unless a prompt was specified with the command-line option
+.Fl p Ar string ,
+the command prompt is by default turned off.
+.It Ic q
+Quits
+.Nm .
+.It Ic Q
+Quits
+.Nm
+unconditionally.
+This is similar to the
+.Ic q
+command, except that unwritten changes are discarded without warning.
+.It ($) Ns Ic r Ar file
+Reads
+.Ar file
+to after the addressed line.
+If
+.Ar file
+is not specified, then the default filename is used.
+If there was no default filename prior to the command,
+then the default filename is set to
+.Ar file .
+Otherwise, the default filename is unchanged.
+The current address is set to the last line read.
+.It ($) Ns Ic r Ar !command
+Reads to after the addressed line the standard output of
+.Ar command ,
+(see the
+.Ic \&!
+command below).
+The default filename is unchanged.
+The current address is set to the last line read.
+.Sm off
+.It (.,.) Ic s Ar /re/replacement/ , \ (.,.) \
+Ic s Ar /re/replacement/ Em g , Ar \ (.,.) \
+Ic s Ar /re/replacement/ Em n
+.Sm on
+Replaces text in the addressed lines matching a regular expression
+.Ar re
+with
+.Ar replacement .
+By default, only the first match in each line is replaced.
+If the
+.Em g
+(global) suffix is given, then every match to be replaced.
+The
+.Em n
+suffix, where
+.Em n
+is a positive number, causes only the
+.Em n Ns No th
+match to be replaced.
+It is an error if no substitutions are performed on any of the addressed
+lines.
+The current address is set the last line affected.
+.Pp
+.Ar re
+and
+.Ar replacement
+may be delimited by any character other than space and newline
+(see the
+.Ic s
+command below).
+If one or two of the last delimiters is omitted, then the last line
+affected is printed as though the print suffix
+.Em p
+were specified.
+.Pp
+An unescaped
+.Ql &
+in
+.Ar replacement
+is replaced by the currently matched text.
+The character sequence
+.Em \em ,
+where
+.Em m
+is a number in the range [1,9], is replaced by the
+.Em m Ns No th
+backreference expression of the matched text.
+If
+.Ar replacement
+consists of a single
+.Ql % ,
+then
+.Ar replacement
+from the last substitution is used.
+Newlines may be embedded in
+.Ar replacement
+if they are escaped with a backslash
+.Pq Ql \e .
+.It (.,.) Ns Ic s
+Repeats the last substitution.
+This form of the
+.Ic s
+command accepts a count suffix
+.Em n ,
+or any combination of the characters
+.Em r ,
+.Em g ,
+and
+.Em p .
+If a count suffix
+.Em n
+is given, then only the
+.Em n Ns No th
+match is replaced.
+The
+.Em r
+suffix causes
+the regular expression of the last search to be used instead of
+that of the last substitution.
+The
+.Em g
+suffix toggles the global suffix of the last substitution.
+The
+.Em p
+suffix toggles the print suffix of the last substitution.
+The current address is set to the last line affected.
+.It (.,.) Ns Ic t Ns No (.)
+Copies (i.e., transfers) the addressed lines to after the right-hand
+destination address, which may be the address
+.Em 0
+(zero).
+The current address is set to the last line copied.
+.It Ic u
+Undoes the last command and restores the current address
+to what it was before the command.
+The global commands
+.Ic g ,
+.Ic G ,
+.Ic v ,
+and
+.Ic V
+are treated as a single command by undo.
+.Ic u
+is its own inverse.
+.It (1,$) Ns Ic v Ns Ar /re/command-list
+Applies
+.Ar command-list
+to each of the addressed lines not matching a regular expression
+.Ar re .
+This is similar to the
+.Ic g
+command.
+.It (1,$) Ns Ic V Ns Ar /re/
+Interactively edits the addressed lines not matching a regular expression
+.Ar re .
+This is similar to the
+.Ic G
+command.
+.It (1,$) Ns Ic w Ar file
+Writes the addressed lines to
+.Ar file .
+Any previous contents of
+.Ar file
+are lost without warning.
+If there is no default filename, then the default filename is set to
+.Ar file ,
+otherwise it is unchanged.
+If no filename is specified, then the default filename is used.
+The current address is unchanged.
+.It (1,$) Ns Ic wq Ar file
+Writes the addressed lines to
+.Ar file ,
+and then executes a
+.Ic q
+command.
+.It (1,$) Ns Ic w Ar !command
+Writes the addressed lines to the standard input of
+.Ar command ,
+(see the
+.Ic \&!
+command below).
+The default filename and current address are unchanged.
+.It (1,$) Ns Ic W Ar file
+Appends the addressed lines to the end of
+.Ar file .
+This is similar to the
+.Ic w
+command, except that the previous contents of file are not clobbered.
+The current address is unchanged.
+.It Ic x
+Prompts for an encryption key which is used in subsequent reads and writes.
+If a newline alone is entered as the key, then encryption is turned off.
+Otherwise, echoing is disabled while a key is read.
+Encryption/decryption is done using the
+.Xr bdes 1
+algorithm.
+.It (.+1) Ns Ic z Ns Ar n
+Scrolls
+.Ar n
+lines at a time starting at addressed line.
+If
+.Ar n
+is not specified, then the current window size is used.
+The current address is set to the last line printed.
+.It ($) Ns Ic =
+Prints the line number of the addressed line.
+.It (.+1) Ns Ic newline
+Prints the addressed line, and sets the current address to that line.
+.It Ic \&! Ns Ar command
+Executes
+.Ar command
+via
+.Xr sh 1 .
+If the first character of
+.Ar command
+is
+.Ic \&! ,
+then it is replaced by text of the previous
+.Ic !command .
+.Nm
+does not process
+.Ar command
+for
+.Sq \e
+(backslash) escapes.
+However, an unescaped
+.Sq %
+is replaced by the default filename.
+When the shell returns from execution, a
+.Sq \&!
+is printed to the standard output.
+The current line is unchanged.
+.El
+.Sh LIMITATIONS
+.Nm
+processes
+.Em file
+arguments for backslash escapes, i.e., in a filename,
+any characters preceded by a backslash
+.Pq Ql \e
+are interpreted literally.
+.Pp
+If a text (non-binary) file is not terminated by a newline character,
+then
+.Nm
+appends one on reading/writing it.
+In the case of a binary file,
+.Nm
+does not append a newline on reading/writing.
+.Sh ENVIRONMENT
+.Bl -tag -width iTMPDIR
+.It Ev TMPDIR
+The location used to store temporary files.
+.El
+.Sh FILES
+.Bl -tag -width /tmp/ed.* -compact
+.It Pa /tmp/ed.*
+buffer file
+.It Pa ed.hup
+where
+.Nm
+attempts to write the buffer if the terminal hangs up
+.El
+.Sh DIAGNOSTICS
+When an error occurs,
+.Nm
+prints a
+.Dq \&?
+and either returns to command mode or exits if its input is from a script.
+An explanation of the last error can be printed with the
+.Ic h
+(help) command.
+.Pp
+Since the
+.Ic g
+(global) command masks any errors from failed searches and substitutions,
+it can be used to perform conditional operations in scripts; e.g.,
+.Bd -literal -offset indent
+g/old/s//new/
+.Ed
+.Pp
+replaces any occurrences of
+.Em old
+with
+.Em new .
+.Pp
+If the
+.Ic u
+(undo) command occurs in a global command list, then
+the command list is executed only once.
+.Pp
+If diagnostics are not disabled, attempting to quit
+.Nm
+or edit another file before writing a modified buffer results in an error.
+If the command is entered a second time, it succeeds,
+but any changes to the buffer are lost.
+.Sh SEE ALSO
+.Xr bdes 1 ,
+.Xr patch 1 ,
+.Xr sed 1 ,
+.Xr sh 1 ,
+.Xr vi 1 ,
+.Xr regex 3
+.Pp
+USD:09-10
+.Rs
+.%A B. W. Kernighan
+.%A P. J. Plauger
+.%B Software Tools in Pascal
+.%I Addison-Wesley
+.%D 1981
+.Re
+.Sh HISTORY
+An
+.Nm
+command appeared in
+.At v1 .
diff --git a/bin/ed/ed.h b/bin/ed/ed.h
new file mode 100644
index 0000000..4f397c4
--- /dev/null
+++ b/bin/ed/ed.h
@@ -0,0 +1,265 @@
+/* $NetBSD: ed.h,v 1.38 2019/01/04 19:13:58 maya Exp $ */
+
+/* ed.h: type and constant definitions for the ed editor. */
+/*
+ * Copyright (c) 1993 Andrew Moore
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * @(#)ed.h,v 1.5 1994/02/01 00:34:39 alm Exp
+ */
+#include <sys/types.h>
+#if defined(BSD) && BSD >= 199103 || defined(__386BSD__)
+# include <sys/param.h> /* for MAXPATHLEN */
+#endif
+#include <errno.h>
+#if defined(sun) || defined(__NetBSD__) || defined(__APPLE__)
+# include <limits.h>
+#endif
+#include <regex.h>
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#define ERR (-2)
+#define EMOD (-3)
+#define FATAL (-4)
+
+#ifndef MAXPATHLEN
+# define MAXPATHLEN 255 /* _POSIX_PATH_MAX */
+#endif
+
+#define MINBUFSZ 512 /* minimum buffer size - must be > 0 */
+#define SE_MAX 30 /* max subexpressions in a regular expression */
+#ifdef INT_MAX
+# define LINECHARS INT_MAX /* max chars per line */
+#else
+# define LINECHARS MAXINT /* max chars per line */
+#endif
+
+/* gflags */
+#define GLB 001 /* global command */
+#define GPR 002 /* print after command */
+#define GLS 004 /* list after command */
+#define GNP 010 /* enumerate after command */
+#define GSG 020 /* global substitute */
+
+typedef regex_t pattern_t;
+
+/* Line node */
+typedef struct line {
+ struct line *q_forw;
+ struct line *q_back;
+ off_t seek; /* address of line in scratch buffer */
+ int len; /* length of line */
+} line_t;
+
+
+typedef struct undo {
+
+/* type of undo nodes */
+#define UADD 0
+#define UDEL 1
+#define UMOV 2
+#define VMOV 3
+
+ int type; /* command type */
+ line_t *h; /* head of list */
+ line_t *t; /* tail of list */
+} undo_t;
+
+#ifndef max
+# define max(a,b) ((a) > (b) ? (a) : (b))
+#endif
+#ifndef min
+# define min(a,b) ((a) < (b) ? (a) : (b))
+#endif
+
+#define INC_MOD(l, k) ((l) + 1 > (k) ? 0 : (l) + 1)
+#define DEC_MOD(l, k) ((l) - 1 < 0 ? (k) : (l) - 1)
+
+/* SPL1: disable some interrupts (requires reliable signals) */
+#define SPL1() mutex++
+
+/* SPL0: enable all interrupts; check sigflags (requires reliable signals) */
+#define SPL0() \
+if (--mutex == 0) { \
+ if (sigflags & (1 << (SIGHUP - 1))) handle_hup(SIGHUP); \
+ if (sigflags & (1 << (SIGINT - 1))) handle_int(SIGINT); \
+}
+
+/* STRTOL: convert a string to long */
+#define STRTOL(i, p) { \
+ errno = 0 ; \
+ if (((i = strtol(p, &p, 10)) == LONG_MIN || i == LONG_MAX) && \
+ errno == ERANGE) { \
+ seterrmsg("number out of range"); \
+ i = 0; \
+ return ERR; \
+ } \
+}
+
+/* REALLOC: assure at least a minimum size for buffer b */
+#define REALLOC(b,n,i,err) \
+if ((i) > (n)) { \
+ int ti = (n); \
+ char *ts; \
+ SPL1(); \
+ if ((ts = (char *) realloc((b), ti += max((i), MINBUFSZ))) == NULL) { \
+ fprintf(stderr, "%s\n", strerror(errno)); \
+ seterrmsg("out of memory"); \
+ SPL0(); \
+ return err; \
+ } \
+ (n) = ti; \
+ (b) = ts; \
+ SPL0(); \
+}
+
+/* REQUE: link pred before succ */
+#define REQUE(pred, succ) (pred)->q_forw = (succ), (succ)->q_back = (pred)
+
+/* INSQUE: insert elem in circular queue after pred */
+#define INSQUE(elem, pred) \
+{ \
+ REQUE((elem), (pred)->q_forw); \
+ REQUE((pred), elem); \
+}
+
+/* remque: remove_lines elem from circular queue */
+#define REMQUE(elem) REQUE((elem)->q_back, (elem)->q_forw);
+
+/* NUL_TO_NEWLINE: overwrite ASCII NULs with newlines */
+#define NUL_TO_NEWLINE(s, l) translit_text(s, l, '\0', '\n')
+
+/* NEWLINE_TO_NUL: overwrite newlines with ASCII NULs */
+#define NEWLINE_TO_NUL(s, l) translit_text(s, l, '\n', '\0')
+
+#if defined(sun) && !defined(__SVR4)
+# define strerror(n) sys_errlist[n]
+#endif
+
+/* Local Function Declarations */
+void add_line_node(line_t *);
+int append_lines(long);
+int apply_subst_template(char *, regmatch_t *, int, int);
+int build_active_list(int);
+int check_addr_range(long, long);
+void clear_active_list(void);
+void clear_undo_stack(void);
+int close_sbuf(void);
+int copy_lines(long);
+int delete_lines(long, long);
+int display_lines(long, long, int);
+line_t *dup_line_node(line_t *);
+int exec_command(void);
+long exec_global(int, int);
+int extract_addr_range(void);
+char *extract_pattern(int);
+int extract_subst_tail(int *, long *);
+char *extract_subst_template(void);
+int filter_lines(long, long, char *);
+int flush_des_file(FILE *);
+line_t *get_addressed_line_node(long);
+pattern_t *get_compiled_pattern(void);
+int get_des_char(FILE *);
+char *get_extended_line(int *, int);
+char *get_filename(void);
+int get_keyword(void);
+long get_line_node_addr(line_t *);
+long get_matching_node_addr(pattern_t *, int);
+long get_marked_node_addr(int);
+char *get_sbuf_line(line_t *);
+int get_shell_command(void);
+int get_stream_line(FILE *);
+int get_tty_line(void);
+__dead void handle_hup(int);
+__dead void handle_int(int);
+void handle_winch(int);
+int has_trailing_escape(char *, char *);
+void init_buffers(void);
+void init_des_cipher(void);
+int is_legal_filename(char *);
+int join_lines(long, long);
+int mark_line_node(line_t *, int);
+int move_lines(long);
+line_t *next_active_node(void);
+long next_addr(void);
+int open_sbuf(void);
+char *parse_char_class(char *);
+int pop_undo_stack(void);
+undo_t *push_undo_stack(int, long, long);
+int put_des_char(int, FILE *);
+char *put_sbuf_line(char *);
+int put_stream_line(FILE *, char *, int);
+int put_tty_line(char *, int, long, int);
+__dead void quit(int);
+long read_file(char *, long);
+long read_stream(FILE *, long);
+int search_and_replace(pattern_t *, int, int);
+int set_active_node(line_t *);
+void signal_hup(int);
+void signal_int(int);
+char *strip_escapes(const char *);
+int substitute_matching_text(pattern_t *, line_t *, int, int);
+char *translit_text(char *, int, int, int);
+void unmark_line_node(line_t *);
+void unset_active_nodes(line_t *, line_t *);
+long write_file(const char *, const char *, long, long);
+long write_stream(FILE *, long, long);
+void seterrmsg(const char *, ...) __printflike(1, 2);
+
+/* global buffers */
+extern char stdinbuf[];
+extern char *ibuf;
+extern char *ibufp;
+extern int ibufsz;
+
+/* global flags */
+extern int isbinary;
+extern int isglobal;
+extern int modified;
+extern int mutex;
+extern int sigflags;
+
+/* global vars */
+extern long addr_last;
+extern long current_addr;
+extern long first_addr;
+extern int lineno;
+extern long second_addr;
+extern long rows;
+extern int cols;
+extern int scripted;
+extern int ere;
+extern int des;
+extern int newline_added; /* io.c */
+extern int patlock;
+extern char errmsg[]; /* re.c */
+extern long u_current_addr; /* undo.c */
+extern long u_addr_last; /* undo.c */
+#if defined(sun) && !defined(__SVR4)
+extern char *sys_errlist[];
+#endif
diff --git a/bin/ed/glbl.c b/bin/ed/glbl.c
new file mode 100644
index 0000000..d79675d
--- /dev/null
+++ b/bin/ed/glbl.c
@@ -0,0 +1,213 @@
+/* $NetBSD: glbl.c,v 1.10 2019/01/04 19:13:58 maya Exp $ */
+
+/* glob.c: This file contains the global command routines for the ed line
+ editor */
+/*-
+ * Copyright (c) 1993 Andrew Moore, Talke Studio.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+#ifndef lint
+#if 0
+static char *rcsid = "@(#)glob.c,v 1.1 1994/02/01 00:34:40 alm Exp";
+#else
+__RCSID("$NetBSD: glbl.c,v 1.10 2019/01/04 19:13:58 maya Exp $");
+#endif
+#endif /* not lint */
+
+#include <sys/ioctl.h>
+#include <sys/wait.h>
+
+#include "ed.h"
+
+
+/* build_active_list: add line matching a pattern to the global-active list */
+int
+build_active_list(int isgcmd)
+{
+ pattern_t *pat;
+ line_t *lp;
+ long n;
+ char *s;
+ char delimiter;
+
+ if ((delimiter = *ibufp) == ' ' || delimiter == '\n') {
+ seterrmsg("invalid pattern delimiter");
+ return ERR;
+ } else if ((pat = get_compiled_pattern()) == NULL)
+ return ERR;
+ else if (*ibufp == delimiter)
+ ibufp++;
+ clear_active_list();
+ lp = get_addressed_line_node(first_addr);
+ for (n = first_addr; n <= second_addr; n++, lp = lp->q_forw) {
+ if ((s = get_sbuf_line(lp)) == NULL)
+ return ERR;
+ if (isbinary)
+ NUL_TO_NEWLINE(s, lp->len);
+ if ((!regexec(pat, s, 0, NULL, 0)) == isgcmd &&
+ set_active_node(lp) < 0)
+ return ERR;
+ }
+ return 0;
+}
+
+
+/* exec_global: apply command list in the command buffer to the active
+ lines in a range; return command status */
+long
+exec_global(int interact, int gflag)
+{
+ static char *ocmd = NULL;
+ static int ocmdsz = 0;
+
+ line_t *lp = NULL;
+ int status;
+ int n;
+ char *cmd = NULL;
+#ifdef BACKWARDS
+ char cmdp[] = "p\n";
+
+ if (!interact) {
+ if (!strcmp(ibufp, "\n"))
+ cmd = cmdp; /* null cmd-list == `p' */
+ else if ((cmd = get_extended_line(&n, 0)) == NULL)
+ return ERR;
+ }
+#else
+ if (!interact && (cmd = get_extended_line(&n, 0)) == NULL)
+ return ERR;
+#endif
+ clear_undo_stack();
+ while ((lp = next_active_node()) != NULL) {
+ if ((current_addr = get_line_node_addr(lp)) < 0)
+ return ERR;
+ if (interact) {
+ /* print current_addr; get a command in global syntax */
+ if (display_lines(current_addr, current_addr, gflag) < 0)
+ return ERR;
+ while ((n = get_tty_line()) > 0 &&
+ ibuf[n - 1] != '\n')
+ clearerr(stdin);
+ if (n < 0)
+ return ERR;
+ else if (n == 0) {
+ seterrmsg("unexpected end-of-file");
+ return ERR;
+ } else if (n == 1 && !strcmp(ibuf, "\n"))
+ continue;
+ else if (n == 2 && !strcmp(ibuf, "&\n")) {
+ if (cmd == NULL) {
+ seterrmsg("no previous command");
+ return ERR;
+ } else cmd = ocmd;
+ } else if ((cmd = get_extended_line(&n, 0)) == NULL)
+ return ERR;
+ else {
+ REALLOC(ocmd, ocmdsz, n + 1, ERR);
+ memcpy(ocmd, cmd, n + 1);
+ cmd = ocmd;
+ }
+
+ }
+ ibufp = cmd;
+ for (; *ibufp;)
+ if ((status = extract_addr_range()) < 0 ||
+ (status = exec_command()) < 0 ||
+ (status > 0 && (status = display_lines(
+ current_addr, current_addr, status)) < 0))
+ return status;
+ }
+ return 0;
+}
+
+
+line_t **active_list; /* list of lines active in a global command */
+long active_last; /* index of last active line in active_list */
+long active_size; /* size of active_list */
+long active_ptr; /* active_list index (non-decreasing) */
+long active_ndx; /* active_list index (modulo active_last) */
+
+/* set_active_node: add a line node to the global-active list */
+int
+set_active_node(line_t *lp)
+{
+ if (active_last + 1 > active_size) {
+ int ti = active_size;
+ line_t **ts;
+ SPL1();
+ if ((ts = (line_t **) realloc(active_list,
+ (ti += MINBUFSZ) * sizeof(line_t **))) == NULL) {
+ fprintf(stderr, "%s\n", strerror(errno));
+ seterrmsg("out of memory");
+ SPL0();
+ return ERR;
+ }
+ active_size = ti;
+ active_list = ts;
+ SPL0();
+ }
+ active_list[active_last++] = lp;
+ return 0;
+}
+
+
+/* unset_active_nodes: remove a range of lines from the global-active list */
+void
+unset_active_nodes(line_t *np, line_t *mp)
+{
+ line_t *lp;
+ long i;
+
+ for (lp = np; lp != mp; lp = lp->q_forw)
+ for (i = 0; i < active_last; i++)
+ if (active_list[active_ndx] == lp) {
+ active_list[active_ndx] = NULL;
+ active_ndx = INC_MOD(active_ndx, active_last - 1);
+ break;
+ } else active_ndx = INC_MOD(active_ndx, active_last - 1);
+}
+
+
+/* next_active_node: return the next global-active line node */
+line_t *
+next_active_node(void)
+{
+ while (active_ptr < active_last && active_list[active_ptr] == NULL)
+ active_ptr++;
+ return (active_ptr < active_last) ? active_list[active_ptr++] : NULL;
+}
+
+
+/* clear_active_list: clear the global-active list */
+void
+clear_active_list(void)
+{
+ SPL1();
+ active_size = active_last = active_ptr = active_ndx = 0;
+ free(active_list);
+ active_list = NULL;
+ SPL0();
+}
diff --git a/bin/ed/io.c b/bin/ed/io.c
new file mode 100644
index 0000000..7ef23b4
--- /dev/null
+++ b/bin/ed/io.c
@@ -0,0 +1,358 @@
+/* $NetBSD: io.c,v 1.10 2014/03/23 05:06:42 dholland Exp $ */
+
+/* io.c: This file contains the i/o routines for the ed line editor */
+/*-
+ * Copyright (c) 1993 Andrew Moore, Talke Studio.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+#ifndef lint
+#if 0
+static char *rcsid = "@(#)io.c,v 1.1 1994/02/01 00:34:41 alm Exp";
+#else
+__RCSID("$NetBSD: io.c,v 1.10 2014/03/23 05:06:42 dholland Exp $");
+#endif
+#endif /* not lint */
+
+#include "ed.h"
+
+
+/* read_file: read a named file/pipe into the buffer; return line count */
+long
+read_file(char *fn, long n)
+{
+ FILE *fp;
+ long size;
+
+
+ fp = (*fn == '!') ? popen(fn + 1, "r") : fopen(strip_escapes(fn), "r");
+ if (fp == NULL) {
+ fprintf(stderr, "%s: %s\n", fn, strerror(errno));
+ seterrmsg("cannot open input file");
+ return ERR;
+ } else if ((size = read_stream(fp, n)) < 0)
+ return ERR;
+ else if (((*fn == '!') ? pclose(fp) : fclose(fp)) < 0) {
+ fprintf(stderr, "%s: %s\n", fn, strerror(errno));
+ seterrmsg("cannot close input file");
+ return ERR;
+ }
+ if (!scripted)
+ fprintf(stderr, "%lu\n", size);
+ return current_addr - n;
+}
+
+
+char *sbuf; /* file i/o buffer */
+int sbufsz; /* file i/o buffer size */
+int newline_added; /* if set, newline appended to input file */
+
+/* read_stream: read a stream into the editor buffer; return status */
+long
+read_stream(FILE *fp, long n)
+{
+ line_t *lp = get_addressed_line_node(n);
+ undo_t *up = NULL;
+ unsigned long size = 0;
+ int o_newline_added = newline_added;
+ int o_isbinary = isbinary;
+ int appended = (n == addr_last);
+ int len;
+
+ isbinary = newline_added = 0;
+ if (des)
+ init_des_cipher();
+ for (current_addr = n; (len = get_stream_line(fp)) > 0; size += len) {
+ SPL1();
+ if (put_sbuf_line(sbuf) == NULL) {
+ SPL0();
+ return ERR;
+ }
+ lp = lp->q_forw;
+ if (up)
+ up->t = lp;
+ else if ((up = push_undo_stack(UADD, current_addr,
+ current_addr)) == NULL) {
+ SPL0();
+ return ERR;
+ }
+ SPL0();
+ }
+ if (len < 0)
+ return ERR;
+ if (appended && size && o_isbinary && o_newline_added)
+ fputs("newline inserted\n", stderr);
+ else if (newline_added && (!appended || (!isbinary && !o_isbinary)))
+ fputs("newline appended\n", stderr);
+ if (isbinary && newline_added && !appended)
+ size += 1;
+ if (!size)
+ newline_added = 1;
+ newline_added = appended ? newline_added : o_newline_added;
+ isbinary = isbinary | o_isbinary;
+ if (des)
+ size += 8 - size % 8; /* adjust DES size */
+ return size;
+}
+
+
+/* get_stream_line: read a line of text from a stream; return line length */
+int
+get_stream_line(FILE *fp)
+{
+ int c;
+ int i = 0;
+
+ while (((c = des ? get_des_char(fp) : getc(fp)) != EOF || (!feof(fp) &&
+ !ferror(fp))) && c != '\n') {
+ REALLOC(sbuf, sbufsz, i + 1, ERR);
+ if (!(sbuf[i++] = c))
+ isbinary = 1;
+ }
+ REALLOC(sbuf, sbufsz, i + 2, ERR);
+ if (c == '\n')
+ sbuf[i++] = c;
+ else if (ferror(fp)) {
+ fprintf(stderr, "%s\n", strerror(errno));
+ seterrmsg("cannot read input file");
+ return ERR;
+ } else if (i) {
+ sbuf[i++] = '\n';
+ newline_added = 1;
+ }
+ sbuf[i] = '\0';
+ return (isbinary && newline_added && i) ? --i : i;
+}
+
+
+/* write_file: write a range of lines to a named file/pipe; return line count */
+long
+write_file(const char *fn, const char *mode, long n, long m)
+{
+ FILE *fp;
+ long size;
+
+ fp = (*fn == '!') ? popen(fn+1, "w") : fopen(strip_escapes(fn), mode);
+ if (fp == NULL) {
+ fprintf(stderr, "%s: %s\n", fn, strerror(errno));
+ seterrmsg("cannot open output file");
+ return ERR;
+ } else if ((size = write_stream(fp, n, m)) < 0)
+ return ERR;
+ else if (((*fn == '!') ? pclose(fp) : fclose(fp)) < 0) {
+ fprintf(stderr, "%s: %s\n", fn, strerror(errno));
+ seterrmsg("cannot close output file");
+ return ERR;
+ }
+ if (!scripted)
+ fprintf(stderr, "%lu\n", size);
+ return n ? m - n + 1 : 0;
+}
+
+
+/* write_stream: write a range of lines to a stream; return status */
+long
+write_stream(FILE *fp, long n, long m)
+{
+ line_t *lp = get_addressed_line_node(n);
+ unsigned long size = 0;
+ char *s;
+ int len;
+
+ if (des)
+ init_des_cipher();
+ for (; n && n <= m; n++, lp = lp->q_forw) {
+ if ((s = get_sbuf_line(lp)) == NULL)
+ return ERR;
+ len = lp->len;
+ if (n != addr_last || !isbinary || !newline_added)
+ s[len++] = '\n';
+ if (put_stream_line(fp, s, len) < 0)
+ return ERR;
+ size += len;
+ }
+ if (des) {
+ flush_des_file(fp); /* flush buffer */
+ size += 8 - size % 8; /* adjust DES size */
+ }
+ return size;
+}
+
+
+/* put_stream_line: write a line of text to a stream; return status */
+int
+put_stream_line(FILE *fp, char *s, int len)
+{
+ while (len--)
+ if ((des ? put_des_char(*s++, fp) : fputc(*s++, fp)) < 0) {
+ fprintf(stderr, "%s\n", strerror(errno));
+ seterrmsg("cannot write file");
+ return ERR;
+ }
+ return 0;
+}
+
+/* get_extended_line: get a an extended line from stdin */
+char *
+get_extended_line(int *sizep, int nonl)
+{
+ static char *cvbuf = NULL; /* buffer */
+ static int cvbufsz = 0; /* buffer size */
+
+ int l, n;
+ char *t = ibufp;
+
+ while (*t++ != '\n')
+ ;
+ if ((l = t - ibufp) < 2 || !has_trailing_escape(ibufp, ibufp + l - 1)) {
+ *sizep = l;
+ return ibufp;
+ }
+ *sizep = -1;
+ REALLOC(cvbuf, cvbufsz, l, NULL);
+ memcpy(cvbuf, ibufp, l);
+ *(cvbuf + --l - 1) = '\n'; /* strip trailing esc */
+ if (nonl) l--; /* strip newline */
+ for (;;) {
+ if ((n = get_tty_line()) < 0)
+ return NULL;
+ else if (n == 0 || ibuf[n - 1] != '\n') {
+ seterrmsg("unexpected end-of-file");
+ return NULL;
+ }
+ REALLOC(cvbuf, cvbufsz, l + n, NULL);
+ memcpy(cvbuf + l, ibuf, n);
+ l += n;
+ if (n < 2 || !has_trailing_escape(cvbuf, cvbuf + l - 1))
+ break;
+ *(cvbuf + --l - 1) = '\n'; /* strip trailing esc */
+ if (nonl) l--; /* strip newline */
+ }
+ REALLOC(cvbuf, cvbufsz, l + 1, NULL);
+ cvbuf[l] = '\0';
+ *sizep = l;
+ return cvbuf;
+}
+
+
+/* get_tty_line: read a line of text from stdin; return line length */
+int
+get_tty_line(void)
+{
+ int oi = 0;
+ int i = 0;
+ int c;
+
+ for (;;)
+ switch (c = getchar()) {
+ default:
+ oi = 0;
+ REALLOC(ibuf, ibufsz, i + 2, ERR);
+ if (!(ibuf[i++] = c)) isbinary = 1;
+ if (c != '\n')
+ continue;
+ lineno++;
+ ibuf[i] = '\0';
+ ibufp = ibuf;
+ return i;
+ case EOF:
+ if (ferror(stdin)) {
+ fprintf(stderr, "stdin: %s\n", strerror(errno));
+ seterrmsg("cannot read stdin");
+ clearerr(stdin);
+ ibufp = NULL;
+ return ERR;
+ } else {
+ clearerr(stdin);
+ if (i != oi) {
+ oi = i;
+ continue;
+ } else if (i)
+ ibuf[i] = '\0';
+ ibufp = ibuf;
+ return i;
+ }
+ }
+}
+
+
+
+#define ESCAPES "\a\b\f\n\r\t\v\\"
+#define ESCCHARS "abfnrtv\\"
+
+/* put_tty_line: print text to stdout */
+int
+put_tty_line(char *s, int l, long n, int gflag)
+{
+ int col = 0;
+ char *cp;
+#ifndef BACKWARDS
+ int lc = 0;
+#endif
+
+ if (gflag & GNP) {
+ printf("%ld\t", n);
+ col = 8;
+ }
+ for (; l--; s++) {
+ if ((gflag & GLS) && ++col > cols) {
+ fputs("\\\n", stdout);
+ col = 1;
+#ifndef BACKWARDS
+ if (!scripted && !isglobal && ++lc > rows) {
+ lc = 0;
+ fputs("Press <RETURN> to continue... ", stdout);
+ fflush(stdout);
+ if (get_tty_line() < 0)
+ return ERR;
+ }
+#endif
+ }
+ if (gflag & GLS) {
+ if (31 < *s && *s < 127 && *s != '\\')
+ putchar(*s);
+ else {
+ putchar('\\');
+ col++;
+ if (*s && (cp = strchr(ESCAPES, *s)) != NULL)
+ putchar(ESCCHARS[cp - ESCAPES]);
+ else {
+ putchar((((unsigned char) *s & 0300) >> 6) + '0');
+ putchar((((unsigned char) *s & 070) >> 3) + '0');
+ putchar(((unsigned char) *s & 07) + '0');
+ col += 2;
+ }
+ }
+
+ } else
+ putchar(*s);
+ }
+#ifndef BACKWARDS
+ if (gflag & GLS)
+ putchar('$');
+#endif
+ putchar('\n');
+ return 0;
+}
diff --git a/bin/ed/main.c b/bin/ed/main.c
new file mode 100644
index 0000000..f86148a
--- /dev/null
+++ b/bin/ed/main.c
@@ -0,0 +1,1433 @@
+/* $NetBSD: main.c,v 1.30 2018/06/18 14:56:24 christos Exp $ */
+
+/* main.c: This file contains the main control and user-interface routines
+ for the ed line editor. */
+/*-
+ * Copyright (c) 1993 Andrew Moore, Talke Studio.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+#ifndef lint
+__COPYRIGHT(
+"@(#) Copyright (c) 1993 Andrew Moore, Talke Studio.\
+ All rights reserved.");
+#endif /* not lint */
+
+#ifndef lint
+#if 0
+static char *rcsid = "@(#)main.c,v 1.1 1994/02/01 00:34:42 alm Exp";
+#else
+__RCSID("$NetBSD: main.c,v 1.30 2018/06/18 14:56:24 christos Exp $");
+#endif
+#endif /* not lint */
+
+/*
+ * CREDITS
+ *
+ * This program is based on the editor algorithm described in
+ * Brian W. Kernighan and P. J. Plauger's book "Software Tools
+ * in Pascal," Addison-Wesley, 1981.
+ *
+ * The buffering algorithm is attributed to Rodney Ruddock of
+ * the University of Guelph, Guelph, Ontario.
+ *
+ * The cbc.c encryption code is adapted from
+ * the bdes program by Matt Bishop of Dartmouth College,
+ * Hanover, NH.
+ *
+ */
+
+#include <sys/ioctl.h>
+#include <sys/wait.h>
+#include <termios.h>
+#include <ctype.h>
+#include <setjmp.h>
+#include <pwd.h>
+
+#include "ed.h"
+
+
+#ifdef _POSIX_SOURCE
+sigjmp_buf env;
+#else
+jmp_buf env;
+#endif
+
+/* static buffers */
+char stdinbuf[1]; /* stdin buffer */
+char *shcmd; /* shell command buffer */
+int shcmdsz; /* shell command buffer size */
+int shcmdi; /* shell command buffer index */
+char *ibuf; /* ed command-line buffer */
+int ibufsz; /* ed command-line buffer size */
+char *ibufp; /* pointer to ed command-line buffer */
+
+/* global flags */
+int des = 0; /* if set, use crypt(3) for i/o */
+int garrulous = 0; /* if set, print all error messages */
+int isbinary; /* if set, buffer contains ASCII NULs */
+int isglobal; /* if set, doing a global command */
+int modified; /* if set, buffer modified since last write */
+int mutex = 0; /* if set, signals set "sigflags" */
+int red = 0; /* if set, restrict shell/directory access */
+int ere = 0; /* if set, use extended regexes */
+int scripted = 0; /* if set, suppress diagnostics */
+int secure = 0; /* is set, ! is not allowed */
+int sigflags = 0; /* if set, signals received while mutex set */
+int sigactive = 0; /* if set, signal handlers are enabled */
+
+char old_filename[MAXPATHLEN + 1] = ""; /* default filename */
+long current_addr; /* current address in editor buffer */
+long addr_last; /* last address in editor buffer */
+int lineno; /* script line number */
+const char *prompt; /* command-line prompt */
+const char *dps = "*"; /* default command-line prompt */
+
+
+static const char usage[] = "Usage: %s [-] [-ESsx] [-p string] [name]\n";
+
+/* ed: line editor */
+int
+main(int ac, char *av[])
+{
+ int c, n;
+ long status = 0;
+ volatile int argc = ac;
+ char ** volatile argv = av;
+
+ red = (n = strlen(argv[0])) > 2 && argv[0][n - 3] == 'r';
+top:
+ while ((c = getopt(argc, argv, "p:sxES")) != -1)
+ switch(c) {
+ case 'p': /* set prompt */
+ prompt = optarg;
+ break;
+ case 's': /* run script */
+ scripted = 1;
+ break;
+ case 'x': /* use crypt */
+#ifdef DES
+ des = get_keyword();
+#else
+ fprintf(stderr, "crypt unavailable\n?\n");
+#endif
+ break;
+
+ case 'E':
+ ere = REG_EXTENDED;
+ break;
+ case 'S': /* ! is not allowed */
+ secure = 1;
+ break;
+ default:
+ fprintf(stderr, usage, getprogname());
+ exit(1);
+ /* NOTREACHED */
+ }
+ argv += optind;
+ argc -= optind;
+ if (argc && **argv == '-') {
+ scripted = 1;
+ if (argc > 1) {
+ optind = 1;
+ goto top;
+ }
+ argv++;
+ argc--;
+ }
+ /* assert: reliable signals! */
+#ifdef SIGWINCH
+ handle_winch(SIGWINCH);
+ if (isatty(0)) signal(SIGWINCH, handle_winch);
+#endif
+ signal(SIGHUP, signal_hup);
+ signal(SIGQUIT, SIG_IGN);
+ signal(SIGINT, signal_int);
+#ifdef _POSIX_SOURCE
+ if ((status = sigsetjmp(env, 1)) != 0)
+#else
+ if ((status = setjmp(env)) != 0)
+#endif
+ {
+ fputs("\n?\n", stderr);
+ seterrmsg("interrupt");
+ } else {
+ init_buffers();
+ sigactive = 1; /* enable signal handlers */
+ if (argc && **argv && is_legal_filename(*argv)) {
+ if (read_file(*argv, 0) < 0 && !isatty(0))
+ quit(2);
+ else if (**argv != '!')
+ strlcpy(old_filename, *argv,
+ sizeof(old_filename) - 2);
+ } else if (argc) {
+ fputs("?\n", stderr);
+ if (**argv == '\0')
+ seterrmsg("invalid filename");
+ if (!isatty(0))
+ quit(2);
+ }
+ }
+ for (;;) {
+ if (status < 0 && garrulous)
+ fprintf(stderr, "%s\n", errmsg);
+ if (prompt) {
+ printf("%s", prompt);
+ fflush(stdout);
+ }
+ if ((n = get_tty_line()) < 0) {
+ status = ERR;
+ continue;
+ } else if (n == 0) {
+ if (modified && !scripted) {
+ fputs("?\n", stderr);
+ seterrmsg("warning: file modified");
+ if (!isatty(0)) {
+ if (garrulous) {
+ fprintf(stderr,
+ "script, line %d: %s\n",
+ lineno, errmsg);
+ }
+ quit(2);
+ }
+ clearerr(stdin);
+ modified = 0;
+ status = EMOD;
+ continue;
+ } else
+ quit(0);
+ } else if (ibuf[n - 1] != '\n') {
+ /* discard line */
+ seterrmsg("unexpected end-of-file");
+ clearerr(stdin);
+ status = ERR;
+ continue;
+ }
+ isglobal = 0;
+ if ((status = extract_addr_range()) >= 0 &&
+ (status = exec_command()) >= 0) {
+ if (status == 0)
+ continue;
+ status = display_lines(current_addr, current_addr,
+ status);
+ if (status >= 0)
+ continue;
+ }
+ switch (status) {
+ case EOF:
+ quit(0);
+ case EMOD:
+ modified = 0;
+ fputs("?\n", stderr); /* give warning */
+ seterrmsg("warning: file modified");
+ if (!isatty(0)) {
+ if (garrulous) {
+ fprintf(stderr,
+ "script, line %d: %s\n",
+ lineno, errmsg);
+ }
+ quit(2);
+ }
+ break;
+ case FATAL:
+ if (garrulous) {
+ if (!isatty(0)) {
+ fprintf(stderr,
+ "script, line %d: %s\n",
+ lineno, errmsg);
+ } else {
+ fprintf(stderr, "%s\n", errmsg);
+ }
+ }
+ quit(3);
+ default:
+ fputs("?\n", stderr);
+ if (!isatty(0)) {
+ if (garrulous) {
+ fprintf(stderr, "script, line %d: %s\n",
+ lineno, errmsg);
+ }
+ quit(2);
+ }
+ break;
+ }
+ }
+ /* NOTREACHED */
+}
+
+long first_addr, second_addr, addr_cnt;
+
+/* extract_addr_range: get line addresses from the command buffer until an
+ illegal address is seen; return status */
+int
+extract_addr_range(void)
+{
+ long addr;
+
+ addr_cnt = 0;
+ first_addr = second_addr = current_addr;
+ while ((addr = next_addr()) >= 0) {
+ addr_cnt++;
+ first_addr = second_addr;
+ second_addr = addr;
+ if (*ibufp != ',' && *ibufp != ';')
+ break;
+ else if (*ibufp++ == ';')
+ current_addr = addr;
+ }
+ if ((addr_cnt = min(addr_cnt, 2)) == 1 || second_addr != addr)
+ first_addr = second_addr;
+ return (addr == ERR) ? ERR : 0;
+}
+
+
+#define SKIP_BLANKS() while (isspace((unsigned char)*ibufp) && *ibufp != '\n') \
+ ibufp++
+
+#define MUST_BE_FIRST() \
+ if (!first) { seterrmsg("invalid address"); return ERR; }
+
+/* next_addr: return the next line address in the command buffer */
+long
+next_addr(void)
+{
+ char *hd;
+ long addr = current_addr;
+ long n;
+ int first = 1;
+ int c;
+
+ SKIP_BLANKS();
+ for (hd = ibufp;; first = 0)
+ switch (c = *ibufp) {
+ case '+':
+ case '\t':
+ case ' ':
+ case '-':
+ case '^':
+ ibufp++;
+ SKIP_BLANKS();
+ if (isdigit((unsigned char)*ibufp)) {
+ STRTOL(n, ibufp);
+ addr += (c == '-' || c == '^') ? -n : n;
+ } else if (!isspace((unsigned char)c))
+ addr += (c == '-' || c == '^') ? -1 : 1;
+ break;
+ case '0': case '1': case '2':
+ case '3': case '4': case '5':
+ case '6': case '7': case '8': case '9':
+ MUST_BE_FIRST();
+ STRTOL(addr, ibufp);
+ break;
+ case '.':
+ case '$':
+ MUST_BE_FIRST();
+ ibufp++;
+ addr = (c == '.') ? current_addr : addr_last;
+ break;
+ case '/':
+ case '?':
+ MUST_BE_FIRST();
+ if ((addr = get_matching_node_addr(
+ get_compiled_pattern(), c == '/')) < 0)
+ return ERR;
+ else if (c == *ibufp)
+ ibufp++;
+ break;
+ case '\'':
+ MUST_BE_FIRST();
+ ibufp++;
+ if ((addr = get_marked_node_addr((unsigned char)*ibufp++)) < 0)
+ return ERR;
+ break;
+ case '%':
+ case ',':
+ case ';':
+ if (first) {
+ ibufp++;
+ addr_cnt++;
+ second_addr = (c == ';') ? current_addr : 1;
+ addr = addr_last;
+ break;
+ }
+ /* FALL THROUGH */
+ default:
+ if (ibufp == hd)
+ return EOF;
+ else if (addr < 0 || addr_last < addr) {
+ seterrmsg("invalid address");
+ return ERR;
+ } else
+ return addr;
+ }
+ /* NOTREACHED */
+}
+
+
+#ifdef BACKWARDS
+/* GET_THIRD_ADDR: get a legal address from the command buffer */
+#define GET_THIRD_ADDR(addr) \
+{ \
+ long ol1, ol2; \
+\
+ ol1 = first_addr, ol2 = second_addr; \
+ if (extract_addr_range() < 0) \
+ return ERR; \
+ else if (addr_cnt == 0) { \
+ seterrmsg("destination expected"); \
+ return ERR; \
+ } else if (second_addr < 0 || addr_last < second_addr) { \
+ seterrmsg("invalid address"); \
+ return ERR; \
+ } \
+ addr = second_addr; \
+ first_addr = ol1, second_addr = ol2; \
+}
+#else /* BACKWARDS */
+/* GET_THIRD_ADDR: get a legal address from the command buffer */
+#define GET_THIRD_ADDR(addr) \
+{ \
+ long ol1, ol2; \
+\
+ ol1 = first_addr, ol2 = second_addr; \
+ if (extract_addr_range() < 0) \
+ return ERR; \
+ if (second_addr < 0 || addr_last < second_addr) { \
+ seterrmsg("invalid address"); \
+ return ERR; \
+ } \
+ addr = second_addr; \
+ first_addr = ol1, second_addr = ol2; \
+}
+#endif
+
+
+/* GET_COMMAND_SUFFIX: verify the command suffix in the command buffer */
+#define GET_COMMAND_SUFFIX() { \
+ int done = 0; \
+ do { \
+ switch(*ibufp) { \
+ case 'p': \
+ gflag |= GPR, ibufp++; \
+ break; \
+ case 'l': \
+ gflag |= GLS, ibufp++; \
+ break; \
+ case 'n': \
+ gflag |= GNP, ibufp++; \
+ break; \
+ default: \
+ done++; \
+ } \
+ } while (!done); \
+ if (*ibufp++ != '\n') { \
+ seterrmsg("invalid command suffix"); \
+ return ERR; \
+ } \
+}
+
+
+/* sflags */
+#define SGG 001 /* complement previous global substitute suffix */
+#define SGP 002 /* complement previous print suffix */
+#define SGR 004 /* use last regex instead of last pat */
+#define SGF 010 /* repeat last substitution */
+
+int patlock = 0; /* if set, pattern not freed by get_compiled_pattern() */
+
+long rows = 22; /* scroll length: ws_row - 2 */
+
+/* exec_command: execute the next command in command buffer; return print
+ request, if any */
+int
+exec_command(void)
+{
+ static pattern_t *pat = NULL;
+ static int sgflag = 0;
+ static long sgnum = 0;
+
+ pattern_t *tpat;
+ char *fnp;
+ int gflag = 0;
+ int sflags = 0;
+ long addr = 0;
+ int n = 0;
+ int c;
+
+ SKIP_BLANKS();
+ switch(c = *ibufp++) {
+ case 'a':
+ GET_COMMAND_SUFFIX();
+ if (!isglobal) clear_undo_stack();
+ if (append_lines(second_addr) < 0)
+ return ERR;
+ break;
+ case 'c':
+ if (check_addr_range(current_addr, current_addr) < 0)
+ return ERR;
+ GET_COMMAND_SUFFIX();
+ if (!isglobal) clear_undo_stack();
+ if (delete_lines(first_addr, second_addr) < 0 ||
+ append_lines(current_addr) < 0)
+ return ERR;
+ break;
+ case 'd':
+ if (check_addr_range(current_addr, current_addr) < 0)
+ return ERR;
+ GET_COMMAND_SUFFIX();
+ if (!isglobal) clear_undo_stack();
+ if (delete_lines(first_addr, second_addr) < 0)
+ return ERR;
+ else if ((addr = INC_MOD(current_addr, addr_last)) != 0)
+ current_addr = addr;
+ break;
+ case 'e':
+ if (modified && !scripted)
+ return EMOD;
+ /* fall through */
+ case 'E':
+ if (addr_cnt > 0) {
+ seterrmsg("unexpected address");
+ return ERR;
+ } else if (!isspace((unsigned char)*ibufp)) {
+ seterrmsg("unexpected command suffix");
+ return ERR;
+ } else if ((fnp = get_filename()) == NULL)
+ return ERR;
+ GET_COMMAND_SUFFIX();
+ if (delete_lines(1, addr_last) < 0)
+ return ERR;
+ clear_undo_stack();
+ if (close_sbuf() < 0)
+ return ERR;
+ else if (open_sbuf() < 0)
+ return FATAL;
+ if (*fnp && *fnp != '!') strlcpy(old_filename, fnp,
+ sizeof(old_filename) - 2);
+#ifdef BACKWARDS
+ if (*fnp == '\0' && *old_filename == '\0') {
+ seterrmsg("no current filename");
+ return ERR;
+ }
+#endif
+ if (read_file(*fnp ? fnp : old_filename, 0) < 0)
+ return ERR;
+ clear_undo_stack();
+ modified = 0;
+ u_current_addr = u_addr_last = -1;
+ break;
+ case 'f':
+ if (addr_cnt > 0) {
+ seterrmsg("unexpected address");
+ return ERR;
+ } else if (!isspace((unsigned char)*ibufp)) {
+ seterrmsg("unexpected command suffix");
+ return ERR;
+ } else if ((fnp = get_filename()) == NULL)
+ return ERR;
+ else if (*fnp == '!') {
+ seterrmsg("invalid redirection");
+ return ERR;
+ }
+ GET_COMMAND_SUFFIX();
+ if (*fnp) strlcpy(old_filename, fnp, sizeof(old_filename) - 2);
+ printf("%s\n", strip_escapes(old_filename));
+ break;
+ case 'g':
+ case 'v':
+ case 'G':
+ case 'V':
+ if (isglobal) {
+ seterrmsg("cannot nest global commands");
+ return ERR;
+ } else if (check_addr_range(1, addr_last) < 0)
+ return ERR;
+ else if (build_active_list(c == 'g' || c == 'G') < 0)
+ return ERR;
+ else if ((n = (c == 'G' || c == 'V')) != 0)
+ GET_COMMAND_SUFFIX();
+ isglobal++;
+ if (exec_global(n, gflag) < 0)
+ return ERR;
+ break;
+ case 'h':
+ if (addr_cnt > 0) {
+ seterrmsg("unexpected address");
+ return ERR;
+ }
+ GET_COMMAND_SUFFIX();
+ if (*errmsg) fprintf(stderr, "%s\n", errmsg);
+ break;
+ case 'H':
+ if (addr_cnt > 0) {
+ seterrmsg("unexpected address");
+ return ERR;
+ }
+ GET_COMMAND_SUFFIX();
+ if ((garrulous = 1 - garrulous) && *errmsg)
+ fprintf(stderr, "%s\n", errmsg);
+ break;
+ case 'i':
+ if (second_addr == 0) {
+ seterrmsg("invalid address");
+ return ERR;
+ }
+ GET_COMMAND_SUFFIX();
+ if (!isglobal) clear_undo_stack();
+ if (append_lines(second_addr - 1) < 0)
+ return ERR;
+ break;
+ case 'j':
+ if (check_addr_range(current_addr, current_addr + 1) < 0)
+ return ERR;
+ GET_COMMAND_SUFFIX();
+ if (!isglobal) clear_undo_stack();
+ if (first_addr != second_addr &&
+ join_lines(first_addr, second_addr) < 0)
+ return ERR;
+ break;
+ case 'k':
+ c = *ibufp++;
+ if (second_addr == 0) {
+ seterrmsg("invalid address");
+ return ERR;
+ }
+ GET_COMMAND_SUFFIX();
+ if (mark_line_node(get_addressed_line_node(second_addr), (unsigned char)c) < 0)
+ return ERR;
+ break;
+ case 'l':
+ if (check_addr_range(current_addr, current_addr) < 0)
+ return ERR;
+ GET_COMMAND_SUFFIX();
+ if (display_lines(first_addr, second_addr, gflag | GLS) < 0)
+ return ERR;
+ gflag = 0;
+ break;
+ case 'm':
+ if (check_addr_range(current_addr, current_addr) < 0)
+ return ERR;
+ GET_THIRD_ADDR(addr);
+ if (first_addr <= addr && addr < second_addr) {
+ seterrmsg("invalid destination");
+ return ERR;
+ }
+ GET_COMMAND_SUFFIX();
+ if (!isglobal) clear_undo_stack();
+ if (move_lines(addr) < 0)
+ return ERR;
+ break;
+ case 'n':
+ if (check_addr_range(current_addr, current_addr) < 0)
+ return ERR;
+ GET_COMMAND_SUFFIX();
+ if (display_lines(first_addr, second_addr, gflag | GNP) < 0)
+ return ERR;
+ gflag = 0;
+ break;
+ case 'p':
+ if (check_addr_range(current_addr, current_addr) < 0)
+ return ERR;
+ GET_COMMAND_SUFFIX();
+ if (display_lines(first_addr, second_addr, gflag | GPR) < 0)
+ return ERR;
+ gflag = 0;
+ break;
+ case 'P':
+ if (addr_cnt > 0) {
+ seterrmsg("unexpected address");
+ return ERR;
+ }
+ GET_COMMAND_SUFFIX();
+ prompt = prompt ? NULL : optarg ? optarg : dps;
+ break;
+ case 'q':
+ case 'Q':
+ if (addr_cnt > 0) {
+ seterrmsg("unexpected address");
+ return ERR;
+ }
+ GET_COMMAND_SUFFIX();
+ gflag = (modified && !scripted && c == 'q') ? EMOD : EOF;
+ break;
+ case 'r':
+ if (!isspace((unsigned char)*ibufp)) {
+ seterrmsg("unexpected command suffix");
+ return ERR;
+ } else if (addr_cnt == 0)
+ second_addr = addr_last;
+ if ((fnp = get_filename()) == NULL)
+ return ERR;
+ GET_COMMAND_SUFFIX();
+ if (!isglobal) clear_undo_stack();
+ if (*old_filename == '\0' && *fnp != '!')
+ strlcpy(old_filename, fnp, sizeof(old_filename) - 2);
+#ifdef BACKWARDS
+ if (*fnp == '\0' && *old_filename == '\0') {
+ seterrmsg("no current filename");
+ return ERR;
+ }
+#endif
+ if ((addr = read_file(*fnp ? fnp : old_filename, second_addr)) < 0)
+ return ERR;
+ else if (addr && addr != addr_last)
+ modified = 1;
+ break;
+ case 's':
+ do {
+ switch(*ibufp) {
+ case '\n':
+ sflags |=SGF;
+ break;
+ case 'g':
+ sflags |= SGG;
+ ibufp++;
+ break;
+ case 'p':
+ sflags |= SGP;
+ ibufp++;
+ break;
+ case 'r':
+ sflags |= SGR;
+ ibufp++;
+ break;
+ case '0': case '1': case '2': case '3': case '4':
+ case '5': case '6': case '7': case '8': case '9':
+ STRTOL(sgnum, ibufp);
+ sflags |= SGF;
+ sgflag &= ~GSG; /* override GSG */
+ break;
+ default:
+ if (sflags) {
+ seterrmsg("invalid command suffix");
+ return ERR;
+ }
+ }
+ } while (sflags && *ibufp != '\n');
+ if (sflags && !pat) {
+ seterrmsg("no previous substitution");
+ return ERR;
+ } else if (sflags & SGG)
+ sgnum = 0; /* override numeric arg */
+ if (*ibufp != '\n' && *(ibufp + 1) == '\n') {
+ seterrmsg("invalid pattern delimiter");
+ return ERR;
+ }
+ tpat = pat;
+ SPL1();
+ if ((!sflags || (sflags & SGR)) &&
+ (tpat = get_compiled_pattern()) == NULL) {
+ SPL0();
+ return ERR;
+ } else if (tpat != pat) {
+ if (pat) {
+ regfree(pat);
+ free(pat);
+ }
+ pat = tpat;
+ patlock = 1; /* reserve pattern */
+ }
+ SPL0();
+ if (!sflags && extract_subst_tail(&sgflag, &sgnum) < 0)
+ return ERR;
+ else if (isglobal)
+ sgflag |= GLB;
+ else
+ sgflag &= ~GLB;
+ if (sflags & SGG)
+ sgflag ^= GSG;
+ if (sflags & SGP)
+ sgflag ^= GPR, sgflag &= ~(GLS | GNP);
+ do {
+ switch(*ibufp) {
+ case 'p':
+ sgflag |= GPR, ibufp++;
+ break;
+ case 'l':
+ sgflag |= GLS, ibufp++;
+ break;
+ case 'n':
+ sgflag |= GNP, ibufp++;
+ break;
+ default:
+ n++;
+ }
+ } while (!n);
+ if (check_addr_range(current_addr, current_addr) < 0)
+ return ERR;
+ GET_COMMAND_SUFFIX();
+ if (!isglobal) clear_undo_stack();
+ if (search_and_replace(pat, sgflag, sgnum) < 0)
+ return ERR;
+ break;
+ case 't':
+ if (check_addr_range(current_addr, current_addr) < 0)
+ return ERR;
+ GET_THIRD_ADDR(addr);
+ GET_COMMAND_SUFFIX();
+ if (!isglobal) clear_undo_stack();
+ if (copy_lines(addr) < 0)
+ return ERR;
+ break;
+ case 'u':
+ if (addr_cnt > 0) {
+ seterrmsg("unexpected address");
+ return ERR;
+ }
+ GET_COMMAND_SUFFIX();
+ if (pop_undo_stack() < 0)
+ return ERR;
+ break;
+ case 'w':
+ case 'W':
+ if ((n = *ibufp) == 'q' || n == 'Q') {
+ gflag = EOF;
+ ibufp++;
+ }
+ if (!isspace((unsigned char)*ibufp)) {
+ seterrmsg("unexpected command suffix");
+ return ERR;
+ } else if ((fnp = get_filename()) == NULL)
+ return ERR;
+ if (addr_cnt == 0 && !addr_last)
+ first_addr = second_addr = 0;
+ else if (check_addr_range(1, addr_last) < 0)
+ return ERR;
+ GET_COMMAND_SUFFIX();
+ if (*old_filename == '\0' && *fnp != '!')
+ strlcpy(old_filename, fnp, sizeof(old_filename) - 2);
+#ifdef BACKWARDS
+ if (*fnp == '\0' && *old_filename == '\0') {
+ seterrmsg("no current filename");
+ return ERR;
+ }
+#endif
+ if ((addr = write_file(*fnp ? fnp : old_filename,
+ (c == 'W') ? "a" : "w", first_addr, second_addr)) < 0)
+ return ERR;
+ else if (addr == addr_last)
+ modified = 0;
+ else if (modified && !scripted && n == 'q')
+ gflag = EMOD;
+ break;
+ case 'x':
+ if (addr_cnt > 0) {
+ seterrmsg("unexpected address");
+ return ERR;
+ }
+ GET_COMMAND_SUFFIX();
+#ifdef DES
+ des = get_keyword();
+#else
+ seterrmsg("crypt unavailable");
+ return ERR;
+#endif
+ break;
+ case 'z':
+#ifdef BACKWARDS
+ if (check_addr_range(first_addr = 1, current_addr + 1) < 0)
+#else
+ if (check_addr_range(first_addr = 1, current_addr + !isglobal) < 0)
+#endif
+ return ERR;
+ else if ('0' < *ibufp && *ibufp <= '9')
+ STRTOL(rows, ibufp);
+ GET_COMMAND_SUFFIX();
+ if (display_lines(second_addr, min(addr_last,
+ second_addr + rows), gflag) < 0)
+ return ERR;
+ gflag = 0;
+ break;
+ case '=':
+ GET_COMMAND_SUFFIX();
+ printf("%ld\n", addr_cnt ? second_addr : addr_last);
+ break;
+ case '!':
+ if (addr_cnt > 0) {
+ seterrmsg("unexpected address");
+ return ERR;
+ }
+ if ((sflags = get_shell_command()) < 0)
+ return ERR;
+ GET_COMMAND_SUFFIX();
+ if (sflags) printf("%s\n", shcmd + 1);
+ system(shcmd + 1);
+ if (!scripted) printf("!\n");
+ break;
+ case '\n':
+#ifdef BACKWARDS
+ if (check_addr_range(first_addr = 1, current_addr + 1) < 0
+#else
+ if (check_addr_range(first_addr = 1, current_addr + !isglobal) < 0
+#endif
+ || display_lines(second_addr, second_addr, 0) < 0)
+ return ERR;
+ break;
+ default:
+ seterrmsg("unknown command");
+ return ERR;
+ }
+ return gflag;
+}
+
+
+/* check_addr_range: return status of address range check */
+int
+check_addr_range(long n, long m)
+{
+ if (addr_cnt == 0) {
+ first_addr = n;
+ second_addr = m;
+ }
+ if (first_addr > second_addr || 1 > first_addr ||
+ second_addr > addr_last) {
+ seterrmsg("invalid address");
+ return ERR;
+ }
+ return 0;
+}
+
+
+/* get_matching_node_addr: return the address of the next line matching a
+ pattern in a given direction. wrap around begin/end of editor buffer if
+ necessary */
+long
+get_matching_node_addr(pattern_t *pat, int dir)
+{
+ char *s;
+ long n = current_addr;
+ line_t *lp;
+
+ if (!pat) return ERR;
+ do {
+ if ((n = dir ? INC_MOD(n, addr_last) :
+ DEC_MOD(n, addr_last)) != 0) {
+ lp = get_addressed_line_node(n);
+ if ((s = get_sbuf_line(lp)) == NULL)
+ return ERR;
+ if (isbinary)
+ NUL_TO_NEWLINE(s, lp->len);
+ if (!regexec(pat, s, 0, NULL, 0))
+ return n;
+ }
+ } while (n != current_addr);
+ seterrmsg("no match");
+ return ERR;
+}
+
+
+/* get_filename: return pointer to copy of filename in the command buffer */
+char *
+get_filename(void)
+{
+ static char *file = NULL;
+ static int filesz = 0;
+
+ int n;
+
+ if (*ibufp != '\n') {
+ SKIP_BLANKS();
+ if (*ibufp == '\n') {
+ seterrmsg("invalid filename");
+ return NULL;
+ } else if ((ibufp = get_extended_line(&n, 1)) == NULL)
+ return NULL;
+ else if (*ibufp == '!') {
+ ibufp++;
+ if ((n = get_shell_command()) < 0)
+ return NULL;
+ if (n) printf("%s\n", shcmd + 1);
+ return shcmd;
+ } else if (n - 1 > MAXPATHLEN) {
+ seterrmsg("filename too long");
+ return NULL;
+ }
+ }
+#ifndef BACKWARDS
+ else if (*old_filename == '\0') {
+ seterrmsg("no current filename");
+ return NULL;
+ }
+#endif
+ REALLOC(file, filesz, MAXPATHLEN + 1, NULL);
+ for (n = 0; *ibufp != '\n';)
+ file[n++] = *ibufp++;
+ file[n] = '\0';
+ return is_legal_filename(file) ? file : NULL;
+}
+
+
+/* get_shell_command: read a shell command from stdin; return substitution
+ status */
+int
+get_shell_command(void)
+{
+ static char *buf = NULL;
+ static int n = 0;
+
+ char *s; /* substitution char pointer */
+ int i = 0;
+ int j = 0;
+
+ if (red || secure) {
+ seterrmsg("shell access restricted");
+ return ERR;
+ } else if ((s = ibufp = get_extended_line(&j, 1)) == NULL)
+ return ERR;
+ REALLOC(buf, n, j + 1, ERR);
+ buf[i++] = '!'; /* prefix command w/ bang */
+ while (*ibufp != '\n')
+ switch (*ibufp) {
+ default:
+ REALLOC(buf, n, i + 2, ERR);
+ buf[i++] = *ibufp;
+ if (*ibufp++ == '\\')
+ buf[i++] = *ibufp++;
+ break;
+ case '!':
+ if (s != ibufp) {
+ REALLOC(buf, n, i + 1, ERR);
+ buf[i++] = *ibufp++;
+ }
+#ifdef BACKWARDS
+ else if (shcmd == NULL || *(shcmd + 1) == '\0')
+#else
+ else if (shcmd == NULL)
+#endif
+ {
+ seterrmsg("no previous command");
+ return ERR;
+ } else {
+ REALLOC(buf, n, i + shcmdi, ERR);
+ for (s = shcmd + 1; s < shcmd + shcmdi;)
+ buf[i++] = *s++;
+ s = ibufp++;
+ }
+ break;
+ case '%':
+ if (*old_filename == '\0') {
+ seterrmsg("no current filename");
+ return ERR;
+ }
+ j = strlen(s = strip_escapes(old_filename));
+ REALLOC(buf, n, i + j, ERR);
+ while (j--)
+ buf[i++] = *s++;
+ s = ibufp++;
+ break;
+ }
+ REALLOC(shcmd, shcmdsz, i + 1, ERR);
+ memcpy(shcmd, buf, i);
+ shcmd[shcmdi = i] = '\0';
+ return *s == '!' || *s == '%';
+}
+
+
+/* append_lines: insert text from stdin to after line n; stop when either a
+ single period is read or EOF; return status */
+int
+append_lines(long n)
+{
+ int l;
+ char *lp = ibuf;
+ char *eot;
+ undo_t *up = NULL;
+
+ for (current_addr = n;;) {
+ if (!isglobal) {
+ if ((l = get_tty_line()) < 0)
+ return ERR;
+ else if (l == 0 || ibuf[l - 1] != '\n') {
+ clearerr(stdin);
+ return l ? EOF : 0;
+ }
+ lp = ibuf;
+ } else if (*(lp = ibufp) == '\0')
+ return 0;
+ else {
+ while (*ibufp++ != '\n')
+ ;
+ l = ibufp - lp;
+ }
+ if (l == 2 && lp[0] == '.' && lp[1] == '\n') {
+ return 0;
+ }
+ eot = lp + l;
+ SPL1();
+ do {
+ if ((lp = put_sbuf_line(lp)) == NULL) {
+ SPL0();
+ return ERR;
+ } else if (up)
+ up->t = get_addressed_line_node(current_addr);
+ else if ((up = push_undo_stack(UADD, current_addr,
+ current_addr)) == NULL) {
+ SPL0();
+ return ERR;
+ }
+ } while (lp != eot);
+ modified = 1;
+ SPL0();
+ }
+ /* NOTREACHED */
+}
+
+
+/* join_lines: replace a range of lines with the joined text of those lines */
+int
+join_lines(long from, long to)
+{
+ static char *buf = NULL;
+ static int n;
+
+ char *s;
+ int size = 0;
+ line_t *bp, *ep;
+
+ ep = get_addressed_line_node(INC_MOD(to, addr_last));
+ bp = get_addressed_line_node(from);
+ for (; bp != ep; bp = bp->q_forw) {
+ if ((s = get_sbuf_line(bp)) == NULL)
+ return ERR;
+ REALLOC(buf, n, size + bp->len, ERR);
+ memcpy(buf + size, s, bp->len);
+ size += bp->len;
+ }
+ REALLOC(buf, n, size + 2, ERR);
+ memcpy(buf + size, "\n", 2);
+ if (delete_lines(from, to) < 0)
+ return ERR;
+ current_addr = from - 1;
+ SPL1();
+ if (put_sbuf_line(buf) == NULL ||
+ push_undo_stack(UADD, current_addr, current_addr) == NULL) {
+ SPL0();
+ return ERR;
+ }
+ modified = 1;
+ SPL0();
+ return 0;
+}
+
+
+/* move_lines: move a range of lines */
+int
+move_lines(long addr)
+{
+ line_t *b1, *a1, *b2, *a2;
+ long n = INC_MOD(second_addr, addr_last);
+ long p = first_addr - 1;
+ int done = (addr == first_addr - 1 || addr == second_addr);
+
+ SPL1();
+ if (done) {
+ a2 = get_addressed_line_node(n);
+ b2 = get_addressed_line_node(p);
+ current_addr = second_addr;
+ } else if (push_undo_stack(UMOV, p, n) == NULL ||
+ push_undo_stack(UMOV, addr, INC_MOD(addr, addr_last)) == NULL) {
+ SPL0();
+ return ERR;
+ } else {
+ a1 = get_addressed_line_node(n);
+ if (addr < first_addr) {
+ b1 = get_addressed_line_node(p);
+ b2 = get_addressed_line_node(addr);
+ /* this get_addressed_line_node last! */
+ } else {
+ b2 = get_addressed_line_node(addr);
+ b1 = get_addressed_line_node(p);
+ /* this get_addressed_line_node last! */
+ }
+ a2 = b2->q_forw;
+ REQUE(b2, b1->q_forw);
+ REQUE(a1->q_back, a2);
+ REQUE(b1, a1);
+ current_addr = addr + ((addr < first_addr) ?
+ second_addr - first_addr + 1 : 0);
+ }
+ if (isglobal)
+ unset_active_nodes(b2->q_forw, a2);
+ modified = 1;
+ SPL0();
+ return 0;
+}
+
+
+/* copy_lines: copy a range of lines; return status */
+int
+copy_lines(long addr)
+{
+ line_t *lp, *np = get_addressed_line_node(first_addr);
+ undo_t *up = NULL;
+ long n = second_addr - first_addr + 1;
+ long m = 0;
+
+ current_addr = addr;
+ if (first_addr <= addr && addr < second_addr) {
+ n = addr - first_addr + 1;
+ m = second_addr - addr;
+ }
+ for (; n > 0; n=m, m=0, np = get_addressed_line_node(current_addr + 1))
+ for (; n-- > 0; np = np->q_forw) {
+ SPL1();
+ if ((lp = dup_line_node(np)) == NULL) {
+ SPL0();
+ return ERR;
+ }
+ add_line_node(lp);
+ if (up)
+ up->t = lp;
+ else if ((up = push_undo_stack(UADD, current_addr,
+ current_addr)) == NULL) {
+ SPL0();
+ return ERR;
+ }
+ modified = 1;
+ SPL0();
+ }
+ return 0;
+}
+
+
+/* delete_lines: delete a range of lines */
+int
+delete_lines(long from, long to)
+{
+ line_t *n, *p;
+
+ SPL1();
+ if (push_undo_stack(UDEL, from, to) == NULL) {
+ SPL0();
+ return ERR;
+ }
+ n = get_addressed_line_node(INC_MOD(to, addr_last));
+ p = get_addressed_line_node(from - 1);
+ /* this get_addressed_line_node last! */
+ if (isglobal)
+ unset_active_nodes(p->q_forw, n);
+ REQUE(p, n);
+ addr_last -= to - from + 1;
+ current_addr = from - 1;
+ modified = 1;
+ SPL0();
+ return 0;
+}
+
+
+/* display_lines: print a range of lines to stdout */
+int
+display_lines(long from, long to, int gflag)
+{
+ line_t *bp;
+ line_t *ep;
+ char *s;
+
+ if (!from) {
+ seterrmsg("invalid address");
+ return ERR;
+ }
+ ep = get_addressed_line_node(INC_MOD(to, addr_last));
+ bp = get_addressed_line_node(from);
+ for (; bp != ep; bp = bp->q_forw) {
+ if ((s = get_sbuf_line(bp)) == NULL)
+ return ERR;
+ if (put_tty_line(s, bp->len, current_addr = from++, gflag) < 0)
+ return ERR;
+ }
+ return 0;
+}
+
+
+#define MAXMARK 26 /* max number of marks */
+
+line_t *mark[MAXMARK]; /* line markers */
+int markno; /* line marker count */
+
+/* mark_line_node: set a line node mark */
+int
+mark_line_node(line_t *lp, int n)
+{
+ if (!islower(n)) {
+ seterrmsg("invalid mark character");
+ return ERR;
+ } else if (mark[n - 'a'] == NULL)
+ markno++;
+ mark[n - 'a'] = lp;
+ return 0;
+}
+
+
+/* get_marked_node_addr: return address of a marked line */
+long
+get_marked_node_addr(int n)
+{
+ if (!islower(n)) {
+ seterrmsg("invalid mark character");
+ return ERR;
+ }
+ return get_line_node_addr(mark[n - 'a']);
+}
+
+
+/* unmark_line_node: clear line node mark */
+void
+unmark_line_node(line_t *lp)
+{
+ int i;
+
+ for (i = 0; markno && i < MAXMARK; i++)
+ if (mark[i] == lp) {
+ mark[i] = NULL;
+ markno--;
+ }
+}
+
+
+/* dup_line_node: return a pointer to a copy of a line node */
+line_t *
+dup_line_node(line_t *lp)
+{
+ line_t *np;
+
+ if ((np = (line_t *) malloc(sizeof(line_t))) == NULL) {
+ fprintf(stderr, "%s\n", strerror(errno));
+ seterrmsg("out of memory");
+ return NULL;
+ }
+ np->seek = lp->seek;
+ np->len = lp->len;
+ return np;
+}
+
+
+/* has_trailing_escape: return the parity of escapes preceding a character
+ in a string */
+int
+has_trailing_escape(char *s, char *t)
+{
+ return (s == t || *(t - 1) != '\\') ? 0 : !has_trailing_escape(s, t - 1);
+}
+
+
+/* strip_escapes: return copy of escaped string of at most length MAXPATHLEN */
+char *
+strip_escapes(const char *s)
+{
+ static char *file = NULL;
+ static int filesz = 0;
+
+ int i = 0;
+
+ REALLOC(file, filesz, MAXPATHLEN + 1, NULL);
+ while ((i < (filesz - 1)) &&
+ (file[i++] = (*s == '\\') != '\0' ? *++s : *s))
+ s++;
+ file[filesz - 1] = '\0';
+ return file;
+}
+
+
+void
+signal_hup(int signo)
+{
+ if (mutex)
+ sigflags |= (1 << (signo - 1));
+ else handle_hup(signo);
+}
+
+
+void
+signal_int(int signo)
+{
+ if (mutex)
+ sigflags |= (1 << (signo - 1));
+ else handle_int(signo);
+}
+
+
+void
+handle_hup(int signo)
+{
+ char *hup = NULL; /* hup filename */
+ char *s;
+ int n;
+
+ if (!sigactive)
+ quit(1);
+ sigflags &= ~(1 << (signo - 1));
+ if (addr_last && write_file("ed.hup", "w", 1, addr_last) < 0 &&
+ (s = getenv("HOME")) != NULL &&
+ (n = strlen(s)) + 8 <= MAXPATHLEN && /* "ed.hup" + '/' */
+ (hup = (char *) malloc(n + 10)) != NULL) {
+ strcpy(hup, s);
+ if (hup[n - 1] != '/')
+ hup[n] = '/', hup[n+1] = '\0';
+ strcat(hup, "ed.hup");
+ write_file(hup, "w", 1, addr_last);
+ }
+ quit(2);
+}
+
+
+void
+handle_int(int signo)
+{
+ if (!sigactive)
+ quit(1);
+ sigflags &= ~(1 << (signo - 1));
+#ifdef _POSIX_SOURCE
+ siglongjmp(env, -1);
+#else
+ longjmp(env, -1);
+#endif
+}
+
+
+int cols = 72; /* wrap column */
+
+void
+handle_winch(int signo)
+{
+ struct winsize ws; /* window size structure */
+
+ sigflags &= ~(1 << (signo - 1));
+ if (ioctl(0, TIOCGWINSZ, (char *) &ws) >= 0) {
+ if (ws.ws_row > 2) rows = ws.ws_row - 2;
+ if (ws.ws_col > 8) cols = ws.ws_col - 8;
+ }
+}
+
+
+/* is_legal_filename: return a legal filename */
+int
+is_legal_filename(char *s)
+{
+ if (red && (*s == '!' || !strcmp(s, "..") || strchr(s, '/'))) {
+ seterrmsg("shell access restricted");
+ return 0;
+ }
+ return 1;
+}
diff --git a/bin/ed/re.c b/bin/ed/re.c
new file mode 100644
index 0000000..bc4abc9
--- /dev/null
+++ b/bin/ed/re.c
@@ -0,0 +1,146 @@
+/* $NetBSD: re.c,v 1.21 2014/03/23 05:06:42 dholland Exp $ */
+
+/* re.c: This file contains the regular expression interface routines for
+ the ed line editor. */
+/*-
+ * Copyright (c) 1993 Andrew Moore, Talke Studio.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+#ifndef lint
+#if 0
+static char *rcsid = "@(#)re.c,v 1.6 1994/02/01 00:34:43 alm Exp";
+#else
+__RCSID("$NetBSD: re.c,v 1.21 2014/03/23 05:06:42 dholland Exp $");
+#endif
+#endif /* not lint */
+
+#include <stdarg.h>
+#include "ed.h"
+
+
+char errmsg[MAXPATHLEN + 40] = "";
+
+void
+seterrmsg(const char *fmt, ...)
+{
+ va_list ap;
+
+ va_start(ap, fmt);
+ vsnprintf(errmsg, sizeof(errmsg), fmt, ap);
+ va_end(ap);
+}
+
+/* get_compiled_pattern: return pointer to compiled pattern from command
+ buffer */
+pattern_t *
+get_compiled_pattern(void)
+{
+ static pattern_t *expr = NULL;
+
+ char *exps;
+ char delimiter;
+ int n;
+
+ if ((delimiter = *ibufp) == ' ') {
+ seterrmsg("invalid pattern delimiter");
+ return NULL;
+ } else if (delimiter == '\n' || *++ibufp == '\n' || *ibufp == delimiter) {
+ if (!expr) seterrmsg("no previous pattern");
+ return expr;
+ } else if ((exps = extract_pattern(delimiter)) == NULL)
+ return NULL;
+ /* buffer alloc'd && not reserved */
+ if (expr && !patlock)
+ regfree(expr);
+ else if ((expr = (pattern_t *) malloc(sizeof(pattern_t))) == NULL) {
+ fprintf(stderr, "%s\n", strerror(errno));
+ seterrmsg("out of memory");
+ return NULL;
+ }
+ patlock = 0;
+ if ((n = regcomp(expr, exps, ere)) != 0) {
+ regerror(n, expr, errmsg, sizeof errmsg);
+ free(expr);
+ return expr = NULL;
+ }
+ return expr;
+}
+
+
+/* extract_pattern: copy a pattern string from the command buffer; return
+ pointer to the copy */
+char *
+extract_pattern(int delimiter)
+{
+ static char *lhbuf = NULL; /* buffer */
+ static int lhbufsz = 0; /* buffer size */
+
+ char *nd;
+ int len;
+
+ for (nd = ibufp; *nd != delimiter && *nd != '\n'; nd++)
+ switch (*nd) {
+ default:
+ break;
+ case '[':
+ if ((nd = parse_char_class(nd + 1)) == NULL) {
+ seterrmsg("unbalanced brackets ([])");
+ return NULL;
+ }
+ break;
+ case '\\':
+ if (*++nd == '\n') {
+ seterrmsg("trailing backslash (\\)");
+ return NULL;
+ }
+ break;
+ }
+ len = nd - ibufp;
+ REALLOC(lhbuf, lhbufsz, len + 1, NULL);
+ memcpy(lhbuf, ibufp, len);
+ lhbuf[len] = '\0';
+ ibufp = nd;
+ return (isbinary) ? NUL_TO_NEWLINE(lhbuf, len) : lhbuf;
+}
+
+
+/* parse_char_class: expand a POSIX character class */
+char *
+parse_char_class(char *s)
+{
+ int c, d;
+
+ if (*s == '^')
+ s++;
+ if (*s == ']')
+ s++;
+ for (; *s != ']' && *s != '\n'; s++)
+ if (*s == '[' && ((d = *(s+1)) == '.' || d == ':' || d == '='))
+ for (s++, c = *++s; *s != ']' || c != d; s++)
+ if ((c = *s) == '\n')
+ return NULL;
+ return (*s == ']') ? s : NULL;
+}
diff --git a/bin/ed/sub.c b/bin/ed/sub.c
new file mode 100644
index 0000000..933c6b9
--- /dev/null
+++ b/bin/ed/sub.c
@@ -0,0 +1,262 @@
+/* $NetBSD: sub.c,v 1.7 2014/03/23 05:06:42 dholland Exp $ */
+
+/* sub.c: This file contains the substitution routines for the ed
+ line editor */
+/*-
+ * Copyright (c) 1993 Andrew Moore, Talke Studio.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+#ifndef lint
+#if 0
+static char *rcsid = "@(#)sub.c,v 1.1 1994/02/01 00:34:44 alm Exp";
+#else
+__RCSID("$NetBSD: sub.c,v 1.7 2014/03/23 05:06:42 dholland Exp $");
+#endif
+#endif /* not lint */
+
+#include "ed.h"
+
+
+char *rhbuf; /* rhs substitution buffer */
+int rhbufsz; /* rhs substitution buffer size */
+int rhbufi; /* rhs substitution buffer index */
+
+/* extract_subst_tail: extract substitution tail from the command buffer */
+int
+extract_subst_tail(int *flagp, long *np)
+{
+ char delimiter;
+
+ *flagp = *np = 0;
+ if ((delimiter = *ibufp) == '\n') {
+ rhbufi = 0;
+ *flagp = GPR;
+ return 0;
+ } else if (extract_subst_template() == NULL)
+ return ERR;
+ else if (*ibufp == '\n') {
+ *flagp = GPR;
+ return 0;
+ } else if (*ibufp == delimiter)
+ ibufp++;
+ if ('1' <= *ibufp && *ibufp <= '9') {
+ STRTOL(*np, ibufp);
+ return 0;
+ } else if (*ibufp == 'g') {
+ ibufp++;
+ *flagp = GSG;
+ return 0;
+ }
+ return 0;
+}
+
+
+/* extract_subst_template: return pointer to copy of substitution template
+ in the command buffer */
+char *
+extract_subst_template(void)
+{
+ int n = 0;
+ int i = 0;
+ char c;
+ char delimiter = *ibufp++;
+
+ if (*ibufp == '%' && *(ibufp + 1) == delimiter) {
+ ibufp++;
+ if (!rhbuf) {
+ seterrmsg("no previous substitution");
+ }
+ return rhbuf;
+ }
+ while (*ibufp != delimiter) {
+ REALLOC(rhbuf, rhbufsz, i + 2, NULL);
+ if ((c = rhbuf[i++] = *ibufp++) == '\n' && *ibufp == '\0') {
+ i--, ibufp--;
+ break;
+ } else if (c != '\\')
+ ;
+ else if ((rhbuf[i++] = *ibufp++) != '\n')
+ ;
+ else if (!isglobal) {
+ while ((n = get_tty_line()) == 0 ||
+ (n > 0 && ibuf[n - 1] != '\n'))
+ clearerr(stdin);
+ if (n < 0)
+ return NULL;
+ }
+ }
+ REALLOC(rhbuf, rhbufsz, i + 1, NULL);
+ rhbuf[rhbufi = i] = '\0';
+ return rhbuf;
+}
+
+
+char *rbuf; /* substitute_matching_text buffer */
+int rbufsz; /* substitute_matching_text buffer size */
+
+/* search_and_replace: for each line in a range, change text matching a pattern
+ according to a substitution template; return status */
+int
+search_and_replace(pattern_t *pat, int gflag, int kth)
+{
+ undo_t *up;
+ char *txt;
+ char *eot;
+ long lc;
+ long xa = current_addr;
+ int nsubs = 0;
+ line_t *lp;
+ int len;
+
+ current_addr = first_addr - 1;
+ for (lc = 0; lc <= second_addr - first_addr; lc++) {
+ lp = get_addressed_line_node(++current_addr);
+ if ((len = substitute_matching_text(pat, lp, gflag, kth)) < 0)
+ return ERR;
+ else if (len) {
+ up = NULL;
+ if (delete_lines(current_addr, current_addr) < 0)
+ return ERR;
+ txt = rbuf;
+ eot = rbuf + len;
+ SPL1();
+ do {
+ if ((txt = put_sbuf_line(txt)) == NULL) {
+ SPL0();
+ return ERR;
+ } else if (up)
+ up->t = get_addressed_line_node(current_addr);
+ else if ((up = push_undo_stack(UADD,
+ current_addr, current_addr)) == NULL) {
+ SPL0();
+ return ERR;
+ }
+ } while (txt != eot);
+ SPL0();
+ nsubs++;
+ xa = current_addr;
+ }
+ }
+ current_addr = xa;
+ if (nsubs == 0 && !(gflag & GLB)) {
+ seterrmsg("no match");
+ return ERR;
+ } else if ((gflag & (GPR | GLS | GNP)) &&
+ display_lines(current_addr, current_addr, gflag) < 0)
+ return ERR;
+ return 0;
+}
+
+
+/* substitute_matching_text: replace text matched by a pattern according to
+ a substitution template; return pointer to the modified text */
+int
+substitute_matching_text(pattern_t *pat, line_t *lp, int gflag, int kth)
+{
+ int off = 0;
+ int changed = 0;
+ int matchno = 0;
+ int i = 0;
+ regmatch_t rm[SE_MAX];
+ char *txt;
+ char *eot;
+
+ if ((txt = get_sbuf_line(lp)) == NULL)
+ return ERR;
+ if (isbinary)
+ NUL_TO_NEWLINE(txt, lp->len);
+ eot = txt + lp->len;
+ if (!regexec(pat, txt, SE_MAX, rm, 0)) {
+ do {
+ if (!kth || kth == ++matchno) {
+ changed++;
+ i = rm[0].rm_so;
+ REALLOC(rbuf, rbufsz, off + i, ERR);
+ if (isbinary)
+ NEWLINE_TO_NUL(txt, rm[0].rm_eo);
+ memcpy(rbuf + off, txt, i);
+ off += i;
+ if ((off = apply_subst_template(txt, rm, off,
+ pat->re_nsub)) < 0)
+ return ERR;
+ } else {
+ i = rm[0].rm_eo;
+ REALLOC(rbuf, rbufsz, off + i, ERR);
+ if (isbinary)
+ NEWLINE_TO_NUL(txt, i);
+ memcpy(rbuf + off, txt, i);
+ off += i;
+ }
+ txt += rm[0].rm_eo;
+ } while (*txt && (!changed || ((gflag & GSG) && rm[0].rm_eo))
+ && !regexec(pat, txt, SE_MAX, rm, REG_NOTBOL));
+ i = eot - txt;
+ REALLOC(rbuf, rbufsz, off + i + 2, ERR);
+ if (i > 0 && !rm[0].rm_eo && (gflag & GSG)) {
+ seterrmsg("infinite substitution loop");
+ return ERR;
+ }
+ if (isbinary)
+ NEWLINE_TO_NUL(txt, i);
+ memcpy(rbuf + off, txt, i);
+ memcpy(rbuf + off + i, "\n", 2);
+ }
+ return changed ? off + i + 1 : 0;
+}
+
+
+/* apply_subst_template: modify text according to a substitution template;
+ return offset to end of modified text */
+int
+apply_subst_template(char *boln, regmatch_t *rm, int off, int re_nsub)
+{
+ int j = 0;
+ int k = 0;
+ int n;
+ char *sub = rhbuf;
+
+ for (; sub - rhbuf < rhbufi; sub++)
+ if (*sub == '&') {
+ j = rm[0].rm_so;
+ k = rm[0].rm_eo;
+ REALLOC(rbuf, rbufsz, off + k - j, ERR);
+ while (j < k)
+ rbuf[off++] = boln[j++];
+ } else if (*sub == '\\' && '1' <= *++sub && *sub <= '9' &&
+ (n = *sub - '0') <= re_nsub) {
+ j = rm[n].rm_so;
+ k = rm[n].rm_eo;
+ REALLOC(rbuf, rbufsz, off + k - j, ERR);
+ while (j < k)
+ rbuf[off++] = boln[j++];
+ } else {
+ REALLOC(rbuf, rbufsz, off + 1, ERR);
+ rbuf[off++] = *sub;
+ }
+ REALLOC(rbuf, rbufsz, off + 1, ERR);
+ rbuf[off] = '\0';
+ return off;
+}
diff --git a/bin/ed/test/=.err b/bin/ed/test/=.err
new file mode 100644
index 0000000..6a60559
--- /dev/null
+++ b/bin/ed/test/=.err
@@ -0,0 +1 @@
+1,$=
diff --git a/bin/ed/test/Makefile b/bin/ed/test/Makefile
new file mode 100644
index 0000000..fe631a8
--- /dev/null
+++ b/bin/ed/test/Makefile
@@ -0,0 +1,28 @@
+# $NetBSD: Makefile,v 1.12 2003/10/26 03:50:07 lukem Exp $
+
+.include <bsd.own.mk>
+
+ED?= ../obj/ed
+
+all: check
+ @:
+
+check: build test
+ @if grep -h '\*\*\*' errs.o scripts.o; then :; else \
+ echo "tests completed successfully."; \
+ fi
+
+build: mkscripts.sh
+ @if [ -f errs.o ]; then :; else \
+ echo "building test scripts for $(ED) ..."; \
+ ${HOST_SH} ${.CURDIR}/mkscripts.sh $(ED); \
+ fi
+
+test: build ckscripts.sh
+ @echo testing $(ED) ...
+ @${HOST_SH} ckscripts.sh $(ED)
+
+clean:
+ rm -f *.ed *.red *.[oz] *~
+
+.include <bsd.prog.mk>
diff --git a/bin/ed/test/README b/bin/ed/test/README
new file mode 100644
index 0000000..73d7f2e
--- /dev/null
+++ b/bin/ed/test/README
@@ -0,0 +1,32 @@
+$NetBSD: README,v 1.8 1995/03/21 09:05:18 cgd Exp $
+
+The files in this directory with suffixes `.t', `.d', `.r' and `.err' are
+used for testing ed. To run the tests, set the ED variable in the Makefile
+for the path name of the program to be tested (e.g., /bin/ed), and type
+`make'. The tests do not exhaustively verify POSIX compliance nor do
+they verify correct 8-bit or long line support.
+
+The test file suffixes have the following meanings:
+.t Template - a list of ed commands from which an ed script is
+ constructed
+.d Data - read by an ed script
+.r Result - the expected output after processing data via an ed
+ script.
+.err Error - invalid ed commands that should generate an error
+
+The output of the tests is written to the two files err.o and scripts.o.
+At the end of the tests, these files are grep'ed for error messages,
+which look like:
+ *** The script u.ed exited abnormally ***
+or:
+ *** Output u.o of script u.ed is incorrect ***
+
+The POSIX requirement that an address range not be used where at most
+a single address is expected has been relaxed in this version of ed.
+Therefore, the following scripts which test for compliance with this
+POSIX rule exit abnormally:
+=-err.ed
+a1-err.ed
+i1-err.ed
+k1-err.ed
+r1-err.ed
diff --git a/bin/ed/test/TODO b/bin/ed/test/TODO
new file mode 100644
index 0000000..c516d1e
--- /dev/null
+++ b/bin/ed/test/TODO
@@ -0,0 +1,17 @@
+$NetBSD: TODO,v 1.3 1995/03/21 09:05:20 cgd Exp $
+
+Some missing tests:
+0) g/./s^@^@ - okay: NULs in commands
+1) g/./s/^@/ - okay: NULs in patterns
+2) a
+ hello^V^Jworld
+ . - okay: embedded newlines in insert mode
+3) ed "" - error: invalid filename
+4) red .. - error: restricted
+5) red / - error: restricted
+5) red !xx - error: restricted
+6) ed -x - verify: 8-bit clean
+7) ed - verify: long-line support
+8) ed - verify: interactive/help mode
+9) G/pat/ - verify: global interactive command
+10) V/pat/ - verify: global interactive command
diff --git a/bin/ed/test/a.d b/bin/ed/test/a.d
new file mode 100644
index 0000000..92f337e
--- /dev/null
+++ b/bin/ed/test/a.d
@@ -0,0 +1,5 @@
+line 1
+line 2
+line 3
+line 4
+line5
diff --git a/bin/ed/test/a.r b/bin/ed/test/a.r
new file mode 100644
index 0000000..26257bd
--- /dev/null
+++ b/bin/ed/test/a.r
@@ -0,0 +1,8 @@
+hello world
+line 1
+hello world!
+line 2
+line 3
+line 4
+line5
+hello world!!
diff --git a/bin/ed/test/a.t b/bin/ed/test/a.t
new file mode 100644
index 0000000..ac98c40
--- /dev/null
+++ b/bin/ed/test/a.t
@@ -0,0 +1,9 @@
+0a
+hello world
+.
+2a
+hello world!
+.
+$a
+hello world!!
+.
diff --git a/bin/ed/test/a1.err b/bin/ed/test/a1.err
new file mode 100644
index 0000000..e80815f
--- /dev/null
+++ b/bin/ed/test/a1.err
@@ -0,0 +1,3 @@
+1,$a
+hello world
+.
diff --git a/bin/ed/test/a2.err b/bin/ed/test/a2.err
new file mode 100644
index 0000000..ec4b00b
--- /dev/null
+++ b/bin/ed/test/a2.err
@@ -0,0 +1,3 @@
+aa
+hello world
+.
diff --git a/bin/ed/test/addr.d b/bin/ed/test/addr.d
new file mode 100644
index 0000000..8f7ba1b
--- /dev/null
+++ b/bin/ed/test/addr.d
@@ -0,0 +1,9 @@
+line 1
+line 2
+line 3
+line 4
+line5
+1ine6
+line7
+line8
+line9
diff --git a/bin/ed/test/addr.r b/bin/ed/test/addr.r
new file mode 100644
index 0000000..04caf17
--- /dev/null
+++ b/bin/ed/test/addr.r
@@ -0,0 +1,2 @@
+line 2
+line9
diff --git a/bin/ed/test/addr.t b/bin/ed/test/addr.t
new file mode 100644
index 0000000..750b224
--- /dev/null
+++ b/bin/ed/test/addr.t
@@ -0,0 +1,5 @@
+1 d
+1 1 d
+1,2,d
+1;+ + ,d
+1,2;., + 2d
diff --git a/bin/ed/test/addr1.err b/bin/ed/test/addr1.err
new file mode 100644
index 0000000..29d6383
--- /dev/null
+++ b/bin/ed/test/addr1.err
@@ -0,0 +1 @@
+100
diff --git a/bin/ed/test/addr2.err b/bin/ed/test/addr2.err
new file mode 100644
index 0000000..e96acb9
--- /dev/null
+++ b/bin/ed/test/addr2.err
@@ -0,0 +1 @@
+-100
diff --git a/bin/ed/test/ascii.d b/bin/ed/test/ascii.d
new file mode 100644
index 0000000..c866266
--- /dev/null
+++ b/bin/ed/test/ascii.d
Binary files differ
diff --git a/bin/ed/test/ascii.r b/bin/ed/test/ascii.r
new file mode 100644
index 0000000..c866266
--- /dev/null
+++ b/bin/ed/test/ascii.r
Binary files differ
diff --git a/bin/ed/test/ascii.t b/bin/ed/test/ascii.t
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/bin/ed/test/ascii.t
diff --git a/bin/ed/test/bang1.d b/bin/ed/test/bang1.d
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/bin/ed/test/bang1.d
diff --git a/bin/ed/test/bang1.err b/bin/ed/test/bang1.err
new file mode 100644
index 0000000..630af90
--- /dev/null
+++ b/bin/ed/test/bang1.err
@@ -0,0 +1 @@
+.!date
diff --git a/bin/ed/test/bang1.r b/bin/ed/test/bang1.r
new file mode 100644
index 0000000..dcf02b2
--- /dev/null
+++ b/bin/ed/test/bang1.r
@@ -0,0 +1 @@
+okay
diff --git a/bin/ed/test/bang1.t b/bin/ed/test/bang1.t
new file mode 100644
index 0000000..d7b1fea
--- /dev/null
+++ b/bin/ed/test/bang1.t
@@ -0,0 +1,5 @@
+!read one
+hello, world
+a
+okay
+.
diff --git a/bin/ed/test/bang2.err b/bin/ed/test/bang2.err
new file mode 100644
index 0000000..79d8956
--- /dev/null
+++ b/bin/ed/test/bang2.err
@@ -0,0 +1 @@
+!!
diff --git a/bin/ed/test/c.d b/bin/ed/test/c.d
new file mode 100644
index 0000000..92f337e
--- /dev/null
+++ b/bin/ed/test/c.d
@@ -0,0 +1,5 @@
+line 1
+line 2
+line 3
+line 4
+line5
diff --git a/bin/ed/test/c.r b/bin/ed/test/c.r
new file mode 100644
index 0000000..0fb3e4f
--- /dev/null
+++ b/bin/ed/test/c.r
@@ -0,0 +1,4 @@
+at the top
+between top/middle
+in the middle
+at the bottom
diff --git a/bin/ed/test/c.t b/bin/ed/test/c.t
new file mode 100644
index 0000000..ebdd536
--- /dev/null
+++ b/bin/ed/test/c.t
@@ -0,0 +1,12 @@
+1c
+at the top
+.
+4c
+in the middle
+.
+$c
+at the bottom
+.
+2,3c
+between top/middle
+.
diff --git a/bin/ed/test/c1.err b/bin/ed/test/c1.err
new file mode 100644
index 0000000..658ec38
--- /dev/null
+++ b/bin/ed/test/c1.err
@@ -0,0 +1,3 @@
+cc
+hello world
+.
diff --git a/bin/ed/test/c2.err b/bin/ed/test/c2.err
new file mode 100644
index 0000000..24b3227
--- /dev/null
+++ b/bin/ed/test/c2.err
@@ -0,0 +1,3 @@
+0c
+hello world
+.
diff --git a/bin/ed/test/ckscripts.sh b/bin/ed/test/ckscripts.sh
new file mode 100755
index 0000000..86a19b1
--- /dev/null
+++ b/bin/ed/test/ckscripts.sh
@@ -0,0 +1,37 @@
+#!/bin/sh -
+# $NetBSD: ckscripts.sh,v 1.9 1995/04/23 10:07:34 cgd Exp $
+#
+# This script runs the .ed scripts generated by mkscripts.sh
+# and compares their output against the .r files, which contain
+# the correct output
+
+PATH="/bin:/usr/bin:/usr/local/bin/:."
+ED=$1
+[ ! -x $ED ] && { echo "$ED: cannot execute"; exit 1; }
+
+# Run the *.red scripts first, since these don't generate output;
+# they exit with non-zero status
+for i in *.red; do
+ echo $i
+ if $i; then
+ echo "*** The script $i exited abnormally ***"
+ fi
+done >errs.o 2>&1
+
+# Run the remainding scripts; they exit with zero status
+for i in *.ed; do
+# base=`expr $i : '\([^.]*\)'`
+# base=`echo $i | sed 's/\..*//'`
+ base=`$ED - \!"echo $i" <<-EOF
+ s/\..*
+ EOF`
+ if $base.ed; then
+ if cmp -s $base.o $base.r; then :; else
+ echo "*** Output $base.o of script $i is incorrect ***"
+ fi
+ else
+ echo "*** The script $i exited abnormally ***"
+ fi
+done >scripts.o 2>&1
+
+grep -h '\*\*\*' errs.o scripts.o
diff --git a/bin/ed/test/d.d b/bin/ed/test/d.d
new file mode 100644
index 0000000..92f337e
--- /dev/null
+++ b/bin/ed/test/d.d
@@ -0,0 +1,5 @@
+line 1
+line 2
+line 3
+line 4
+line5
diff --git a/bin/ed/test/d.err b/bin/ed/test/d.err
new file mode 100644
index 0000000..f03f694
--- /dev/null
+++ b/bin/ed/test/d.err
@@ -0,0 +1 @@
+dd
diff --git a/bin/ed/test/d.r b/bin/ed/test/d.r
new file mode 100644
index 0000000..b7e242c
--- /dev/null
+++ b/bin/ed/test/d.r
@@ -0,0 +1 @@
+line 2
diff --git a/bin/ed/test/d.t b/bin/ed/test/d.t
new file mode 100644
index 0000000..c7c473f
--- /dev/null
+++ b/bin/ed/test/d.t
@@ -0,0 +1,3 @@
+1d
+2;+1d
+$d
diff --git a/bin/ed/test/e1.d b/bin/ed/test/e1.d
new file mode 100644
index 0000000..3b18e51
--- /dev/null
+++ b/bin/ed/test/e1.d
@@ -0,0 +1 @@
+hello world
diff --git a/bin/ed/test/e1.err b/bin/ed/test/e1.err
new file mode 100644
index 0000000..827cc29
--- /dev/null
+++ b/bin/ed/test/e1.err
@@ -0,0 +1 @@
+ee e1.err
diff --git a/bin/ed/test/e1.r b/bin/ed/test/e1.r
new file mode 100644
index 0000000..e656728
--- /dev/null
+++ b/bin/ed/test/e1.r
@@ -0,0 +1 @@
+E e1.t
diff --git a/bin/ed/test/e1.t b/bin/ed/test/e1.t
new file mode 100644
index 0000000..e656728
--- /dev/null
+++ b/bin/ed/test/e1.t
@@ -0,0 +1 @@
+E e1.t
diff --git a/bin/ed/test/e2.d b/bin/ed/test/e2.d
new file mode 100644
index 0000000..aa44630
--- /dev/null
+++ b/bin/ed/test/e2.d
@@ -0,0 +1 @@
+E !echo hello world-
diff --git a/bin/ed/test/e2.err b/bin/ed/test/e2.err
new file mode 100644
index 0000000..779a64b
--- /dev/null
+++ b/bin/ed/test/e2.err
@@ -0,0 +1 @@
+.e e2.err
diff --git a/bin/ed/test/e2.r b/bin/ed/test/e2.r
new file mode 100644
index 0000000..59ebf11
--- /dev/null
+++ b/bin/ed/test/e2.r
@@ -0,0 +1 @@
+hello world-
diff --git a/bin/ed/test/e2.t b/bin/ed/test/e2.t
new file mode 100644
index 0000000..aa44630
--- /dev/null
+++ b/bin/ed/test/e2.t
@@ -0,0 +1 @@
+E !echo hello world-
diff --git a/bin/ed/test/e3.d b/bin/ed/test/e3.d
new file mode 100644
index 0000000..aa44630
--- /dev/null
+++ b/bin/ed/test/e3.d
@@ -0,0 +1 @@
+E !echo hello world-
diff --git a/bin/ed/test/e3.err b/bin/ed/test/e3.err
new file mode 100644
index 0000000..80a7fdc
--- /dev/null
+++ b/bin/ed/test/e3.err
@@ -0,0 +1 @@
+ee.err
diff --git a/bin/ed/test/e3.r b/bin/ed/test/e3.r
new file mode 100644
index 0000000..aa44630
--- /dev/null
+++ b/bin/ed/test/e3.r
@@ -0,0 +1 @@
+E !echo hello world-
diff --git a/bin/ed/test/e3.t b/bin/ed/test/e3.t
new file mode 100644
index 0000000..1c50726
--- /dev/null
+++ b/bin/ed/test/e3.t
@@ -0,0 +1 @@
+E
diff --git a/bin/ed/test/e4.d b/bin/ed/test/e4.d
new file mode 100644
index 0000000..aa44630
--- /dev/null
+++ b/bin/ed/test/e4.d
@@ -0,0 +1 @@
+E !echo hello world-
diff --git a/bin/ed/test/e4.r b/bin/ed/test/e4.r
new file mode 100644
index 0000000..aa44630
--- /dev/null
+++ b/bin/ed/test/e4.r
@@ -0,0 +1 @@
+E !echo hello world-
diff --git a/bin/ed/test/e4.t b/bin/ed/test/e4.t
new file mode 100644
index 0000000..d905d9d
--- /dev/null
+++ b/bin/ed/test/e4.t
@@ -0,0 +1 @@
+e
diff --git a/bin/ed/test/f1.err b/bin/ed/test/f1.err
new file mode 100644
index 0000000..e60975a
--- /dev/null
+++ b/bin/ed/test/f1.err
@@ -0,0 +1 @@
+.f f1.err
diff --git a/bin/ed/test/f2.err b/bin/ed/test/f2.err
new file mode 100644
index 0000000..26d1c5e
--- /dev/null
+++ b/bin/ed/test/f2.err
@@ -0,0 +1 @@
+ff1.err
diff --git a/bin/ed/test/g1.d b/bin/ed/test/g1.d
new file mode 100644
index 0000000..92f337e
--- /dev/null
+++ b/bin/ed/test/g1.d
@@ -0,0 +1,5 @@
+line 1
+line 2
+line 3
+line 4
+line5
diff --git a/bin/ed/test/g1.err b/bin/ed/test/g1.err
new file mode 100644
index 0000000..f95ea22
--- /dev/null
+++ b/bin/ed/test/g1.err
@@ -0,0 +1 @@
+g/./s //x/
diff --git a/bin/ed/test/g1.r b/bin/ed/test/g1.r
new file mode 100644
index 0000000..578a44b
--- /dev/null
+++ b/bin/ed/test/g1.r
@@ -0,0 +1,15 @@
+line5
+help! world
+order
+line 4
+help! world
+order
+line 3
+help! world
+order
+line 2
+help! world
+order
+line 1
+help! world
+order
diff --git a/bin/ed/test/g1.t b/bin/ed/test/g1.t
new file mode 100644
index 0000000..2d0b54f
--- /dev/null
+++ b/bin/ed/test/g1.t
@@ -0,0 +1,6 @@
+g/./m0
+g/./s/$/\
+hello world
+g/hello /s/lo/p!/\
+a\
+order
diff --git a/bin/ed/test/g2.d b/bin/ed/test/g2.d
new file mode 100644
index 0000000..92f337e
--- /dev/null
+++ b/bin/ed/test/g2.d
@@ -0,0 +1,5 @@
+line 1
+line 2
+line 3
+line 4
+line5
diff --git a/bin/ed/test/g2.err b/bin/ed/test/g2.err
new file mode 100644
index 0000000..0ff6a5a
--- /dev/null
+++ b/bin/ed/test/g2.err
@@ -0,0 +1 @@
+g//s/./x/
diff --git a/bin/ed/test/g2.r b/bin/ed/test/g2.r
new file mode 100644
index 0000000..3b18e51
--- /dev/null
+++ b/bin/ed/test/g2.r
@@ -0,0 +1 @@
+hello world
diff --git a/bin/ed/test/g2.t b/bin/ed/test/g2.t
new file mode 100644
index 0000000..831ee83
--- /dev/null
+++ b/bin/ed/test/g2.t
@@ -0,0 +1,2 @@
+g/[2-4]/-1,+1c\
+hello world
diff --git a/bin/ed/test/g3.d b/bin/ed/test/g3.d
new file mode 100644
index 0000000..92f337e
--- /dev/null
+++ b/bin/ed/test/g3.d
@@ -0,0 +1,5 @@
+line 1
+line 2
+line 3
+line 4
+line5
diff --git a/bin/ed/test/g3.err b/bin/ed/test/g3.err
new file mode 100644
index 0000000..01058d8
--- /dev/null
+++ b/bin/ed/test/g3.err
@@ -0,0 +1 @@
+g
diff --git a/bin/ed/test/g3.r b/bin/ed/test/g3.r
new file mode 100644
index 0000000..cc6fbdd
--- /dev/null
+++ b/bin/ed/test/g3.r
@@ -0,0 +1,5 @@
+linc 3
+xine 1
+xine 2
+xinc 4
+xinc5
diff --git a/bin/ed/test/g3.t b/bin/ed/test/g3.t
new file mode 100644
index 0000000..2d052a6
--- /dev/null
+++ b/bin/ed/test/g3.t
@@ -0,0 +1,4 @@
+g/./s//x/\
+3m0
+g/./s/e/c/\
+2,3m1
diff --git a/bin/ed/test/g4.d b/bin/ed/test/g4.d
new file mode 100644
index 0000000..92f337e
--- /dev/null
+++ b/bin/ed/test/g4.d
@@ -0,0 +1,5 @@
+line 1
+line 2
+line 3
+line 4
+line5
diff --git a/bin/ed/test/g4.r b/bin/ed/test/g4.r
new file mode 100644
index 0000000..350882d
--- /dev/null
+++ b/bin/ed/test/g4.r
@@ -0,0 +1,7 @@
+hello
+zine 1
+line 2
+line 3
+line 4
+line5
+world
diff --git a/bin/ed/test/g4.t b/bin/ed/test/g4.t
new file mode 100644
index 0000000..ec61816
--- /dev/null
+++ b/bin/ed/test/g4.t
@@ -0,0 +1,13 @@
+g/./s/./x/\
+u\
+s/./y/\
+u\
+s/./z/\
+u
+u
+0a
+hello
+.
+$a
+world
+.
diff --git a/bin/ed/test/g5.d b/bin/ed/test/g5.d
new file mode 100644
index 0000000..a92d664
--- /dev/null
+++ b/bin/ed/test/g5.d
@@ -0,0 +1,3 @@
+line 1
+line 2
+line 3
diff --git a/bin/ed/test/g5.r b/bin/ed/test/g5.r
new file mode 100644
index 0000000..15a2675
--- /dev/null
+++ b/bin/ed/test/g5.r
@@ -0,0 +1,9 @@
+line 1
+line 2
+line 3
+line 2
+line 3
+line 1
+line 3
+line 1
+line 2
diff --git a/bin/ed/test/g5.t b/bin/ed/test/g5.t
new file mode 100644
index 0000000..e213481
--- /dev/null
+++ b/bin/ed/test/g5.t
@@ -0,0 +1,2 @@
+g/./1,3t$\
+1d
diff --git a/bin/ed/test/h.err b/bin/ed/test/h.err
new file mode 100644
index 0000000..a71e506
--- /dev/null
+++ b/bin/ed/test/h.err
@@ -0,0 +1 @@
+.h
diff --git a/bin/ed/test/i.d b/bin/ed/test/i.d
new file mode 100644
index 0000000..92f337e
--- /dev/null
+++ b/bin/ed/test/i.d
@@ -0,0 +1,5 @@
+line 1
+line 2
+line 3
+line 4
+line5
diff --git a/bin/ed/test/i.r b/bin/ed/test/i.r
new file mode 100644
index 0000000..5f27af0
--- /dev/null
+++ b/bin/ed/test/i.r
@@ -0,0 +1,8 @@
+hello world
+hello world!
+line 1
+line 2
+line 3
+line 4
+hello world!!
+line5
diff --git a/bin/ed/test/i.t b/bin/ed/test/i.t
new file mode 100644
index 0000000..d1d9805
--- /dev/null
+++ b/bin/ed/test/i.t
@@ -0,0 +1,9 @@
+1i
+hello world
+.
+2i
+hello world!
+.
+$i
+hello world!!
+.
diff --git a/bin/ed/test/i1.err b/bin/ed/test/i1.err
new file mode 100644
index 0000000..aaddede
--- /dev/null
+++ b/bin/ed/test/i1.err
@@ -0,0 +1,3 @@
+1,$i
+hello world
+.
diff --git a/bin/ed/test/i2.err b/bin/ed/test/i2.err
new file mode 100644
index 0000000..b63f5ac
--- /dev/null
+++ b/bin/ed/test/i2.err
@@ -0,0 +1,3 @@
+ii
+hello world
+.
diff --git a/bin/ed/test/i3.err b/bin/ed/test/i3.err
new file mode 100644
index 0000000..6d200c8
--- /dev/null
+++ b/bin/ed/test/i3.err
@@ -0,0 +1,3 @@
+0i
+hello world
+.
diff --git a/bin/ed/test/j.d b/bin/ed/test/j.d
new file mode 100644
index 0000000..92f337e
--- /dev/null
+++ b/bin/ed/test/j.d
@@ -0,0 +1,5 @@
+line 1
+line 2
+line 3
+line 4
+line5
diff --git a/bin/ed/test/j.r b/bin/ed/test/j.r
new file mode 100644
index 0000000..66f36a8
--- /dev/null
+++ b/bin/ed/test/j.r
@@ -0,0 +1,4 @@
+line 1
+line 2line 3
+line 4
+line5
diff --git a/bin/ed/test/j.t b/bin/ed/test/j.t
new file mode 100644
index 0000000..9b5d28d
--- /dev/null
+++ b/bin/ed/test/j.t
@@ -0,0 +1,2 @@
+1,1j
+2,3j
diff --git a/bin/ed/test/k.d b/bin/ed/test/k.d
new file mode 100644
index 0000000..92f337e
--- /dev/null
+++ b/bin/ed/test/k.d
@@ -0,0 +1,5 @@
+line 1
+line 2
+line 3
+line 4
+line5
diff --git a/bin/ed/test/k.r b/bin/ed/test/k.r
new file mode 100644
index 0000000..eeb38db
--- /dev/null
+++ b/bin/ed/test/k.r
@@ -0,0 +1,5 @@
+line 3
+hello world
+line 4
+line5
+line 2
diff --git a/bin/ed/test/k.t b/bin/ed/test/k.t
new file mode 100644
index 0000000..53d588d
--- /dev/null
+++ b/bin/ed/test/k.t
@@ -0,0 +1,10 @@
+2ka
+1d
+'am$
+1ka
+0a
+hello world
+.
+'ad
+u
+'am0
diff --git a/bin/ed/test/k1.err b/bin/ed/test/k1.err
new file mode 100644
index 0000000..eba1f3d
--- /dev/null
+++ b/bin/ed/test/k1.err
@@ -0,0 +1 @@
+1,$ka
diff --git a/bin/ed/test/k2.err b/bin/ed/test/k2.err
new file mode 100644
index 0000000..b34a18d
--- /dev/null
+++ b/bin/ed/test/k2.err
@@ -0,0 +1 @@
+kA
diff --git a/bin/ed/test/k3.err b/bin/ed/test/k3.err
new file mode 100644
index 0000000..70190c4
--- /dev/null
+++ b/bin/ed/test/k3.err
@@ -0,0 +1 @@
+0ka
diff --git a/bin/ed/test/k4.err b/bin/ed/test/k4.err
new file mode 100644
index 0000000..3457642
--- /dev/null
+++ b/bin/ed/test/k4.err
@@ -0,0 +1,6 @@
+a
+hello
+.
+.ka
+'ad
+'ap
diff --git a/bin/ed/test/l.d b/bin/ed/test/l.d
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/bin/ed/test/l.d
diff --git a/bin/ed/test/l.r b/bin/ed/test/l.r
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/bin/ed/test/l.r
diff --git a/bin/ed/test/l.t b/bin/ed/test/l.t
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/bin/ed/test/l.t
diff --git a/bin/ed/test/m.d b/bin/ed/test/m.d
new file mode 100644
index 0000000..92f337e
--- /dev/null
+++ b/bin/ed/test/m.d
@@ -0,0 +1,5 @@
+line 1
+line 2
+line 3
+line 4
+line5
diff --git a/bin/ed/test/m.err b/bin/ed/test/m.err
new file mode 100644
index 0000000..3aec4c3
--- /dev/null
+++ b/bin/ed/test/m.err
@@ -0,0 +1,4 @@
+a
+hello world
+.
+1,$m1
diff --git a/bin/ed/test/m.r b/bin/ed/test/m.r
new file mode 100644
index 0000000..186cf54
--- /dev/null
+++ b/bin/ed/test/m.r
@@ -0,0 +1,5 @@
+line5
+line 1
+line 2
+line 3
+line 4
diff --git a/bin/ed/test/m.t b/bin/ed/test/m.t
new file mode 100644
index 0000000..c39c088
--- /dev/null
+++ b/bin/ed/test/m.t
@@ -0,0 +1,7 @@
+1,2m$
+1,2m$
+1,2m$
+$m0
+$m0
+2,3m1
+2,3m3
diff --git a/bin/ed/test/mkscripts.sh b/bin/ed/test/mkscripts.sh
new file mode 100755
index 0000000..5e1a095
--- /dev/null
+++ b/bin/ed/test/mkscripts.sh
@@ -0,0 +1,75 @@
+#!/bin/sh -
+# $NetBSD: mkscripts.sh,v 1.10 1995/04/23 10:07:36 cgd Exp $
+#
+# This script generates ed test scripts (.ed) from .t files
+
+PATH="/bin:/usr/bin:/usr/local/bin/:."
+ED=$1
+[ ! -x $ED ] && { echo "$ED: cannot execute"; exit 1; }
+
+for i in *.t; do
+# base=${i%.*}
+# base=`echo $i | sed 's/\..*//'`
+# base=`expr $i : '\([^.]*\)'`
+# (
+# echo "#!/bin/sh -"
+# echo "$ED - <<\EOT"
+# echo "r $base.d"
+# cat $i
+# echo "w $base.o"
+# echo EOT
+# ) >$base.ed
+# chmod +x $base.ed
+# The following is pretty ugly way of doing the above, and not appropriate
+# use of ed but the point is that it can be done...
+ base=`$ED - \!"echo $i" <<-EOF
+ s/\..*
+ EOF`
+ $ED - <<-EOF
+ a
+ #!/bin/sh -
+ $ED - <<\EOT
+ H
+ r $base.d
+ w $base.o
+ EOT
+ .
+ -2r $i
+ w $base.ed
+ !chmod +x $base.ed
+ EOF
+done
+
+for i in *.err; do
+# base=${i%.*}
+# base=`echo $i | sed 's/\..*//'`
+# base=`expr $i : '\([^.]*\)'`
+# (
+# echo "#!/bin/sh -"
+# echo "$ED - <<\EOT"
+# echo H
+# echo "r $base.err"
+# cat $i
+# echo "w $base.o"
+# echo EOT
+# ) >$base-err.ed
+# chmod +x $base-err.ed
+# The following is pretty ugly way of doing the above, and not appropriate
+# use of ed but the point is that it can be done...
+ base=`$ED - \!"echo $i" <<-EOF
+ s/\..*
+ EOF`
+ $ED - <<-EOF
+ a
+ #!/bin/sh -
+ $ED - <<\EOT
+ H
+ r $base.err
+ w $base.o
+ EOT
+ .
+ -2r $i
+ w ${base}.red
+ !chmod +x ${base}.red
+ EOF
+done
diff --git a/bin/ed/test/n.d b/bin/ed/test/n.d
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/bin/ed/test/n.d
diff --git a/bin/ed/test/n.r b/bin/ed/test/n.r
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/bin/ed/test/n.r
diff --git a/bin/ed/test/n.t b/bin/ed/test/n.t
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/bin/ed/test/n.t
diff --git a/bin/ed/test/nl.err b/bin/ed/test/nl.err
new file mode 100644
index 0000000..8949a85
--- /dev/null
+++ b/bin/ed/test/nl.err
@@ -0,0 +1 @@
+,1
diff --git a/bin/ed/test/nl1.d b/bin/ed/test/nl1.d
new file mode 100644
index 0000000..92f337e
--- /dev/null
+++ b/bin/ed/test/nl1.d
@@ -0,0 +1,5 @@
+line 1
+line 2
+line 3
+line 4
+line5
diff --git a/bin/ed/test/nl1.r b/bin/ed/test/nl1.r
new file mode 100644
index 0000000..9d8854c
--- /dev/null
+++ b/bin/ed/test/nl1.r
@@ -0,0 +1,8 @@
+
+
+hello world
+line 1
+line 2
+line 3
+line 4
+line5
diff --git a/bin/ed/test/nl1.t b/bin/ed/test/nl1.t
new file mode 100644
index 0000000..ea192e9
--- /dev/null
+++ b/bin/ed/test/nl1.t
@@ -0,0 +1,8 @@
+1
+
+
+0a
+
+
+hello world
+.
diff --git a/bin/ed/test/nl2.d b/bin/ed/test/nl2.d
new file mode 100644
index 0000000..92f337e
--- /dev/null
+++ b/bin/ed/test/nl2.d
@@ -0,0 +1,5 @@
+line 1
+line 2
+line 3
+line 4
+line5
diff --git a/bin/ed/test/nl2.r b/bin/ed/test/nl2.r
new file mode 100644
index 0000000..fe99e41
--- /dev/null
+++ b/bin/ed/test/nl2.r
@@ -0,0 +1,6 @@
+line 1
+line 2
+line 3
+line 4
+line5
+hello world
diff --git a/bin/ed/test/nl2.t b/bin/ed/test/nl2.t
new file mode 100644
index 0000000..73fd27b
--- /dev/null
+++ b/bin/ed/test/nl2.t
@@ -0,0 +1,4 @@
+a
+hello world
+.
+0;/./
diff --git a/bin/ed/test/p.d b/bin/ed/test/p.d
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/bin/ed/test/p.d
diff --git a/bin/ed/test/p.r b/bin/ed/test/p.r
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/bin/ed/test/p.r
diff --git a/bin/ed/test/p.t b/bin/ed/test/p.t
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/bin/ed/test/p.t
diff --git a/bin/ed/test/q.d b/bin/ed/test/q.d
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/bin/ed/test/q.d
diff --git a/bin/ed/test/q.r b/bin/ed/test/q.r
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/bin/ed/test/q.r
diff --git a/bin/ed/test/q.t b/bin/ed/test/q.t
new file mode 100644
index 0000000..123a2c8
--- /dev/null
+++ b/bin/ed/test/q.t
@@ -0,0 +1,5 @@
+w q.o
+a
+hello
+.
+q
diff --git a/bin/ed/test/q1.err b/bin/ed/test/q1.err
new file mode 100644
index 0000000..0a7e178
--- /dev/null
+++ b/bin/ed/test/q1.err
@@ -0,0 +1 @@
+.q
diff --git a/bin/ed/test/r1.d b/bin/ed/test/r1.d
new file mode 100644
index 0000000..92f337e
--- /dev/null
+++ b/bin/ed/test/r1.d
@@ -0,0 +1,5 @@
+line 1
+line 2
+line 3
+line 4
+line5
diff --git a/bin/ed/test/r1.err b/bin/ed/test/r1.err
new file mode 100644
index 0000000..269aa7c
--- /dev/null
+++ b/bin/ed/test/r1.err
@@ -0,0 +1 @@
+1,$r r1.err
diff --git a/bin/ed/test/r1.r b/bin/ed/test/r1.r
new file mode 100644
index 0000000..a3ff506
--- /dev/null
+++ b/bin/ed/test/r1.r
@@ -0,0 +1,7 @@
+line 1
+hello world
+line 2
+line 3
+line 4
+line5
+hello world
diff --git a/bin/ed/test/r1.t b/bin/ed/test/r1.t
new file mode 100644
index 0000000..d787a92
--- /dev/null
+++ b/bin/ed/test/r1.t
@@ -0,0 +1,3 @@
+1;r !echo hello world
+1
+r !echo hello world
diff --git a/bin/ed/test/r2.d b/bin/ed/test/r2.d
new file mode 100644
index 0000000..92f337e
--- /dev/null
+++ b/bin/ed/test/r2.d
@@ -0,0 +1,5 @@
+line 1
+line 2
+line 3
+line 4
+line5
diff --git a/bin/ed/test/r2.err b/bin/ed/test/r2.err
new file mode 100644
index 0000000..1c44fa3
--- /dev/null
+++ b/bin/ed/test/r2.err
@@ -0,0 +1 @@
+r a-good-book
diff --git a/bin/ed/test/r2.r b/bin/ed/test/r2.r
new file mode 100644
index 0000000..ac152ba
--- /dev/null
+++ b/bin/ed/test/r2.r
@@ -0,0 +1,10 @@
+line 1
+line 2
+line 3
+line 4
+line5
+line 1
+line 2
+line 3
+line 4
+line5
diff --git a/bin/ed/test/r2.t b/bin/ed/test/r2.t
new file mode 100644
index 0000000..4286f42
--- /dev/null
+++ b/bin/ed/test/r2.t
@@ -0,0 +1 @@
+r
diff --git a/bin/ed/test/r3.d b/bin/ed/test/r3.d
new file mode 100644
index 0000000..593eec6
--- /dev/null
+++ b/bin/ed/test/r3.d
@@ -0,0 +1 @@
+r r3.t
diff --git a/bin/ed/test/r3.r b/bin/ed/test/r3.r
new file mode 100644
index 0000000..86d5f90
--- /dev/null
+++ b/bin/ed/test/r3.r
@@ -0,0 +1,2 @@
+r r3.t
+r r3.t
diff --git a/bin/ed/test/r3.t b/bin/ed/test/r3.t
new file mode 100644
index 0000000..593eec6
--- /dev/null
+++ b/bin/ed/test/r3.t
@@ -0,0 +1 @@
+r r3.t
diff --git a/bin/ed/test/s1.d b/bin/ed/test/s1.d
new file mode 100644
index 0000000..92f337e
--- /dev/null
+++ b/bin/ed/test/s1.d
@@ -0,0 +1,5 @@
+line 1
+line 2
+line 3
+line 4
+line5
diff --git a/bin/ed/test/s1.err b/bin/ed/test/s1.err
new file mode 100644
index 0000000..d7ca0cf
--- /dev/null
+++ b/bin/ed/test/s1.err
@@ -0,0 +1 @@
+s . x
diff --git a/bin/ed/test/s1.r b/bin/ed/test/s1.r
new file mode 100644
index 0000000..4eb0980
--- /dev/null
+++ b/bin/ed/test/s1.r
@@ -0,0 +1,5 @@
+liene 1
+(liene) (2)
+(liene) (3)
+liene (4)
+(()liene5)
diff --git a/bin/ed/test/s1.t b/bin/ed/test/s1.t
new file mode 100644
index 0000000..b0028bb
--- /dev/null
+++ b/bin/ed/test/s1.t
@@ -0,0 +1,6 @@
+s/\([^ ][^ ]*\)/(\1)/g
+2s
+/3/s
+/\(4\)/sr
+/\(.\)/srg
+%s/i/&e/
diff --git a/bin/ed/test/s10.err b/bin/ed/test/s10.err
new file mode 100644
index 0000000..0d8d83d
--- /dev/null
+++ b/bin/ed/test/s10.err
@@ -0,0 +1,4 @@
+a
+hello
+.
+s/[h[.]/x/
diff --git a/bin/ed/test/s2.d b/bin/ed/test/s2.d
new file mode 100644
index 0000000..92f337e
--- /dev/null
+++ b/bin/ed/test/s2.d
@@ -0,0 +1,5 @@
+line 1
+line 2
+line 3
+line 4
+line5
diff --git a/bin/ed/test/s2.err b/bin/ed/test/s2.err
new file mode 100644
index 0000000..b5c851d
--- /dev/null
+++ b/bin/ed/test/s2.err
@@ -0,0 +1,4 @@
+a
+a
+.
+s/x*/a/g
diff --git a/bin/ed/test/s2.r b/bin/ed/test/s2.r
new file mode 100644
index 0000000..ca305c8
--- /dev/null
+++ b/bin/ed/test/s2.r
@@ -0,0 +1,5 @@
+li(n)e 1
+i(n)e 200
+li(n)e 3
+li(n)e 4
+li(n)e500
diff --git a/bin/ed/test/s2.t b/bin/ed/test/s2.t
new file mode 100644
index 0000000..f365849
--- /dev/null
+++ b/bin/ed/test/s2.t
@@ -0,0 +1,4 @@
+,s/./(&)/3
+s/$/00
+2s//%/g
+s/^l
diff --git a/bin/ed/test/s3.d b/bin/ed/test/s3.d
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/bin/ed/test/s3.d
diff --git a/bin/ed/test/s3.err b/bin/ed/test/s3.err
new file mode 100644
index 0000000..d68c7d0
--- /dev/null
+++ b/bin/ed/test/s3.err
@@ -0,0 +1 @@
+s/[xyx/a/
diff --git a/bin/ed/test/s3.r b/bin/ed/test/s3.r
new file mode 100644
index 0000000..d6cada2
--- /dev/null
+++ b/bin/ed/test/s3.r
@@ -0,0 +1 @@
+hello world
diff --git a/bin/ed/test/s3.t b/bin/ed/test/s3.t
new file mode 100644
index 0000000..fbf8803
--- /dev/null
+++ b/bin/ed/test/s3.t
@@ -0,0 +1,6 @@
+a
+hello/[]world
+.
+s/[/]/ /
+s/[[:digit:][]/ /
+s/[]]/ /
diff --git a/bin/ed/test/s4.err b/bin/ed/test/s4.err
new file mode 100644
index 0000000..35b609f
--- /dev/null
+++ b/bin/ed/test/s4.err
@@ -0,0 +1 @@
+s/\a\b\c/xyz/
diff --git a/bin/ed/test/s5.err b/bin/ed/test/s5.err
new file mode 100644
index 0000000..89104c5
--- /dev/null
+++ b/bin/ed/test/s5.err
@@ -0,0 +1 @@
+s//xyz/
diff --git a/bin/ed/test/s6.err b/bin/ed/test/s6.err
new file mode 100644
index 0000000..b478595
--- /dev/null
+++ b/bin/ed/test/s6.err
@@ -0,0 +1 @@
+s
diff --git a/bin/ed/test/s7.err b/bin/ed/test/s7.err
new file mode 100644
index 0000000..30ba4fd
--- /dev/null
+++ b/bin/ed/test/s7.err
@@ -0,0 +1,5 @@
+a
+hello world
+.
+/./
+sr
diff --git a/bin/ed/test/s8.err b/bin/ed/test/s8.err
new file mode 100644
index 0000000..5665767
--- /dev/null
+++ b/bin/ed/test/s8.err
@@ -0,0 +1,4 @@
+a
+hello
+.
+s/[h[=]/x/
diff --git a/bin/ed/test/s9.err b/bin/ed/test/s9.err
new file mode 100644
index 0000000..1ff16dd
--- /dev/null
+++ b/bin/ed/test/s9.err
@@ -0,0 +1,4 @@
+a
+hello
+.
+s/[h[:]/x/
diff --git a/bin/ed/test/t.d b/bin/ed/test/t.d
new file mode 100644
index 0000000..92f337e
--- /dev/null
+++ b/bin/ed/test/t.d
@@ -0,0 +1,5 @@
+line 1
+line 2
+line 3
+line 4
+line5
diff --git a/bin/ed/test/t.r b/bin/ed/test/t.r
new file mode 100644
index 0000000..2b28547
--- /dev/null
+++ b/bin/ed/test/t.r
@@ -0,0 +1,16 @@
+line 1
+line 1
+line 1
+line 2
+line 2
+line 3
+line 4
+line5
+line 1
+line 1
+line 1
+line 2
+line 2
+line 3
+line 4
+line5
diff --git a/bin/ed/test/t1.d b/bin/ed/test/t1.d
new file mode 100644
index 0000000..92f337e
--- /dev/null
+++ b/bin/ed/test/t1.d
@@ -0,0 +1,5 @@
+line 1
+line 2
+line 3
+line 4
+line5
diff --git a/bin/ed/test/t1.err b/bin/ed/test/t1.err
new file mode 100644
index 0000000..c49c556
--- /dev/null
+++ b/bin/ed/test/t1.err
@@ -0,0 +1 @@
+tt
diff --git a/bin/ed/test/t1.r b/bin/ed/test/t1.r
new file mode 100644
index 0000000..2b28547
--- /dev/null
+++ b/bin/ed/test/t1.r
@@ -0,0 +1,16 @@
+line 1
+line 1
+line 1
+line 2
+line 2
+line 3
+line 4
+line5
+line 1
+line 1
+line 1
+line 2
+line 2
+line 3
+line 4
+line5
diff --git a/bin/ed/test/t1.t b/bin/ed/test/t1.t
new file mode 100644
index 0000000..6b66163
--- /dev/null
+++ b/bin/ed/test/t1.t
@@ -0,0 +1,3 @@
+1t0
+2,3t2
+,t$
diff --git a/bin/ed/test/t2.d b/bin/ed/test/t2.d
new file mode 100644
index 0000000..92f337e
--- /dev/null
+++ b/bin/ed/test/t2.d
@@ -0,0 +1,5 @@
+line 1
+line 2
+line 3
+line 4
+line5
diff --git a/bin/ed/test/t2.err b/bin/ed/test/t2.err
new file mode 100644
index 0000000..c202051
--- /dev/null
+++ b/bin/ed/test/t2.err
@@ -0,0 +1 @@
+t0;-1
diff --git a/bin/ed/test/t2.r b/bin/ed/test/t2.r
new file mode 100644
index 0000000..0c75ff5
--- /dev/null
+++ b/bin/ed/test/t2.r
@@ -0,0 +1,6 @@
+line 1
+line5
+line 2
+line 3
+line 4
+line5
diff --git a/bin/ed/test/t2.t b/bin/ed/test/t2.t
new file mode 100644
index 0000000..5175abd
--- /dev/null
+++ b/bin/ed/test/t2.t
@@ -0,0 +1 @@
+t0;/./
diff --git a/bin/ed/test/u.d b/bin/ed/test/u.d
new file mode 100644
index 0000000..92f337e
--- /dev/null
+++ b/bin/ed/test/u.d
@@ -0,0 +1,5 @@
+line 1
+line 2
+line 3
+line 4
+line5
diff --git a/bin/ed/test/u.err b/bin/ed/test/u.err
new file mode 100644
index 0000000..caa1ba1
--- /dev/null
+++ b/bin/ed/test/u.err
@@ -0,0 +1 @@
+.u
diff --git a/bin/ed/test/u.r b/bin/ed/test/u.r
new file mode 100644
index 0000000..ad558d8
--- /dev/null
+++ b/bin/ed/test/u.r
@@ -0,0 +1,9 @@
+line 1
+hello
+hello world!!
+line 2
+line 3
+line 4
+line5
+hello
+hello world!!
diff --git a/bin/ed/test/u.t b/bin/ed/test/u.t
new file mode 100644
index 0000000..131cb6e
--- /dev/null
+++ b/bin/ed/test/u.t
@@ -0,0 +1,31 @@
+1;r u.t
+u
+a
+hello
+world
+.
+g/./s//x/\
+a\
+hello\
+world
+u
+u
+u
+a
+hello world!
+.
+u
+1,$d
+u
+2,3d
+u
+c
+hello world!!
+.
+u
+u
+-1;.,+1j
+u
+u
+u
+.,+1t$
diff --git a/bin/ed/test/v.d b/bin/ed/test/v.d
new file mode 100644
index 0000000..92f337e
--- /dev/null
+++ b/bin/ed/test/v.d
@@ -0,0 +1,5 @@
+line 1
+line 2
+line 3
+line 4
+line5
diff --git a/bin/ed/test/v.r b/bin/ed/test/v.r
new file mode 100644
index 0000000..714db63
--- /dev/null
+++ b/bin/ed/test/v.r
@@ -0,0 +1,11 @@
+line5
+order
+hello world
+line 1
+order
+line 2
+order
+line 3
+order
+line 4
+order
diff --git a/bin/ed/test/v.t b/bin/ed/test/v.t
new file mode 100644
index 0000000..608a77f
--- /dev/null
+++ b/bin/ed/test/v.t
@@ -0,0 +1,6 @@
+v/[ ]/m0
+v/[ ]/s/$/\
+hello world
+v/hello /s/lo/p!/\
+a\
+order
diff --git a/bin/ed/test/w.d b/bin/ed/test/w.d
new file mode 100644
index 0000000..92f337e
--- /dev/null
+++ b/bin/ed/test/w.d
@@ -0,0 +1,5 @@
+line 1
+line 2
+line 3
+line 4
+line5
diff --git a/bin/ed/test/w.r b/bin/ed/test/w.r
new file mode 100644
index 0000000..ac152ba
--- /dev/null
+++ b/bin/ed/test/w.r
@@ -0,0 +1,10 @@
+line 1
+line 2
+line 3
+line 4
+line5
+line 1
+line 2
+line 3
+line 4
+line5
diff --git a/bin/ed/test/w.t b/bin/ed/test/w.t
new file mode 100644
index 0000000..c2e18bd
--- /dev/null
+++ b/bin/ed/test/w.t
@@ -0,0 +1,2 @@
+w !cat >\!.z
+r \!.z
diff --git a/bin/ed/test/w1.err b/bin/ed/test/w1.err
new file mode 100644
index 0000000..e2c8a60
--- /dev/null
+++ b/bin/ed/test/w1.err
@@ -0,0 +1 @@
+w /to/some/far-away/place
diff --git a/bin/ed/test/w2.err b/bin/ed/test/w2.err
new file mode 100644
index 0000000..9daf89c
--- /dev/null
+++ b/bin/ed/test/w2.err
@@ -0,0 +1 @@
+ww.o
diff --git a/bin/ed/test/w3.err b/bin/ed/test/w3.err
new file mode 100644
index 0000000..39bbf4c
--- /dev/null
+++ b/bin/ed/test/w3.err
@@ -0,0 +1 @@
+wqp w.o
diff --git a/bin/ed/test/x.err b/bin/ed/test/x.err
new file mode 100644
index 0000000..0953f01
--- /dev/null
+++ b/bin/ed/test/x.err
@@ -0,0 +1 @@
+.x
diff --git a/bin/ed/test/z.err b/bin/ed/test/z.err
new file mode 100644
index 0000000..6a51a2d
--- /dev/null
+++ b/bin/ed/test/z.err
@@ -0,0 +1,2 @@
+z
+z
diff --git a/bin/ed/undo.c b/bin/ed/undo.c
new file mode 100644
index 0000000..b17eac9
--- /dev/null
+++ b/bin/ed/undo.c
@@ -0,0 +1,150 @@
+/* $NetBSD: undo.c,v 1.7 2019/01/04 19:13:58 maya Exp $ */
+
+/* undo.c: This file contains the undo routines for the ed line editor */
+/*-
+ * Copyright (c) 1993 Andrew Moore, Talke Studio.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+#ifndef lint
+#if 0
+static char *rcsid = "@(#)undo.c,v 1.1 1994/02/01 00:34:44 alm Exp";
+#else
+__RCSID("$NetBSD: undo.c,v 1.7 2019/01/04 19:13:58 maya Exp $");
+#endif
+#endif /* not lint */
+
+#include "ed.h"
+
+
+#define USIZE 100 /* undo stack size */
+undo_t *ustack = NULL; /* undo stack */
+long usize = 0; /* stack size variable */
+long u_p = 0; /* undo stack pointer */
+
+/* push_undo_stack: return pointer to initialized undo node */
+undo_t *
+push_undo_stack(int type, long from, long to)
+{
+ undo_t *t;
+
+ t = ustack;
+ if (u_p < usize ||
+ (t = (undo_t *) realloc(ustack, (usize += USIZE) * sizeof(undo_t))) != NULL) {
+ ustack = t;
+ ustack[u_p].type = type;
+ ustack[u_p].t = get_addressed_line_node(to);
+ ustack[u_p].h = get_addressed_line_node(from);
+ return ustack + u_p++;
+ }
+ /* out of memory - release undo stack */
+ fprintf(stderr, "%s\n", strerror(errno));
+ seterrmsg("out of memory");
+ clear_undo_stack();
+ free(ustack);
+ ustack = NULL;
+ usize = 0;
+ return NULL;
+}
+
+
+/* USWAP: swap undo nodes */
+#define USWAP(x,y) { \
+ undo_t utmp; \
+ utmp = x, x = y, y = utmp; \
+}
+
+
+long u_current_addr = -1; /* if >= 0, undo enabled */
+long u_addr_last = -1; /* if >= 0, undo enabled */
+
+/* pop_undo_stack: undo last change to the editor buffer */
+int
+pop_undo_stack(void)
+{
+ long n;
+ long o_current_addr = current_addr;
+ long o_addr_last = addr_last;
+
+ if (u_current_addr == -1 || u_addr_last == -1) {
+ seterrmsg("nothing to undo");
+ return ERR;
+ } else if (u_p)
+ modified = 1;
+ get_addressed_line_node(0); /* this get_addressed_line_node last! */
+ SPL1();
+ for (n = u_p; n-- > 0;) {
+ switch(ustack[n].type) {
+ case UADD:
+ REQUE(ustack[n].h->q_back, ustack[n].t->q_forw);
+ break;
+ case UDEL:
+ REQUE(ustack[n].h->q_back, ustack[n].h);
+ REQUE(ustack[n].t, ustack[n].t->q_forw);
+ break;
+ case UMOV:
+ case VMOV:
+ REQUE(ustack[n - 1].h, ustack[n].h->q_forw);
+ REQUE(ustack[n].t->q_back, ustack[n - 1].t);
+ REQUE(ustack[n].h, ustack[n].t);
+ n--;
+ break;
+ default:
+ /*NOTREACHED*/
+ ;
+ }
+ ustack[n].type ^= 1;
+ }
+ /* reverse undo stack order */
+ for (n = u_p; n-- > (u_p + 1)/ 2;)
+ USWAP(ustack[n], ustack[u_p - 1 - n]);
+ if (isglobal)
+ clear_active_list();
+ current_addr = u_current_addr, u_current_addr = o_current_addr;
+ addr_last = u_addr_last, u_addr_last = o_addr_last;
+ SPL0();
+ return 0;
+}
+
+
+/* clear_undo_stack: clear the undo stack */
+void
+clear_undo_stack(void)
+{
+ line_t *lp, *ep, *tl;
+
+ while (u_p--)
+ if (ustack[u_p].type == UDEL) {
+ ep = ustack[u_p].t->q_forw;
+ for (lp = ustack[u_p].h; lp != ep; lp = tl) {
+ unmark_line_node(lp);
+ tl = lp->q_forw;
+ free(lp);
+ }
+ }
+ u_p = 0;
+ u_current_addr = current_addr;
+ u_addr_last = addr_last;
+}
diff --git a/bin/expr/expr.1 b/bin/expr/expr.1
new file mode 100644
index 0000000..74d0469
--- /dev/null
+++ b/bin/expr/expr.1
@@ -0,0 +1,280 @@
+.\" $NetBSD: expr.1,v 1.37 2017/07/03 21:33:23 wiz Exp $
+.\"
+.\" Copyright (c) 2000,2003 The NetBSD Foundation, Inc.
+.\" All rights reserved.
+.\"
+.\" This code is derived from software contributed to The NetBSD Foundation
+.\" by J.T. Conklin <jtc@NetBSD.org> and Jaromir Dolecek <jdolecek@NetBSD.org>.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\" notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must reproduce the above copyright
+.\" notice, this list of conditions and the following disclaimer in the
+.\" documentation and/or other materials provided with the distribution.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
+.\" ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+.\" TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+.\" PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
+.\" BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+.\" CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+.\" SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+.\" INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+.\" CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+.\" ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+.\" POSSIBILITY OF SUCH DAMAGE.
+.\"
+.Dd August 23, 2016
+.Dt EXPR 1
+.Os
+.Sh NAME
+.Nm expr
+.Nd evaluate expression
+.Sh SYNOPSIS
+.Nm
+.Ar expression
+.Sh DESCRIPTION
+The
+.Nm
+utility evaluates
+.Ar expression
+and writes the result on standard output.
+.Pp
+All operators are separate arguments to the
+.Nm
+utility.
+Characters special to the command interpreter must be escaped.
+.Pp
+Operators are listed below in order of increasing precedence.
+Operators with equal precedence are grouped within { } symbols.
+.Bl -tag -width indent
+.It Ar expr1 Li \&| Ar expr2
+Returns the evaluation of
+.Ar expr1
+if it is neither an empty string nor zero;
+otherwise, returns the evaluation of
+.Ar expr2 .
+.It Ar expr1 Li & Ar expr2
+Returns the evaluation of
+.Ar expr1
+if neither expression evaluates to an empty string or zero;
+otherwise, returns zero.
+.It Ar expr1 Li "{=, >, \*[Ge], <, \*[Le], !=}" Ar expr2
+Returns the results of integer comparison if both arguments are integers;
+otherwise, returns the results of string comparison using the locale-specific
+collation sequence.
+The result of each comparison is 1 if the specified relation is true,
+or 0 if the relation is false.
+.It Ar expr1 Li "{+, -}" Ar expr2
+Returns the results of addition or subtraction of integer-valued arguments.
+.It Ar expr1 Li "{*, /, %}" Ar expr2
+Returns the results of multiplication, integer division, or remainder of integer-valued arguments.
+.It Ar expr1 Li \&: Ar expr2
+The
+.Dq \&:
+operator matches
+.Ar expr1
+against
+.Ar expr2 ,
+which must be a regular expression.
+The regular expression is anchored
+to the beginning of the string with an implicit
+.Dq ^ .
+.Pp
+If the match succeeds and the pattern contains at least one regular
+expression subexpression
+.Dq "\e(...\e)" ,
+the string corresponding to
+.Dq "\e1"
+is returned;
+otherwise the matching operator returns the number of characters matched.
+If the match fails and the pattern contains a regular expression subexpression
+the null string is returned;
+otherwise 0.
+.It "( " Ar expr No " )"
+Parentheses are used for grouping in the usual manner.
+.El
+.Pp
+Additionally, the following keywords are recognized:
+.Bl -tag -width indent
+.It length Ar expr
+Returns the length of the specified string in bytes.
+.El
+.Pp
+Operator precedence (from highest to lowest):
+.Bl -enum -compact -offset indent
+.It
+parentheses
+.It
+length
+.It
+.Dq \&:
+.It
+.Dq "*" ,
+.Dq "/" ,
+and
+.Dq "%"
+.It
+.Dq "+"
+and
+.Dq "-"
+.It
+compare operators
+.It
+.Dq &
+.It
+.Dq \&|
+.El
+.Sh EXIT STATUS
+The
+.Nm
+utility exits with one of the following values:
+.Bl -tag -width Ds -compact
+.It 0
+the expression is neither an empty string nor 0.
+.It 1
+the expression is an empty string or 0.
+.It 2
+the expression is invalid.
+.It >2
+an error occurred (such as memory allocation failure).
+.El
+.Sh EXAMPLES
+.Bl -enum
+.It
+The following example adds one to variable
+.Dq a :
+.Dl a=`expr $a + 1`
+.It
+The following example returns zero, due to subtraction having higher precedence
+than the
+.Dq &
+operator:
+.Dl expr 1 '&' 1 - 1
+.It
+The following example returns the filename portion of a pathname stored
+in variable
+.Dq a :
+.Dl expr "/$a" Li : '.*/\e(.*\e)'
+.It
+The following example returns the number of characters in variable
+.Dq a :
+.Dl expr $a Li : '.*'
+.El
+.Sh COMPATIBILITY
+This implementation of
+.Nm
+internally uses 64 bit representation of integers and checks for
+over- and underflows.
+It also treats
+.Dq /
+(the division mark) and option
+.Dq --
+correctly depending upon context.
+.Pp
+.Nm
+on other systems (including
+.Nx
+up to and including
+.Nx 1.5 )
+might not be so graceful.
+Arithmetic results might be arbitrarily
+limited on such systems, most commonly to 32 bit quantities.
+This means such
+.Nm
+can only process values between -2147483648 and +2147483647.
+.Pp
+On other systems,
+.Nm
+might also not work correctly for regular expressions where
+either side contains
+.Dq /
+(a single forward slash), like this:
+.Bd -literal -offset indent
+expr / : '.*/\e(.*\e)'
+.Ed
+.Pp
+If this is the case, you might use
+.Dq //
+(a double forward slash)
+to avoid confusion with the division operator:
+.Bd -literal -offset indent
+expr "//$a" : '.*/\e(.*\e)'
+.Ed
+.Pp
+According to
+.St -p1003.2 ,
+.Nm
+has to recognize special option
+.Dq -- ,
+treat it as a delimiter to mark the end of command
+line options, and ignore it.
+Some
+.Nm
+implementations do not recognize it at all; others
+might ignore it even in cases where doing so results in syntax
+error.
+There should be same result for both following examples,
+but it might not always be:
+.Bl -enum -compact -offset indent
+.It
+expr -- : .
+.It
+expr -- -- : .
+.El
+Although
+.Nx
+.Nm
+handles both cases correctly, you should not depend on this behavior
+for portability reasons and avoid passing a bare
+.Dq --
+as the first
+argument.
+.Sh STANDARDS
+The
+.Nm
+utility conforms to
+.St -p1003.2 .
+The
+.Ar length
+keyword is an extension for compatibility with GNU
+.Nm .
+.Sh HISTORY
+An
+.Nm
+utility first appeared in the Programmer's Workbench (PWB/UNIX).
+A public domain version of
+.Nm
+written by
+.An Pace Willisson
+.Aq pace@blitz.com
+appeared in
+.Bx 386 0.1 .
+.Sh AUTHORS
+Initial implementation by
+.An Pace Willisson Aq Mt pace@blitz.com
+was largely rewritten by
+.An -nosplit
+.An J.T. Conklin Aq Mt jtc@NetBSD.org .
+It was rewritten again for
+.Nx 1.6
+by
+.An -nosplit
+.An Jaromir Dolecek Aq Mt jdolecek@NetBSD.org .
+.Sh NOTES
+The empty string
+.Do Dc
+cannot be matched with the intuitive:
+.Bd -literal -offset indent
+expr '' : '$'
+.Ed
+.Pp
+The reason is that the returned number of matched characters (zero)
+is indistinguishable from a failed match, so this returns failure.
+To match the empty string, use something like:
+.Bd -literal -offset indent
+expr x'' : 'x$'
+.Ed
diff --git a/bin/expr/expr.y b/bin/expr/expr.y
new file mode 100644
index 0000000..2bc4ad1
--- /dev/null
+++ b/bin/expr/expr.y
@@ -0,0 +1,467 @@
+/* $NetBSD: expr.y,v 1.45 2018/06/27 17:23:36 kamil Exp $ */
+
+/*_
+ * Copyright (c) 2000 The NetBSD Foundation, Inc.
+ * All rights reserved.
+ *
+ * This code is derived from software contributed to The NetBSD Foundation
+ * by Jaromir Dolecek <jdolecek@NetBSD.org> and J.T. Conklin <jtc@NetBSD.org>.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
+ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+%{
+#include <sys/cdefs.h>
+#ifndef lint
+__RCSID("$NetBSD: expr.y,v 1.45 2018/06/27 17:23:36 kamil Exp $");
+#endif /* not lint */
+
+#include <sys/types.h>
+
+#include <err.h>
+#include <errno.h>
+#include <limits.h>
+#include <locale.h>
+#include <regex.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+static const char * const *av;
+
+static void yyerror(const char *, ...) __dead;
+static int yylex(void);
+static int is_zero_or_null(const char *);
+static int is_integer(const char *);
+static int64_t perform_arith_op(const char *, const char *, const char *);
+
+#define YYSTYPE const char *
+
+%}
+%token STRING
+%left SPEC_OR
+%left SPEC_AND
+%left COMPARE
+%left ADD_SUB_OPERATOR
+%left MUL_DIV_MOD_OPERATOR
+%left SPEC_REG
+%left LENGTH
+%left LEFT_PARENT RIGHT_PARENT
+
+%%
+
+exp: expr = {
+ (void) printf("%s\n", $1);
+ return (is_zero_or_null($1));
+ }
+ ;
+
+expr: item { $$ = $1; }
+ | expr SPEC_OR expr = {
+ /*
+ * Return evaluation of first expression if it is neither
+ * an empty string nor zero; otherwise, returns the evaluation
+ * of second expression.
+ */
+ if (!is_zero_or_null($1))
+ $$ = $1;
+ else
+ $$ = $3;
+ }
+ | expr SPEC_AND expr = {
+ /*
+ * Returns the evaluation of first expr if neither expression
+ * evaluates to an empty string or zero; otherwise, returns
+ * zero.
+ */
+ if (!is_zero_or_null($1) && !is_zero_or_null($3))
+ $$ = $1;
+ else
+ $$ = "0";
+ }
+ | expr SPEC_REG expr = {
+ /*
+ * The ``:'' operator matches first expr against the second,
+ * which must be a regular expression.
+ */
+ regex_t rp;
+ regmatch_t rm[2];
+ int eval;
+
+ /* compile regular expression */
+ if ((eval = regcomp(&rp, $3, REG_BASIC)) != 0) {
+ char errbuf[256];
+ (void)regerror(eval, &rp, errbuf, sizeof(errbuf));
+ yyerror("%s", errbuf);
+ /* NOT REACHED */
+ }
+
+ /* compare string against pattern -- remember that patterns
+ are anchored to the beginning of the line */
+ if (regexec(&rp, $1, 2, rm, 0) == 0 && rm[0].rm_so == 0) {
+ char *val;
+ if (rm[1].rm_so >= 0) {
+ (void) asprintf(&val, "%.*s",
+ (int) (rm[1].rm_eo - rm[1].rm_so),
+ $1 + rm[1].rm_so);
+ } else {
+ (void) asprintf(&val, "%d",
+ (int)(rm[0].rm_eo - rm[0].rm_so));
+ }
+ if (val == NULL)
+ err(1, NULL);
+ $$ = val;
+ } else {
+ if (rp.re_nsub == 0) {
+ $$ = "0";
+ } else {
+ $$ = "";
+ }
+ }
+
+ }
+ | expr ADD_SUB_OPERATOR expr = {
+ /* Returns the results of addition, subtraction */
+ char *val;
+ int64_t res;
+
+ res = perform_arith_op($1, $2, $3);
+ (void) asprintf(&val, "%lld", (long long int) res);
+ if (val == NULL)
+ err(1, NULL);
+ $$ = val;
+ }
+
+ | expr MUL_DIV_MOD_OPERATOR expr = {
+ /*
+ * Returns the results of multiply, divide or remainder of
+ * numeric-valued arguments.
+ */
+ char *val;
+ int64_t res;
+
+ res = perform_arith_op($1, $2, $3);
+ (void) asprintf(&val, "%lld", (long long int) res);
+ if (val == NULL)
+ err(1, NULL);
+ $$ = val;
+
+ }
+ | expr COMPARE expr = {
+ /*
+ * Returns the results of integer comparison if both arguments
+ * are integers; otherwise, returns the results of string
+ * comparison using the locale-specific collation sequence.
+ * The result of each comparison is 1 if the specified relation
+ * is true, or 0 if the relation is false.
+ */
+
+ int64_t l, r;
+ int res;
+
+ res = 0;
+
+ /*
+ * Slight hack to avoid differences in the compare code
+ * between string and numeric compare.
+ */
+ if (is_integer($1) && is_integer($3)) {
+ /* numeric comparison */
+ l = strtoll($1, NULL, 10);
+ r = strtoll($3, NULL, 10);
+ } else {
+ /* string comparison */
+ l = strcoll($1, $3);
+ r = 0;
+ }
+
+ switch($2[0]) {
+ case '=': /* equal */
+ res = (l == r);
+ break;
+ case '>': /* greater or greater-equal */
+ if ($2[1] == '=')
+ res = (l >= r);
+ else
+ res = (l > r);
+ break;
+ case '<': /* lower or lower-equal */
+ if ($2[1] == '=')
+ res = (l <= r);
+ else
+ res = (l < r);
+ break;
+ case '!': /* not equal */
+ /* the check if this is != was done in yylex() */
+ res = (l != r);
+ }
+
+ $$ = (res) ? "1" : "0";
+
+ }
+ | LEFT_PARENT expr RIGHT_PARENT { $$ = $2; }
+ | LENGTH expr {
+ /*
+ * Return length of 'expr' in bytes.
+ */
+ char *ln;
+
+ asprintf(&ln, "%ld", (long) strlen($2));
+ if (ln == NULL)
+ err(1, NULL);
+ $$ = ln;
+ }
+ ;
+
+item: STRING
+ | ADD_SUB_OPERATOR
+ | MUL_DIV_MOD_OPERATOR
+ | COMPARE
+ | SPEC_OR
+ | SPEC_AND
+ | SPEC_REG
+ | LENGTH
+ ;
+%%
+
+/*
+ * Returns 1 if the string is empty or contains only numeric zero.
+ */
+static int
+is_zero_or_null(const char *str)
+{
+ char *endptr;
+
+ return str[0] == '\0'
+ || ( strtoll(str, &endptr, 10) == 0LL
+ && endptr[0] == '\0');
+}
+
+/*
+ * Returns 1 if the string is an integer.
+ */
+static int
+is_integer(const char *str)
+{
+ char *endptr;
+
+ (void) strtoll(str, &endptr, 10);
+ /* note we treat empty string as valid number */
+ return (endptr[0] == '\0');
+}
+
+static int64_t
+perform_arith_op(const char *left, const char *op, const char *right)
+{
+ int64_t res, l, r;
+
+ res = 0;
+
+ if (!is_integer(left)) {
+ yyerror("non-integer argument '%s'", left);
+ /* NOTREACHED */
+ }
+ if (!is_integer(right)) {
+ yyerror("non-integer argument '%s'", right);
+ /* NOTREACHED */
+ }
+
+ errno = 0;
+ l = strtoll(left, NULL, 10);
+ if (errno == ERANGE) {
+ yyerror("value '%s' is %s is %lld", left,
+ (l > 0) ? "too big, maximum" : "too small, minimum",
+ (l > 0) ? LLONG_MAX : LLONG_MIN);
+ /* NOTREACHED */
+ }
+
+ errno = 0;
+ r = strtoll(right, NULL, 10);
+ if (errno == ERANGE) {
+ yyerror("value '%s' is %s is %lld", right,
+ (l > 0) ? "too big, maximum" : "too small, minimum",
+ (l > 0) ? LLONG_MAX : LLONG_MIN);
+ /* NOTREACHED */
+ }
+
+ switch(op[0]) {
+ case '+':
+ /*
+ * Check for over-& underflow.
+ */
+ if ((l >= 0 && r <= INT64_MAX - l) ||
+ (l <= 0 && r >= INT64_MIN - l)) {
+ res = l + r;
+ } else {
+ yyerror("integer overflow or underflow occurred for "
+ "operation '%s %s %s'", left, op, right);
+ }
+ break;
+ case '-':
+ /*
+ * Check for over-& underflow.
+ */
+ if ((r > 0 && l < INT64_MIN + r) ||
+ (r < 0 && l > INT64_MAX + r)) {
+ yyerror("integer overflow or underflow occurred for "
+ "operation '%s %s %s'", left, op, right);
+ } else {
+ res = l - r;
+ }
+ break;
+ case '/':
+ if (r == 0)
+ yyerror("second argument to '%s' must not be zero", op);
+ if (l == INT64_MIN && r == -1)
+ yyerror("integer overflow or underflow occurred for "
+ "operation '%s %s %s'", left, op, right);
+ res = l / r;
+
+ break;
+ case '%':
+ if (r == 0)
+ yyerror("second argument to '%s' must not be zero", op);
+ if (l == INT64_MIN && r == -1)
+ yyerror("integer overflow or underflow occurred for "
+ "operation '%s %s %s'", left, op, right);
+ res = l % r;
+ break;
+ case '*':
+ /*
+ * Check for over-& underflow.
+ */
+
+ /*
+ * Simplify the conditions:
+ * - remove the case of both negative arguments
+ * unless the operation will cause an overflow
+ */
+ if (l < 0 && r < 0 && l != INT64_MIN && r != INT64_MIN) {
+ l = -l;
+ r = -r;
+ }
+
+ /* - remove the case of legative l and positive r */
+ if (l < 0 && r >= 0) {
+ /* Use res as a temporary variable */
+ res = l;
+ l = r;
+ r = res;
+ }
+
+ if ((l < 0 && r < 0) ||
+ (r > 0 && l > INT64_MAX / r) ||
+ (r <= 0 && l != 0 && r < INT64_MIN / l)) {
+ yyerror("integer overflow or underflow occurred for "
+ "operation '%s %s %s'", left, op, right);
+ /* NOTREACHED */
+ } else {
+ res = l * r;
+ }
+ break;
+ }
+ return res;
+}
+
+static const char *x = "|&=<>+-*/%:()";
+static const int x_token[] = {
+ SPEC_OR, SPEC_AND, COMPARE, COMPARE, COMPARE, ADD_SUB_OPERATOR,
+ ADD_SUB_OPERATOR, MUL_DIV_MOD_OPERATOR, MUL_DIV_MOD_OPERATOR,
+ MUL_DIV_MOD_OPERATOR, SPEC_REG, LEFT_PARENT, RIGHT_PARENT
+};
+
+static int handle_ddash = 1;
+
+int
+yylex(void)
+{
+ const char *p = *av++;
+ int retval;
+
+ if (!p)
+ retval = 0;
+ else if (p[1] == '\0') {
+ const char *w = strchr(x, p[0]);
+ if (w) {
+ retval = x_token[w-x];
+ } else {
+ retval = STRING;
+ }
+ } else if (p[1] == '=' && p[2] == '\0'
+ && (p[0] == '>' || p[0] == '<' || p[0] == '!'))
+ retval = COMPARE;
+ else if (handle_ddash && p[0] == '-' && p[1] == '-' && p[2] == '\0') {
+ /* ignore "--" if passed as first argument and isn't followed
+ * by another STRING */
+ retval = yylex();
+ if (retval != STRING && retval != LEFT_PARENT
+ && retval != RIGHT_PARENT) {
+ /* is not followed by string or parenthesis, use as
+ * STRING */
+ retval = STRING;
+ av--; /* was increased in call to yylex() above */
+ p = "--";
+ } else {
+ /* "--" is to be ignored */
+ p = yylval;
+ }
+ } else if (strcmp(p, "length") == 0)
+ retval = LENGTH;
+ else
+ retval = STRING;
+
+ handle_ddash = 0;
+ yylval = p;
+
+ return retval;
+}
+
+/*
+ * Print error message and exit with error 2 (syntax error).
+ */
+static __printflike(1, 2) void
+yyerror(const char *fmt, ...)
+{
+ va_list arg;
+
+ va_start(arg, fmt);
+ verrx(2, fmt, arg);
+ va_end(arg);
+}
+
+int
+main(int argc, const char * const *argv)
+{
+ setprogname(argv[0]);
+ (void)setlocale(LC_ALL, "");
+
+ if (argc == 1) {
+ (void)fprintf(stderr, "usage: %s expression\n",
+ getprogname());
+ exit(2);
+ }
+
+ av = argv + 1;
+
+ return yyparse();
+}
diff --git a/bin/kill/kill.1 b/bin/kill/kill.1
new file mode 100644
index 0000000..86b05fd
--- /dev/null
+++ b/bin/kill/kill.1
@@ -0,0 +1,156 @@
+.\" $NetBSD: kill.1,v 1.28 2017/04/22 23:01:36 christos Exp $
+.\"
+.\" Copyright (c) 1980, 1990, 1993
+.\" The Regents of the University of California. All rights reserved.
+.\"
+.\" This code is derived from software contributed to Berkeley by
+.\" the Institute of Electrical and Electronics Engineers, Inc.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\" notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must reproduce the above copyright
+.\" notice, this list of conditions and the following disclaimer in the
+.\" documentation and/or other materials provided with the distribution.
+.\" 3. Neither the name of the University nor the names of its contributors
+.\" may be used to endorse or promote products derived from this software
+.\" without specific prior written permission.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+.\" ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+.\" SUCH DAMAGE.
+.\"
+.\" @(#)kill.1 8.2 (Berkeley) 4/28/95
+.\"
+.Dd April 22, 2017
+.Dt KILL 1
+.Os
+.Sh NAME
+.Nm kill
+.Nd terminate or signal a process
+.Sh SYNOPSIS
+.Nm
+.Op Fl s Ar signal_name
+.Ar pid
+\&...
+.Nm
+.Fl l
+.Op Ar exit_status
+.Nm
+.Fl signal_name
+.Ar pid
+\&...
+.Nm
+.Fl signal_number
+.Ar pid
+\&...
+.Sh DESCRIPTION
+The
+.Nm
+utility sends a signal to the process(es) specified
+by the pid operand(s).
+.Pp
+Only the super-user may send signals to other users' processes.
+.Pp
+The options are as follows:
+.Bl -tag -width Ds
+.It Fl s Ar signal_name
+A symbolic signal name specifying the signal to be sent instead of the
+default
+.Dv TERM .
+.It Fl l Op Ar exit_status
+Display the name of the signal corresponding to
+.Ar exit_status .
+.Ar exit_status
+may be the exit status of a command killed by a signal
+(see the
+special
+.Xr sh 1
+parameter
+.Sq ?\& )
+or a signal number.
+.Pp
+If no operand is given, display the names of all the signals.
+.It Fl signal_name
+A symbolic signal name specifying the signal to be sent instead of the
+default
+.Dv TERM .
+.It Fl signal_number
+A non-negative decimal integer, specifying the signal to be sent instead
+of the default
+.Dv TERM .
+.El
+.Pp
+The following pids have special meanings:
+.Bl -tag -width Ds -compact
+.It -1
+If superuser, broadcast the signal to all processes; otherwise broadcast
+to all processes belonging to the user.
+.It 0
+Broadcast the signal to all processes in the current process group
+belonging to the user.
+.El
+.Pp
+Some of the more commonly used signals:
+.Bl -tag -width Ds -compact
+.It 0
+0 (does not affect the process; can be used to test whether the
+process exists)
+.It 1
+HUP (hang up)
+.It 2
+INT (interrupt)
+.It 3
+QUIT (quit)
+.It 6
+ABRT (abort)
+.It 9
+KILL (non-catchable, non-ignorable kill)
+.It 14
+ALRM (alarm clock)
+.It 15
+TERM (software termination signal)
+.El
+.Pp
+.Nm
+is a built-in to
+.Xr csh 1 ;
+it allows job specifiers of the form ``%...'' as arguments
+so process id's are not as often used as
+.Nm
+arguments.
+See
+.Xr csh 1
+for details.
+.Sh DIAGNOSTICS
+.Ex -std
+.Sh SEE ALSO
+.Xr csh 1 ,
+.Xr pgrep 1 ,
+.Xr pkill 1 ,
+.Xr ps 1 ,
+.Xr kill 2 ,
+.Xr sigaction 2 ,
+.Xr signal 7
+.Sh STANDARDS
+The
+.Nm
+utility is expected to be
+.St -p1003.2
+compatible.
+.Sh HISTORY
+A
+.Nm
+command appeared in
+.At v3
+in section 8 of the manual.
diff --git a/bin/kill/kill.c b/bin/kill/kill.c
new file mode 100644
index 0000000..6c17cb3
--- /dev/null
+++ b/bin/kill/kill.c
@@ -0,0 +1,320 @@
+/* $NetBSD: kill.c,v 1.30 2018/12/12 20:22:43 kre Exp $ */
+
+/*
+ * Copyright (c) 1988, 1993, 1994
+ * The Regents of the University of California. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+#if !defined(lint) && !defined(SHELL)
+__COPYRIGHT("@(#) Copyright (c) 1988, 1993, 1994\
+ The Regents of the University of California. All rights reserved.");
+#endif /* not lint */
+
+#ifndef lint
+#if 0
+static char sccsid[] = "@(#)kill.c 8.4 (Berkeley) 4/28/95";
+#else
+__RCSID("$NetBSD: kill.c,v 1.30 2018/12/12 20:22:43 kre Exp $");
+#endif
+#endif /* not lint */
+
+#include <ctype.h>
+#include <err.h>
+#include <errno.h>
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <limits.h>
+#include <inttypes.h>
+#include <string.h>
+#include <termios.h>
+#include <unistd.h>
+#include <locale.h>
+#include <sys/ioctl.h>
+
+#ifdef SHELL /* sh (aka ash) builtin */
+int killcmd(int, char *argv[]);
+#define main killcmd
+#include "../../bin/sh/bltin/bltin.h"
+#endif /* SHELL */
+
+__dead static void nosig(const char *);
+void printsignals(FILE *, int);
+static int signum(const char *);
+static pid_t processnum(const char *);
+__dead static void usage(void);
+
+int
+main(int argc, char *argv[])
+{
+ int errors;
+ int numsig;
+ pid_t pid;
+ const char *sn;
+
+ setprogname(argv[0]);
+ setlocale(LC_ALL, "");
+ if (argc < 2)
+ usage();
+
+ numsig = SIGTERM;
+
+ argc--, argv++;
+
+ /*
+ * Process exactly 1 option, if there is one.
+ */
+ if (argv[0][0] == '-') {
+ switch (argv[0][1]) {
+ case 'l':
+ if (argv[0][2] != '\0')
+ sn = argv[0] + 2;
+ else {
+ argc--; argv++;
+ sn = argv[0];
+ }
+ if (argc > 1)
+ usage();
+ if (argc == 1) {
+ if (isdigit((unsigned char)*sn) == 0)
+ usage();
+ numsig = signum(sn);
+ if (numsig >= 128)
+ numsig -= 128;
+ if (numsig == 0 || signalnext(numsig) == -1)
+ nosig(sn);
+ sn = signalname(numsig);
+ if (sn == NULL)
+ errx(EXIT_FAILURE,
+ "unknown signal number: %d", numsig);
+ printf("%s\n", sn);
+ exit(0);
+ }
+ printsignals(stdout, 0);
+ exit(0);
+
+ case 's':
+ if (argv[0][2] != '\0')
+ sn = argv[0] + 2;
+ else {
+ argc--, argv++;
+ if (argc < 1) {
+ warnx(
+ "option requires an argument -- s");
+ usage();
+ }
+ sn = argv[0];
+ }
+ if (strcmp(sn, "0") == 0)
+ numsig = 0;
+ else if ((numsig = signalnumber(sn)) == 0) {
+ if (sn != argv[0])
+ goto trysignal;
+ nosig(sn);
+ }
+ argc--, argv++;
+ break;
+
+ case '-':
+ if (argv[0][2] == '\0') {
+ /* process this one again later */
+ break;
+ }
+ /* FALL THROUGH */
+ case '\0':
+ usage();
+ break;
+
+ default:
+ trysignal:
+ sn = *argv + 1;
+ if (((numsig = signalnumber(sn)) == 0)) {
+ if (isdigit((unsigned char)*sn))
+ numsig = signum(sn);
+ else
+ nosig(sn);
+ }
+
+ if (numsig != 0 && signalnext(numsig) == -1)
+ nosig(sn);
+ argc--, argv++;
+ break;
+ }
+ }
+
+ /* deal with the optional '--' end of options option */
+ if (argc > 0 && strcmp(*argv, "--") == 0)
+ argc--, argv++;
+
+ if (argc == 0)
+ usage();
+
+ for (errors = 0; argc; argc--, argv++) {
+#ifdef SHELL
+ extern int getjobpgrp(const char *);
+ if (*argv[0] == '%') {
+ pid = getjobpgrp(*argv);
+ if (pid == 0) {
+ warnx("illegal job id: %s", *argv);
+ errors = 1;
+ continue;
+ }
+ } else
+#endif
+ if ((pid = processnum(*argv)) == (pid_t)-1) {
+ errors = 1;
+ continue;
+ }
+
+ if (kill(pid, numsig) == -1) {
+ warn("%s", *argv);
+ errors = 1;
+ }
+#ifdef SHELL
+ /*
+ * Wakeup the process if it was suspended, so it can
+ * exit without an explicit 'fg'.
+ * (kernel handles this for SIGKILL)
+ */
+ if (numsig == SIGTERM || numsig == SIGHUP)
+ kill(pid, SIGCONT);
+#endif
+ }
+
+ exit(errors);
+ /* NOTREACHED */
+}
+
+static int
+signum(const char *sn)
+{
+ intmax_t n;
+ char *ep;
+
+ n = strtoimax(sn, &ep, 10);
+
+ /* check for correctly parsed number */
+ if (*ep || n <= INT_MIN || n >= INT_MAX )
+ errx(EXIT_FAILURE, "illegal signal number: %s", sn);
+ /* NOTREACHED */
+
+ return (int)n;
+}
+
+static pid_t
+processnum(const char *s)
+{
+ intmax_t n;
+ char *ep;
+
+ n = strtoimax(s, &ep, 10);
+
+ /* check for correctly parsed number */
+ if (*ep || n == INTMAX_MIN || n == INTMAX_MAX || (pid_t)n != n ||
+ n == -1) {
+ warnx("illegal process%s id: %s", (n < 0 ? " group" : ""), s);
+ n = -1;
+ }
+
+ return (pid_t)n;
+}
+
+static void
+nosig(const char *name)
+{
+
+ warnx("unknown signal %s; valid signals:", name);
+ printsignals(stderr, 0);
+ exit(1);
+ /* NOTREACHED */
+}
+
+#ifndef SHELL
+/*
+ * Print the names of all the signals (neatly) to fp
+ * "len" gives the number of chars already printed to
+ * the current output line (in kill.c, always 0)
+ */
+void
+printsignals(FILE *fp, int len)
+{
+ int sig;
+ int nl, pad;
+ const char *name;
+ int termwidth = 80;
+
+ if ((name = getenv("COLUMNS")) != 0)
+ termwidth = atoi(name);
+ else if (isatty(fileno(fp))) {
+ struct winsize win;
+
+ if (ioctl(fileno(fp), TIOCGWINSZ, &win) == 0 && win.ws_col > 0)
+ termwidth = win.ws_col;
+ }
+
+ pad = (len | 7) + 1 - len;
+
+ for (sig = 0; (sig = signalnext(sig)) != 0; ) {
+ name = signalname(sig);
+ if (name == NULL)
+ continue;
+
+ nl = strlen(name);
+
+ if (len > 0 && nl + len + pad >= termwidth) {
+ fprintf(fp, "\n");
+ len = 0;
+ pad = 0;
+ } else if (pad > 0 && len != 0)
+ fprintf(fp, "%*s", pad, "");
+ else
+ pad = 0;
+
+ len += nl + pad;
+ pad = (nl | 7) + 1 - nl;
+
+ fprintf(fp, "%s", name);
+ }
+ if (len != 0)
+ fprintf(fp, "\n");
+}
+#endif
+
+static void
+usage(void)
+{
+ const char *pn = getprogname();
+
+ fprintf(stderr, "usage: %s [-s signal_name] pid ...\n"
+ " %s -l [exit_status]\n"
+ " %s -signal_name pid ...\n"
+ " %s -signal_number pid ...\n",
+ pn, pn, pn, pn);
+ exit(1);
+ /* NOTREACHED */
+}
diff --git a/bin/ln/ln.1 b/bin/ln/ln.1
new file mode 100644
index 0000000..2eca74f
--- /dev/null
+++ b/bin/ln/ln.1
@@ -0,0 +1,320 @@
+.\" $NetBSD: ln.1,v 1.28 2017/04/20 22:57:30 christos Exp $
+.\"-
+.\" Copyright (c) 1980, 1990, 1993
+.\" The Regents of the University of California. All rights reserved.
+.\"
+.\" This code is derived from software contributed to Berkeley by
+.\" the Institute of Electrical and Electronics Engineers, Inc.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\" notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must reproduce the above copyright
+.\" notice, this list of conditions and the following disclaimer in the
+.\" documentation and/or other materials provided with the distribution.
+.\" 3. Neither the name of the University nor the names of its contributors
+.\" may be used to endorse or promote products derived from this software
+.\" without specific prior written permission.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+.\" ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+.\" SUCH DAMAGE.
+.\"
+.\" @(#)ln.1 8.2 (Berkeley) 12/30/93
+.\" $FreeBSD: head/bin/ln/ln.1 244791 2012-12-28 22:06:33Z gjb $
+.\"
+.Dd April 20, 2017
+.Dt LN 1
+.Os
+.Sh NAME
+.\" .Nm ln ,
+.\" .Nm link
+.Nm ln
+.Nd link files
+.Sh SYNOPSIS
+.Nm
+.Op Fl L | Fl P | Fl s Op Fl F
+.Op Fl f | iw
+.Op Fl hnv
+.Ar source_file
+.Op Ar target_file
+.Nm
+.Op Fl L | Fl P | Fl s Op Fl F
+.Op Fl f | iw
+.Op Fl hnv
+.Ar source_file ...
+.Ar target_dir
+.\" .Nm link
+.\" .Ar source_file Ar target_file
+.Sh DESCRIPTION
+The
+.Nm
+utility creates a new directory entry (linked file) for the file name
+specified by
+.Ar target_file .
+The
+.Ar target_file
+will be created with the same file modes as the
+.Ar source_file .
+It is useful for maintaining multiple copies of a file in many places
+at once without using up storage for the
+.Dq copies ;
+instead, a link
+.Dq points
+to the original copy.
+There are two types of links; hard links and symbolic links.
+How a link
+.Dq points
+to a file is one of the differences between a hard and symbolic link.
+.Pp
+The options are as follows:
+.Bl -tag -width flag
+.It Fl F
+If the target file already exists and is a directory, then remove it
+so that the link may occur.
+The
+.Fl F
+option should be used with either
+.Fl f
+or
+.Fl i
+options.
+If none is specified,
+.Fl f
+is implied.
+The
+.Fl F
+option is a no-op unless
+.Fl s
+option is specified.
+.It Fl L
+When creating a hard link to a symbolic link,
+create a hard link to the target of the symbolic link.
+This is the default.
+This option cancels the
+.Fl P
+option.
+.It Fl P
+When creating a hard link to a symbolic link,
+create a hard link to the symbolic link itself.
+This option cancels the
+.Fl L
+option.
+.It Fl f
+If the target file already exists,
+then unlink it so that the link may occur.
+(The
+.Fl f
+option overrides any previous
+.Fl i
+and
+.Fl w
+options.)
+.It Fl h
+If the
+.Ar target_file
+or
+.Ar target_dir
+is a symbolic link, do not follow it.
+This is most useful with the
+.Fl f
+option, to replace a symlink which may point to a directory.
+.It Fl i
+Cause
+.Nm
+to write a prompt to standard error if the target file exists.
+If the response from the standard input begins with the character
+.Sq Li y
+or
+.Sq Li Y ,
+then unlink the target file so that the link may occur.
+Otherwise, do not attempt the link.
+(The
+.Fl i
+option overrides any previous
+.Fl f
+options.)
+.It Fl n
+Same as
+.Fl h ,
+for compatibility with other
+.Nm
+implementations.
+.It Fl s
+Create a symbolic link.
+.It Fl v
+Cause
+.Nm
+to be verbose, showing files as they are processed.
+.It Fl w
+Warn if the source of a symbolic link does not currently exist.
+.El
+.Pp
+By default,
+.Nm
+makes
+.Em hard
+links.
+A hard link to a file is indistinguishable from the original directory entry;
+any changes to a file are effectively independent of the name used to reference
+the file.
+Directories may not be hardlinked, and hard links may not span file systems.
+.Pp
+A symbolic link contains the name of the file to
+which it is linked.
+The referenced file is used when an
+.Xr open 2
+operation is performed on the link.
+A
+.Xr stat 2
+on a symbolic link will return the linked-to file; an
+.Xr lstat 2
+must be done to obtain information about the link.
+The
+.Xr readlink 2
+call may be used to read the contents of a symbolic link.
+Symbolic links may span file systems and may refer to directories.
+.Pp
+Given one or two arguments,
+.Nm
+creates a link to an existing file
+.Ar source_file .
+If
+.Ar target_file
+is given, the link has that name;
+.Ar target_file
+may also be a directory in which to place the link;
+otherwise it is placed in the current directory.
+If only the directory is specified, the link will be made
+to the last component of
+.Ar source_file .
+.Pp
+Given more than two arguments,
+.Nm
+makes links in
+.Ar target_dir
+to all the named source files.
+The links made will have the same name as the files being linked to.
+.\" .Pp
+.\" When the utility is called as
+.\" .Nm link ,
+.\" exactly two arguments must be supplied,
+.\" neither of which may specify a directory.
+.\" No options may be supplied in this simple mode of operation,
+.\" which performs a
+.\" .Xr link 2
+.\" operation using the two passed arguments.
+.Sh EXAMPLES
+Create a symbolic link named
+.Pa /home/src
+and point it to
+.Pa /usr/src :
+.Pp
+.Dl # ln -s /usr/src /home/src
+.Pp
+Hard link
+.Pa /usr/local/bin/fooprog
+to file
+.Pa /usr/local/bin/fooprog-1.0 :
+.Pp
+.Dl # ln /usr/local/bin/fooprog-1.0 /usr/local/bin/fooprog
+.Pp
+As an exercise, try the following commands:
+.Bd -literal -offset indent
+# ls -i /bin/[
+11553 /bin/[
+# ls -i /bin/test
+11553 /bin/test
+.Ed
+.Pp
+Note that both files have the same inode; that is,
+.Pa /bin/[
+is essentially an alias for the
+.Xr test 1
+command.
+This hard link exists so
+.Xr test 1
+may be invoked from shell scripts, for example, using the
+.Li "if [ ]"
+construct.
+.Pp
+In the next example, the second call to
+.Nm
+removes the original
+.Pa foo
+and creates a replacement pointing to
+.Pa baz :
+.Bd -literal -offset indent
+# mkdir bar baz
+# ln -s bar foo
+# ln -shf baz foo
+.Ed
+.Pp
+Without the
+.Fl h
+option, this would instead leave
+.Pa foo
+pointing to
+.Pa bar
+and inside
+.Pa foo
+create a new symlink
+.Pa baz
+pointing to itself.
+This results from directory-walking.
+.Pp
+An easy rule to remember is that the argument order for
+.Nm
+is the same as for
+.Xr cp 1 :
+The first argument needs to exist, the second one is created.
+.Sh COMPATIBILITY
+The
+.Fl h ,
+.Fl i ,
+.Fl n ,
+.Fl v
+and
+.Fl w
+options are non-standard and their use in scripts is not recommended.
+They are provided solely for compatibility with other
+.Nm
+implementations.
+.Pp
+The
+.Fl F
+option is a
+.Fx
+extension and should not be used in portable scripts.
+.Sh SEE ALSO
+.Xr link 2 ,
+.Xr lstat 2 ,
+.Xr readlink 2 ,
+.Xr stat 2 ,
+.Xr symlink 2 ,
+.Xr symlink 7
+.Sh STANDARDS
+The
+.Nm
+utility conforms to
+.St -p1003.2-92 .
+.\" .Pp
+.\" The simplified
+.\" .Nm link
+.\" command conforms to
+.\" .St -susv2 .
+.Sh HISTORY
+An
+.Nm
+command appeared in
+.At v1 .
diff --git a/bin/ln/ln.c b/bin/ln/ln.c
new file mode 100644
index 0000000..b057e39
--- /dev/null
+++ b/bin/ln/ln.c
@@ -0,0 +1,365 @@
+/* $NetBSD: ln.c,v 1.40 2018/08/26 23:01:06 sevan Exp $ */
+
+/*-
+ * Copyright (c) 1987, 1993, 1994
+ * The Regents of the University of California. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#if 0
+#ifndef lint
+static char const copyright[] =
+"@(#) Copyright (c) 1987, 1993, 1994\n\
+ The Regents of the University of California. All rights reserved.\n";
+#endif /* not lint */
+
+#ifndef lint
+static char sccsid[] = "@(#)ln.c 8.2 (Berkeley) 3/31/94";
+#endif /* not lint */
+#endif
+#include <sys/cdefs.h>
+#ifdef __FBSDID
+__FBSDID("$FreeBSD: head/bin/ln/ln.c 251261 2013-06-02 17:55:00Z eadler $");
+#endif
+__RCSID("$NetBSD: ln.c,v 1.40 2018/08/26 23:01:06 sevan Exp $");
+
+#include <sys/param.h>
+#include <sys/stat.h>
+
+#include <err.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <libgen.h>
+#include <limits.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+static int fflag; /* Unlink existing files. */
+static int Fflag; /* Remove empty directories also. */
+static int hflag; /* Check new name for symlink first. */
+static int iflag; /* Interactive mode. */
+static int Pflag; /* Create hard links to symlinks. */
+static int sflag; /* Symbolic, not hard, link. */
+static int vflag; /* Verbose output. */
+static int wflag; /* Warn if symlink target does not
+ * exist, and -f is not enabled. */
+static char linkch;
+
+static int linkit(const char *, const char *, int);
+static __dead void usage(void);
+
+int
+main(int argc, char *argv[])
+{
+ struct stat sb;
+ char *p, *targetdir;
+ int ch, exitval;
+
+ /*
+ * Test for the special case where the utility is called as
+ * "link", for which the functionality provided is greatly
+ * simplified.
+ */
+ if ((p = strrchr(argv[0], '/')) == NULL)
+ p = argv[0];
+ else
+ ++p;
+ if (strcmp(p, "link") == 0) {
+ while (getopt(argc, argv, "") != -1)
+ usage();
+ argc -= optind;
+ argv += optind;
+ if (argc != 2)
+ usage();
+ if (link(argv[0], argv[1]) == -1)
+ err(EXIT_FAILURE, NULL);
+ exit(EXIT_SUCCESS);
+ }
+
+ while ((ch = getopt(argc, argv, "FLPfhinsvw")) != -1)
+ switch (ch) {
+ case 'F':
+ Fflag = 1;
+ break;
+ case 'L':
+ Pflag = 0;
+ break;
+ case 'P':
+ Pflag = 1;
+ break;
+ case 'f':
+ fflag = 1;
+ iflag = 0;
+ wflag = 0;
+ break;
+ case 'h':
+ case 'n':
+ hflag = 1;
+ break;
+ case 'i':
+ iflag = 1;
+ fflag = 0;
+ break;
+ case 's':
+ sflag = 1;
+ break;
+ case 'v':
+ vflag = 1;
+ break;
+ case 'w':
+ wflag = 1;
+ break;
+ case '?':
+ default:
+ usage();
+ }
+
+ argv += optind;
+ argc -= optind;
+
+ linkch = sflag ? '-' : '=';
+ if (sflag == 0)
+ Fflag = 0;
+ if (Fflag == 1 && iflag == 0) {
+ fflag = 1;
+ wflag = 0; /* Implied when fflag != 0 */
+ }
+
+ switch(argc) {
+ case 0:
+ usage();
+ /* NOTREACHED */
+ case 1: /* ln source */
+ exit(linkit(argv[0], ".", 1));
+ case 2: /* ln source target */
+ exit(linkit(argv[0], argv[1], 0));
+ default:
+ ;
+ }
+ /* ln source1 source2 directory */
+ targetdir = argv[argc - 1];
+ if (hflag && lstat(targetdir, &sb) == 0 && S_ISLNK(sb.st_mode)) {
+ /*
+ * We were asked not to follow symlinks, but found one at
+ * the target--simulate "not a directory" error
+ */
+ errno = ENOTDIR;
+ err(1, "%s", targetdir);
+ }
+ if (stat(targetdir, &sb))
+ err(1, "%s", targetdir);
+ if (!S_ISDIR(sb.st_mode))
+ usage();
+ for (exitval = 0; *argv != targetdir; ++argv)
+ exitval |= linkit(*argv, targetdir, 1);
+ exit(exitval);
+}
+
+/*
+ * Two pathnames refer to the same directory entry if the directories match
+ * and the final components' names match.
+ */
+static int
+samedirent(const char *path1, const char *path2)
+{
+ const char *file1, *file2;
+ char pathbuf[PATH_MAX];
+ struct stat sb1, sb2;
+
+ if (strcmp(path1, path2) == 0)
+ return 1;
+ file1 = strrchr(path1, '/');
+ if (file1 != NULL)
+ file1++;
+ else
+ file1 = path1;
+ file2 = strrchr(path2, '/');
+ if (file2 != NULL)
+ file2++;
+ else
+ file2 = path2;
+ if (strcmp(file1, file2) != 0)
+ return 0;
+ if (file1 - path1 >= PATH_MAX || file2 - path2 >= PATH_MAX)
+ return 0;
+ if (file1 == path1)
+ memcpy(pathbuf, ".", 2);
+ else {
+ memcpy(pathbuf, path1, file1 - path1);
+ pathbuf[file1 - path1] = '\0';
+ }
+ if (stat(pathbuf, &sb1) != 0)
+ return 0;
+ if (file2 == path2)
+ memcpy(pathbuf, ".", 2);
+ else {
+ memcpy(pathbuf, path2, file2 - path2);
+ pathbuf[file2 - path2] = '\0';
+ }
+ if (stat(pathbuf, &sb2) != 0)
+ return 0;
+ return sb1.st_dev == sb2.st_dev && sb1.st_ino == sb2.st_ino;
+}
+
+static int
+linkit(const char *source, const char *target, int isdir)
+{
+ struct stat sb;
+ const char *p;
+ int ch, exists, first;
+ char path[PATH_MAX];
+ char wbuf[PATH_MAX];
+ char bbuf[PATH_MAX];
+
+ if (!sflag) {
+ /* If source doesn't exist, quit now. */
+ if ((Pflag ? lstat : stat)(source, &sb)) {
+ warn("%s", source);
+ return (1);
+ }
+ /* Only symbolic links to directories. */
+ if (S_ISDIR(sb.st_mode)) {
+ errno = EISDIR;
+ warn("%s", source);
+ return (1);
+ }
+ }
+
+ /*
+ * If the target is a directory (and not a symlink if hflag),
+ * append the source's name.
+ */
+ if (isdir ||
+ (lstat(target, &sb) == 0 && S_ISDIR(sb.st_mode)) ||
+ (!hflag && stat(target, &sb) == 0 && S_ISDIR(sb.st_mode))) {
+ if (strlcpy(bbuf, source, sizeof(bbuf)) >= sizeof(bbuf) ||
+ (p = basename(bbuf)) == NULL ||
+ snprintf(path, sizeof(path), "%s/%s", target, p) >=
+ (ssize_t)sizeof(path)) {
+ errno = ENAMETOOLONG;
+ warn("%s", source);
+ return (1);
+ }
+ target = path;
+ }
+
+ /*
+ * If the link source doesn't exist, and a symbolic link was
+ * requested, and -w was specified, give a warning.
+ */
+ if (sflag && wflag) {
+ if (*source == '/') {
+ /* Absolute link source. */
+ if (stat(source, &sb) != 0)
+ warn("warning: %s inaccessible", source);
+ } else {
+ /*
+ * Relative symlink source. Try to construct the
+ * absolute path of the source, by appending `source'
+ * to the parent directory of the target.
+ */
+ strlcpy(bbuf, target, sizeof(bbuf));
+ p = dirname(bbuf);
+ if (p != NULL) {
+ (void)snprintf(wbuf, sizeof(wbuf), "%s/%s",
+ p, source);
+ if (stat(wbuf, &sb) != 0)
+ warn("warning: %s", source);
+ }
+ }
+ }
+
+ /*
+ * If the file exists, first check it is not the same directory entry.
+ */
+ exists = !lstat(target, &sb);
+ if (exists) {
+ if (!sflag && samedirent(source, target)) {
+ warnx("%s and %s are the same directory entry",
+ source, target);
+ return (1);
+ }
+ }
+ /*
+ * Then unlink it forcibly if -f was specified
+ * and interactively if -i was specified.
+ */
+ if (fflag && exists) {
+ if (Fflag && S_ISDIR(sb.st_mode)) {
+ if (rmdir(target)) {
+ warn("%s", target);
+ return (1);
+ }
+ } else if (unlink(target)) {
+ warn("%s", target);
+ return (1);
+ }
+ } else if (iflag && exists) {
+ fflush(stdout);
+ fprintf(stderr, "replace %s? ", target);
+
+ first = ch = getchar();
+ while(ch != '\n' && ch != EOF)
+ ch = getchar();
+ if (first != 'y' && first != 'Y') {
+ fprintf(stderr, "not replaced\n");
+ return (1);
+ }
+
+ if (Fflag && S_ISDIR(sb.st_mode)) {
+ if (rmdir(target)) {
+ warn("%s", target);
+ return (1);
+ }
+ } else if (unlink(target)) {
+ warn("%s", target);
+ return (1);
+ }
+ }
+
+ /* Attempt the link. */
+ if (sflag ? symlink(source, target) :
+ linkat(AT_FDCWD, source, AT_FDCWD, target,
+ Pflag ? 0 : AT_SYMLINK_FOLLOW)) {
+ warn("%s", target);
+ return (1);
+ }
+ if (vflag)
+ (void)printf("%s %c> %s\n", target, linkch, source);
+ return (0);
+}
+
+static __dead void
+usage(void)
+{
+ (void)fprintf(stderr,
+ "usage: %s [-L | -P | -s [-F]] [-f | -iw] [-hnv] source_file [target_file]\n"
+ " %s [-L | -P | -s [-F]] [-f | -iw] [-hnv] source_file ... target_dir\n"
+ " link source_file target_file\n", getprogname(), getprogname());
+ exit(1);
+}
diff --git a/bin/ls/cmp.c b/bin/ls/cmp.c
new file mode 100644
index 0000000..21d50e1
--- /dev/null
+++ b/bin/ls/cmp.c
@@ -0,0 +1,199 @@
+/* $NetBSD: cmp.c,v 1.17 2003/08/07 09:05:14 agc Exp $ */
+
+/*
+ * Copyright (c) 1989, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Michael Fischbein.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+#ifndef lint
+#if 0
+static char sccsid[] = "@(#)cmp.c 8.1 (Berkeley) 5/31/93";
+#else
+__RCSID("$NetBSD: cmp.c,v 1.17 2003/08/07 09:05:14 agc Exp $");
+#endif
+#endif /* not lint */
+
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#include <fts.h>
+#include <string.h>
+
+#include "ls.h"
+#include "extern.h"
+
+#if defined(_POSIX_SOURCE) || defined(_POSIX_C_SOURCE) || \
+ defined(_XOPEN_SOURCE) || defined(__NetBSD__)
+#define ATIMENSEC_CMP(x, op, y) ((x)->st_atimensec op (y)->st_atimensec)
+#define CTIMENSEC_CMP(x, op, y) ((x)->st_ctimensec op (y)->st_ctimensec)
+#define MTIMENSEC_CMP(x, op, y) ((x)->st_mtimensec op (y)->st_mtimensec)
+#else
+#define ATIMENSEC_CMP(x, op, y) \
+ ((x)->st_atimespec.tv_nsec op (y)->st_atimespec.tv_nsec)
+#define CTIMENSEC_CMP(x, op, y) \
+ ((x)->st_ctimespec.tv_nsec op (y)->st_ctimespec.tv_nsec)
+#define MTIMENSEC_CMP(x, op, y) \
+ ((x)->st_mtimespec.tv_nsec op (y)->st_mtimespec.tv_nsec)
+#endif
+
+int
+namecmp(const FTSENT *a, const FTSENT *b)
+{
+
+ return (strcmp(a->fts_name, b->fts_name));
+}
+
+int
+revnamecmp(const FTSENT *a, const FTSENT *b)
+{
+
+ return (strcmp(b->fts_name, a->fts_name));
+}
+
+int
+modcmp(const FTSENT *a, const FTSENT *b)
+{
+
+ if (b->fts_statp->st_mtime > a->fts_statp->st_mtime)
+ return (1);
+ else if (b->fts_statp->st_mtime < a->fts_statp->st_mtime)
+ return (-1);
+ else if (MTIMENSEC_CMP(b->fts_statp, >, a->fts_statp))
+ return (1);
+ else if (MTIMENSEC_CMP(b->fts_statp, <, a->fts_statp))
+ return (-1);
+ else
+ return (namecmp(a, b));
+}
+
+int
+revmodcmp(const FTSENT *a, const FTSENT *b)
+{
+
+ if (b->fts_statp->st_mtime > a->fts_statp->st_mtime)
+ return (-1);
+ else if (b->fts_statp->st_mtime < a->fts_statp->st_mtime)
+ return (1);
+ else if (MTIMENSEC_CMP(b->fts_statp, >, a->fts_statp))
+ return (-1);
+ else if (MTIMENSEC_CMP(b->fts_statp, <, a->fts_statp))
+ return (1);
+ else
+ return (revnamecmp(a, b));
+}
+
+int
+acccmp(const FTSENT *a, const FTSENT *b)
+{
+
+ if (b->fts_statp->st_atime > a->fts_statp->st_atime)
+ return (1);
+ else if (b->fts_statp->st_atime < a->fts_statp->st_atime)
+ return (-1);
+ else if (ATIMENSEC_CMP(b->fts_statp, >, a->fts_statp))
+ return (1);
+ else if (ATIMENSEC_CMP(b->fts_statp, <, a->fts_statp))
+ return (-1);
+ else
+ return (namecmp(a, b));
+}
+
+int
+revacccmp(const FTSENT *a, const FTSENT *b)
+{
+
+ if (b->fts_statp->st_atime > a->fts_statp->st_atime)
+ return (-1);
+ else if (b->fts_statp->st_atime < a->fts_statp->st_atime)
+ return (1);
+ else if (ATIMENSEC_CMP(b->fts_statp, >, a->fts_statp))
+ return (-1);
+ else if (ATIMENSEC_CMP(b->fts_statp, <, a->fts_statp))
+ return (1);
+ else
+ return (revnamecmp(a, b));
+}
+
+int
+statcmp(const FTSENT *a, const FTSENT *b)
+{
+
+ if (b->fts_statp->st_ctime > a->fts_statp->st_ctime)
+ return (1);
+ else if (b->fts_statp->st_ctime < a->fts_statp->st_ctime)
+ return (-1);
+ else if (CTIMENSEC_CMP(b->fts_statp, >, a->fts_statp))
+ return (1);
+ else if (CTIMENSEC_CMP(b->fts_statp, <, a->fts_statp))
+ return (-1);
+ else
+ return (namecmp(a, b));
+}
+
+int
+revstatcmp(const FTSENT *a, const FTSENT *b)
+{
+
+ if (b->fts_statp->st_ctime > a->fts_statp->st_ctime)
+ return (-1);
+ else if (b->fts_statp->st_ctime < a->fts_statp->st_ctime)
+ return (1);
+ else if (CTIMENSEC_CMP(b->fts_statp, >, a->fts_statp))
+ return (-1);
+ else if (CTIMENSEC_CMP(b->fts_statp, <, a->fts_statp))
+ return (1);
+ else
+ return (revnamecmp(a, b));
+}
+
+int
+sizecmp(const FTSENT *a, const FTSENT *b)
+{
+
+ if (b->fts_statp->st_size > a->fts_statp->st_size)
+ return (1);
+ if (b->fts_statp->st_size < a->fts_statp->st_size)
+ return (-1);
+ else
+ return (namecmp(a, b));
+}
+
+int
+revsizecmp(const FTSENT *a, const FTSENT *b)
+{
+
+ if (b->fts_statp->st_size > a->fts_statp->st_size)
+ return (-1);
+ if (b->fts_statp->st_size < a->fts_statp->st_size)
+ return (1);
+ else
+ return (revnamecmp(a, b));
+}
diff --git a/bin/ls/extern.h b/bin/ls/extern.h
new file mode 100644
index 0000000..0a9eea0
--- /dev/null
+++ b/bin/ls/extern.h
@@ -0,0 +1,53 @@
+/* $NetBSD: extern.h,v 1.17 2011/08/29 14:44:21 joerg Exp $ */
+
+/*-
+ * Copyright (c) 1991, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * @(#)extern.h 8.1 (Berkeley) 5/31/93
+ */
+
+int acccmp(const FTSENT *, const FTSENT *);
+int revacccmp(const FTSENT *, const FTSENT *);
+int modcmp(const FTSENT *, const FTSENT *);
+int revmodcmp(const FTSENT *, const FTSENT *);
+int namecmp(const FTSENT *, const FTSENT *);
+int revnamecmp(const FTSENT *, const FTSENT *);
+int statcmp(const FTSENT *, const FTSENT *);
+int revstatcmp(const FTSENT *, const FTSENT *);
+int sizecmp(const FTSENT *, const FTSENT *);
+int revsizecmp(const FTSENT *, const FTSENT *);
+
+int ls_main(int, char *[]);
+
+int printescaped(const char *);
+void printacol(DISPLAY *);
+void printcol(DISPLAY *);
+void printlong(DISPLAY *);
+void printscol(DISPLAY *);
+void printstream(DISPLAY *);
+int safe_print(const char *);
diff --git a/bin/ls/ls.1 b/bin/ls/ls.1
new file mode 100644
index 0000000..1bd9161
--- /dev/null
+++ b/bin/ls/ls.1
@@ -0,0 +1,516 @@
+.\" $NetBSD: ls.1,v 1.80 2017/07/03 21:33:23 wiz Exp $
+.\"
+.\" Copyright (c) 1980, 1990, 1991, 1993, 1994
+.\" The Regents of the University of California. All rights reserved.
+.\"
+.\" This code is derived from software contributed to Berkeley by
+.\" the Institute of Electrical and Electronics Engineers, Inc.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\" notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must reproduce the above copyright
+.\" notice, this list of conditions and the following disclaimer in the
+.\" documentation and/or other materials provided with the distribution.
+.\" 3. Neither the name of the University nor the names of its contributors
+.\" may be used to endorse or promote products derived from this software
+.\" without specific prior written permission.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+.\" ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+.\" SUCH DAMAGE.
+.\"
+.\" @(#)ls.1 8.7 (Berkeley) 7/29/94
+.\"
+.Dd August 10, 2016
+.Dt LS 1
+.Os
+.Sh NAME
+.Nm ls
+.Nd list directory contents
+.Sh SYNOPSIS
+.Nm
+.Op Fl 1AaBbCcdFfghikLlMmnOoPpqRrSsTtuWwXx
+.Op Ar
+.Sh DESCRIPTION
+For each
+.Ar file
+operand that names a file of a type other than
+directory,
+.Nm
+displays its name as well as any requested,
+associated information.
+For each
+.Ar file
+operand that names a file of type directory,
+.Nm
+displays the names of files contained
+within that directory, as well as any requested, associated
+information.
+.Pp
+If no operands are given, the contents of the current
+directory are displayed.
+If more than one operand is given,
+non-directory operands are displayed first; directory
+and non-directory operands are sorted separately and in
+lexicographical order.
+.Pp
+The following options are available:
+.Bl -tag -width indent
+.It Fl \&1
+(The numeric digit
+.Dq one ) .
+Force output to be one entry per line.
+This is the default when output is not to a terminal.
+.It Fl A
+List all entries except for
+.Ql \&.
+and
+.Ql \&.. .
+Always set for the super-user.
+.It Fl a
+Include directory entries whose names begin with a
+dot
+.Pq Sq \&. .
+.It Fl B
+Force printing of non-graphic characters in file names as \exxx, where xxx
+is the numeric value of the character in octal.
+.It Fl b
+As
+.Fl B ,
+but use C escape codes whenever possible.
+.It Fl C
+Force multi-column output; this is the default when output is to a terminal.
+.It Fl c
+Use time when file status was last changed,
+instead of time of last modification of the file for printing
+.Pq Fl l
+or sorting
+.Pq Fl t .
+Overrides
+.Fl u .
+.It Fl d
+Directories are listed as plain files (not searched recursively) and
+symbolic links in the argument list are not followed.
+Turns off
+.Fl R
+if also given.
+.It Fl F
+Display a slash
+.Pq Sq \&/
+immediately after each pathname that is a directory,
+an asterisk
+.Pq Sq \&*
+after each that is executable,
+an at sign
+.Pq Sq \&@
+after each symbolic link,
+a percent sign
+.Pq Sq \&%
+after each whiteout,
+an equal sign
+.Pq Sq \&=
+after each socket,
+and a vertical bar
+.Pq Sq \&|
+after each that is a
+.Tn FIFO .
+.It Fl f
+Output is not sorted.
+This option implies
+.Fl a .
+.It Fl g
+The same as
+.Fl l ,
+except that the owner is not printed.
+.It Fl h
+Modifies the
+.Fl l
+and
+.Fl s
+options, causing the sizes to be reported in bytes displayed in a human
+readable format.
+Overrides
+.Fl k
+and
+.Fl M .
+.It Fl i
+For each file, print the file's file serial number (inode number).
+.It Fl k
+Modifies the
+.Fl s
+option, causing the sizes to be reported in kilobytes.
+Overrides
+.Fl h .
+.It Fl L
+For each file, if it's a link, evaluate file information and file type
+of the referenced file and not the link itself; however still print
+the link name, unless used with
+.Fl l ,
+for example.
+.It Fl l
+(The lowercase letter
+.Dq ell ) .
+List in long format.
+(See below.)
+.It Fl M
+Modifies the
+.Fl l
+and
+.Fl s
+options, causing the sizes or block counts reported to be separated with
+commas (or a locale appropriate separator) resulting in a more readable
+output.
+Overrides
+.Fl h ;
+does not override
+.Fl k .
+.It Fl m
+Stream output format; list files across the page, separated by commas.
+.It Fl n
+The same as
+.Fl l ,
+except that
+the owner and group IDs are displayed numerically rather than converting
+to a owner or group name.
+.It Fl O
+Output only leaf files (not directories), eliding other
+.Nm
+output.
+.It Fl o
+Include the file flags in a long
+.Pq Fl l
+output.
+If no file flags are set,
+.Dq -
+is displayed.
+(See
+.Xr chflags 1
+for a list of possible flags and their meanings.)
+.It Fl P
+Print the full pathname for each file.
+.It Fl p
+Display a slash
+.Pq Sq \&/
+immediately after each pathname that is a directory.
+.It Fl q
+Force printing of non-printable characters in file names as
+the character
+.Sq \&? ;
+this is the default when output is to a terminal.
+.It Fl R
+Recursively list subdirectories encountered.
+See also
+.Fl d .
+.It Fl r
+Reverse the order of the sort to get reverse
+lexicographical order or the smallest or oldest entries first.
+.It Fl S
+Sort by size, largest file first.
+.It Fl s
+Display the number of file system blocks actually used by each file, in units
+of 512 bytes or
+.Ev BLOCKSIZE
+(see
+.Sx ENVIRONMENT )
+where partial units are rounded up to the
+next integer value.
+If the output is to a terminal, a total sum for all the file
+sizes is output on a line before the listing.
+.It Fl T
+When used with the
+.Fl l
+(the lowercase letter
+.Dq ell )
+option, display complete time information for the file, including
+month, day, hour, minute, second, and year.
+.It Fl t
+Sort by time modified (most recently modified
+first) before sorting the operands by lexicographical
+order.
+.It Fl u
+Use time of last access,
+instead of last modification
+of the file for printing
+.Pq Fl l
+or sorting
+.Pq Fl t .
+Overrides
+.Fl c .
+.It Fl W
+Display whiteouts when scanning directories.
+.It Fl w
+Force raw printing of non-printable characters.
+This is the default when output is not to a terminal.
+.It Fl x
+Multi-column output sorted across the page rather than down the page.
+.It Fl X
+Don't cross mount points when recursing.
+.El
+.Pp
+The
+.Fl B ,
+.Fl b ,
+.Fl q ,
+and
+.Fl w
+options all override each other; the last one specified determines
+the format used for non-printable characters.
+.Pp
+The
+.Fl 1 ,
+.Fl C ,
+.Fl g ,
+.Fl l ,
+.Fl m ,
+and
+.Fl x
+options all override each other; the last one specified determines
+the format used with the exception that if both
+.Fl l
+and
+.Fl g
+are specified,
+.Fl l
+will always override
+.Fl g ,
+even if
+.Fl g
+was specified last.
+.Pp
+By default,
+.Nm
+lists one entry per line to standard
+output; the exceptions are to terminals or when the
+.Fl C
+or
+.Fl m
+options are specified.
+.Pp
+File information is displayed with one or more
+.Aq blank
+characters separating the information associated with the
+.Fl i ,
+.Fl l ,
+and
+.Fl s
+options.
+.Ss The Long Format
+If the
+.Fl l
+option is given, the following information
+is displayed for each file:
+.Bl -item -offset indent -compact
+.It
+file mode
+.It
+number of links
+.It
+owner name
+.It
+group name
+.It
+file flags (if
+.Fl o
+given)
+.It
+number of bytes in the file
+.It
+abbreviated month file was last modified
+.It
+day-of-month file was last modified
+.It
+hour and minute file was last modified
+.It
+pathname
+.El
+.Pp
+In addition, for each directory whose contents are displayed, the total
+number of file system blocks in units of 512 bytes or
+.Ev BLOCKSIZE
+(see
+.Sx ENVIRONMENT )
+used by the files in the directory is displayed on a line by itself
+immediately before the information for the files in the directory.
+.Pp
+If the owner or group names are not a known owner or group name,
+or the
+.Fl n
+option is given,
+the numeric ID's are displayed.
+.Pp
+If the file is a character special or block special file,
+the major and minor device numbers for the file are displayed
+in the size field.
+If the file is a symbolic link the pathname of the
+linked-to file is preceded by
+.Dq \-> .
+.Pp
+The file mode printed under the
+.Fl l
+option consists of the entry type, owner permissions, group
+permissions, and other permissions.
+The entry type character describes the type of file, as
+follows:
+.Pp
+.Bl -tag -width 4n -offset indent -compact
+.It Sy \-
+Regular file.
+.It Sy a
+Archive state 1.
+.It Sy A
+Archive state 2.
+.It Sy b
+Block special file.
+.It Sy c
+Character special file.
+.It Sy d
+Directory.
+.It Sy l
+Symbolic link.
+.It Sy p
+FIFO.
+.It Sy s
+Socket link.
+.It Sy w
+Whiteout.
+.El
+.Pp
+The next three fields
+are three characters each:
+owner permissions,
+group permissions, and
+other permissions.
+Each field has three character positions:
+.Bl -enum -offset indent
+.It
+If
+.Sy r ,
+the file is readable; if
+.Sy \- ,
+it is not readable.
+.It
+If
+.Sy w ,
+the file is writable; if
+.Sy \- ,
+it is not writable.
+.It
+The first of the following that applies:
+.Bl -tag -width 4n -offset indent
+.It Sy S
+If in the owner permissions, the file is not executable and
+set-user-ID mode is set.
+If in the group permissions, the file is not executable
+and set-group-ID mode is set.
+.It Sy s
+If in the owner permissions, the file is executable
+and set-user-ID mode is set.
+If in the group permissions, the file is executable
+and setgroup-ID mode is set.
+.It Sy x
+The file is executable or the directory is
+searchable.
+.It Sy \-
+The file is neither readable, writable, executable,
+nor set-user-ID nor set-group-ID mode, nor sticky.
+(See below.)
+.El
+.Pp
+These next two apply only to the third character in the last group
+(other permissions).
+.Bl -tag -width 4n -offset indent
+.It Sy T
+The sticky bit is set
+(mode
+.Li 1000 ) ,
+but not execute or search permission.
+(See
+.Xr chmod 1
+or
+.Xr sticky 7 . )
+.It Sy t
+The sticky bit is set (mode
+.Li 1000 ) ,
+and is searchable or executable.
+(See
+.Xr chmod 1
+or
+.Xr sticky 7 . )
+.El
+.El
+.Pp
+The number of bytes displayed for a directory is a function of the
+number of
+.Xr dirent 3
+structures in the directory, not all of which may be allocated to
+any existing file.
+.Sh ENVIRONMENT
+The following environment variables affect the execution of
+.Nm :
+.Bl -tag -width BLOCKSIZE
+.It Ev BLOCKSIZE
+If the environment variable
+.Ev BLOCKSIZE
+is set, and the
+.Fl k
+option is not specified, the block counts
+(see
+.Fl l
+and
+.Fl s )
+will be displayed in units of that size block.
+.It Ev COLUMNS
+If this variable contains a string representing a
+decimal integer, it is used as the
+column position width for displaying
+multiple-text-column output.
+The
+.Nm
+utility calculates how
+many pathname text columns to display
+based on the width provided.
+(See
+.Fl C . )
+.It Ev TZ
+The timezone to use when displaying dates.
+See
+.Xr environ 7
+for more information.
+.El
+.Sh EXIT STATUS
+.Ex -std
+.Sh COMPATIBILITY
+The group field is now automatically included in the long listing for
+files in order to be compatible with the
+.St -p1003.2
+specification.
+.Sh SEE ALSO
+.Xr chflags 1 ,
+.Xr chmod 1 ,
+.Xr stat 2 ,
+.Xr dirent 3 ,
+.Xr getbsize 3 ,
+.Xr sticky 7 ,
+.Xr symlink 7
+.Sh STANDARDS
+The
+.Nm
+utility is expected to be a superset of the
+.St -p1003.2
+specification.
+.Sh HISTORY
+An
+.Nm
+utility appeared in
+.At v1 .
diff --git a/bin/ls/ls.c b/bin/ls/ls.c
new file mode 100644
index 0000000..282207f
--- /dev/null
+++ b/bin/ls/ls.c
@@ -0,0 +1,715 @@
+/* $NetBSD: ls.c,v 1.76 2017/02/06 21:06:04 rin Exp $ */
+
+/*
+ * Copyright (c) 1989, 1993, 1994
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Michael Fischbein.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+#ifndef lint
+__COPYRIGHT("@(#) Copyright (c) 1989, 1993, 1994\
+ The Regents of the University of California. All rights reserved.");
+#endif /* not lint */
+
+#ifndef lint
+#if 0
+static char sccsid[] = "@(#)ls.c 8.7 (Berkeley) 8/5/94";
+#else
+__RCSID("$NetBSD: ls.c,v 1.76 2017/02/06 21:06:04 rin Exp $");
+#endif
+#endif /* not lint */
+
+#include <sys/param.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/ioctl.h>
+
+#include <dirent.h>
+#include <err.h>
+#include <errno.h>
+#include <fts.h>
+#include <locale.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <termios.h>
+#include <pwd.h>
+#include <grp.h>
+#include <util.h>
+
+#include "ls.h"
+#include "extern.h"
+
+static void display(FTSENT *, FTSENT *);
+static int mastercmp(const FTSENT **, const FTSENT **);
+static void traverse(int, char **, int);
+
+static void (*printfcn)(DISPLAY *);
+static int (*sortfcn)(const FTSENT *, const FTSENT *);
+
+#define BY_NAME 0
+#define BY_SIZE 1
+#define BY_TIME 2
+
+long blocksize; /* block size units */
+int termwidth = 80; /* default terminal width */
+int sortkey = BY_NAME;
+int rval = EXIT_SUCCESS; /* exit value - set if error encountered */
+
+/* flags */
+int f_accesstime; /* use time of last access */
+int f_column; /* columnated format */
+int f_columnacross; /* columnated format, sorted across */
+int f_flags; /* show flags associated with a file */
+int f_grouponly; /* long listing without owner */
+int f_humanize; /* humanize the size field */
+int f_commas; /* separate size field with comma */
+int f_inode; /* print inode */
+int f_listdir; /* list actual directory, not contents */
+int f_listdot; /* list files beginning with . */
+int f_longform; /* long listing format */
+int f_nonprint; /* show unprintables as ? */
+int f_nosort; /* don't sort output */
+int f_numericonly; /* don't convert uid/gid to name */
+int f_octal; /* print octal escapes for nongraphic characters */
+int f_octal_escape; /* like f_octal but use C escapes if possible */
+int f_recursive; /* ls subdirectories also */
+int f_reversesort; /* reverse whatever sort is used */
+int f_sectime; /* print the real time for all files */
+int f_singlecol; /* use single column output */
+int f_size; /* list size in short listing */
+int f_statustime; /* use time of last mode change */
+int f_stream; /* stream format */
+int f_type; /* add type character for non-regular files */
+int f_typedir; /* add type character for directories */
+int f_whiteout; /* show whiteout entries */
+int f_fullpath; /* print full pathname, not filename */
+int f_leafonly; /* when recursing, print leaf names only */
+
+__dead static void
+usage(void)
+{
+
+ (void)fprintf(stderr,
+ "usage: %s [-1AaBbCcdFfghikLlMmnOoPpqRrSsTtuWwXx] [file ...]\n",
+ getprogname());
+ exit(EXIT_FAILURE);
+ /* NOTREACHED */
+}
+
+int
+ls_main(int argc, char *argv[])
+{
+ static char dot[] = ".", *dotav[] = { dot, NULL };
+ struct winsize win;
+ int ch, fts_options;
+ int kflag = 0;
+ const char *p;
+
+ setprogname(argv[0]);
+ (void)setlocale(LC_ALL, "");
+
+ /* Terminal defaults to -Cq, non-terminal defaults to -1. */
+ if (isatty(STDOUT_FILENO)) {
+ if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &win) == 0 &&
+ win.ws_col > 0)
+ termwidth = win.ws_col;
+ f_column = f_nonprint = 1;
+ } else
+ f_singlecol = 1;
+
+ /* Root is -A automatically. */
+ if (!getuid())
+ f_listdot = 1;
+
+ fts_options = FTS_PHYSICAL;
+ while ((ch = getopt(argc, argv, "1AaBbCcdFfghikLlMmnOoPpqRrSsTtuWwXx"))
+ != -1) {
+ switch (ch) {
+ /*
+ * The -1, -C, -l, -m and -x options all override each other so
+ * shell aliasing works correctly.
+ */
+ case '1':
+ f_singlecol = 1;
+ f_column = f_columnacross = f_longform = f_stream = 0;
+ break;
+ case 'C':
+ f_column = 1;
+ f_columnacross = f_longform = f_singlecol = f_stream =
+ 0;
+ break;
+ case 'g':
+ if (f_grouponly != -1)
+ f_grouponly = 1;
+ f_longform = 1;
+ f_column = f_columnacross = f_singlecol = f_stream = 0;
+ break;
+ case 'l':
+ f_longform = 1;
+ f_column = f_columnacross = f_singlecol = f_stream = 0;
+ /* Never let -g take precedence over -l. */
+ f_grouponly = -1;
+ break;
+ case 'm':
+ f_stream = 1;
+ f_column = f_columnacross = f_longform = f_singlecol =
+ 0;
+ break;
+ case 'x':
+ f_columnacross = 1;
+ f_column = f_longform = f_singlecol = f_stream = 0;
+ break;
+ /* The -c and -u options override each other. */
+ case 'c':
+ f_statustime = 1;
+ f_accesstime = 0;
+ break;
+ case 'u':
+ f_accesstime = 1;
+ f_statustime = 0;
+ break;
+ case 'F':
+ f_type = 1;
+ break;
+ case 'L':
+ fts_options &= ~FTS_PHYSICAL;
+ fts_options |= FTS_LOGICAL;
+ break;
+ case 'R':
+ f_recursive = 1;
+ break;
+ case 'f':
+ f_nosort = 1;
+ /* FALLTHROUGH */
+ case 'a':
+ fts_options |= FTS_SEEDOT;
+ /* FALLTHROUGH */
+ case 'A':
+ f_listdot = 1;
+ break;
+ /* The -B option turns off the -b, -q and -w options. */
+ case 'B':
+ f_nonprint = 0;
+ f_octal = 1;
+ f_octal_escape = 0;
+ break;
+ /* The -b option turns off the -B, -q and -w options. */
+ case 'b':
+ f_nonprint = 0;
+ f_octal = 0;
+ f_octal_escape = 1;
+ break;
+ /* The -d option turns off the -R option. */
+ case 'd':
+ f_listdir = 1;
+ f_recursive = 0;
+ break;
+ case 'i':
+ f_inode = 1;
+ break;
+ case 'k':
+ blocksize = 1024;
+ kflag = 1;
+ f_humanize = 0;
+ break;
+ /* The -h option forces all sizes to be measured in bytes. */
+ case 'h':
+ f_humanize = 1;
+ kflag = 0;
+ f_commas = 0;
+ break;
+ case 'M':
+ f_humanize = 0;
+ f_commas = 1;
+ break;
+ case 'n':
+ f_numericonly = 1;
+ f_longform = 1;
+ f_column = f_columnacross = f_singlecol = f_stream = 0;
+ break;
+ case 'O':
+ f_leafonly = 1;
+ break;
+ case 'o':
+ f_flags = 1;
+ break;
+ case 'P':
+ f_fullpath = 1;
+ break;
+ case 'p':
+ f_typedir = 1;
+ break;
+ /* The -q option turns off the -B, -b and -w options. */
+ case 'q':
+ f_nonprint = 1;
+ f_octal = 0;
+ f_octal_escape = 0;
+ break;
+ case 'r':
+ f_reversesort = 1;
+ break;
+ case 'S':
+ sortkey = BY_SIZE;
+ break;
+ case 's':
+ f_size = 1;
+ break;
+ case 'T':
+ f_sectime = 1;
+ break;
+ case 't':
+ sortkey = BY_TIME;
+ break;
+ case 'W':
+ f_whiteout = 1;
+ break;
+ /* The -w option turns off the -B, -b and -q options. */
+ case 'w':
+ f_nonprint = 0;
+ f_octal = 0;
+ f_octal_escape = 0;
+ break;
+ case 'X':
+ fts_options |= FTS_XDEV;
+ break;
+ default:
+ case '?':
+ usage();
+ }
+ }
+ argc -= optind;
+ argv += optind;
+
+ if (f_column || f_columnacross || f_stream) {
+ if ((p = getenv("COLUMNS")) != NULL)
+ termwidth = atoi(p);
+ }
+
+ /*
+ * If both -g and -l options, let -l take precedence.
+ */
+ if (f_grouponly == -1)
+ f_grouponly = 0;
+
+ /*
+ * If not -F, -i, -l, -p, -S, -s or -t options, don't require stat
+ * information.
+ */
+ if (!f_inode && !f_longform && !f_size && !f_type && !f_typedir &&
+ sortkey == BY_NAME)
+ fts_options |= FTS_NOSTAT;
+
+ /*
+ * If not -F, -d or -l options, follow any symbolic links listed on
+ * the command line.
+ */
+ if (!f_longform && !f_listdir && !f_type)
+ fts_options |= FTS_COMFOLLOW;
+
+ /*
+ * If -W, show whiteout entries
+ */
+#ifdef FTS_WHITEOUT
+ if (f_whiteout)
+ fts_options |= FTS_WHITEOUT;
+#endif
+
+ /* If -i, -l, or -s, figure out block size. */
+ if (f_inode || f_longform || f_size) {
+ if (!kflag)
+ (void)getbsize(NULL, &blocksize);
+ blocksize /= 512;
+ }
+
+ /* Select a sort function. */
+ if (f_reversesort) {
+ switch (sortkey) {
+ case BY_NAME:
+ sortfcn = revnamecmp;
+ break;
+ case BY_SIZE:
+ sortfcn = revsizecmp;
+ break;
+ case BY_TIME:
+ if (f_accesstime)
+ sortfcn = revacccmp;
+ else if (f_statustime)
+ sortfcn = revstatcmp;
+ else /* Use modification time. */
+ sortfcn = revmodcmp;
+ break;
+ }
+ } else {
+ switch (sortkey) {
+ case BY_NAME:
+ sortfcn = namecmp;
+ break;
+ case BY_SIZE:
+ sortfcn = sizecmp;
+ break;
+ case BY_TIME:
+ if (f_accesstime)
+ sortfcn = acccmp;
+ else if (f_statustime)
+ sortfcn = statcmp;
+ else /* Use modification time. */
+ sortfcn = modcmp;
+ break;
+ }
+ }
+
+ /* Select a print function. */
+ if (f_singlecol)
+ printfcn = printscol;
+ else if (f_columnacross)
+ printfcn = printacol;
+ else if (f_longform)
+ printfcn = printlong;
+ else if (f_stream)
+ printfcn = printstream;
+ else
+ printfcn = printcol;
+
+ if (argc)
+ traverse(argc, argv, fts_options);
+ else
+ traverse(1, dotav, fts_options);
+ return rval;
+ /* NOTREACHED */
+}
+
+static int output; /* If anything output. */
+
+/*
+ * Traverse() walks the logical directory structure specified by the argv list
+ * in the order specified by the mastercmp() comparison function. During the
+ * traversal it passes linked lists of structures to display() which represent
+ * a superset (may be exact set) of the files to be displayed.
+ */
+static void
+traverse(int argc, char *argv[], int options)
+{
+ FTS *ftsp;
+ FTSENT *p, *chp;
+ int ch_options, error;
+
+ if ((ftsp =
+ fts_open(argv, options, f_nosort ? NULL : mastercmp)) == NULL)
+ err(EXIT_FAILURE, NULL);
+
+ display(NULL, fts_children(ftsp, 0));
+ if (f_listdir) {
+ (void)fts_close(ftsp);
+ return;
+ }
+
+ /*
+ * If not recursing down this tree and don't need stat info, just get
+ * the names.
+ */
+ ch_options = !f_recursive && options & FTS_NOSTAT ? FTS_NAMEONLY : 0;
+
+ while ((p = fts_read(ftsp)) != NULL)
+ switch (p->fts_info) {
+ case FTS_DC:
+ warnx("%s: directory causes a cycle", p->fts_name);
+ break;
+ case FTS_DNR:
+ case FTS_ERR:
+ warnx("%s: %s", p->fts_name, strerror(p->fts_errno));
+ rval = EXIT_FAILURE;
+ break;
+ case FTS_D:
+ if (p->fts_level != FTS_ROOTLEVEL &&
+ p->fts_name[0] == '.' && !f_listdot)
+ break;
+
+ /*
+ * If already output something, put out a newline as
+ * a separator. If multiple arguments, precede each
+ * directory with its name.
+ */
+ if (!f_leafonly) {
+ if (output)
+ (void)printf("\n%s:\n", p->fts_path);
+ else if (argc > 1) {
+ (void)printf("%s:\n", p->fts_path);
+ output = 1;
+ }
+ }
+
+ chp = fts_children(ftsp, ch_options);
+ display(p, chp);
+
+ if (!f_recursive && chp != NULL)
+ (void)fts_set(ftsp, p, FTS_SKIP);
+ break;
+ }
+ error = errno;
+ (void)fts_close(ftsp);
+ errno = error;
+ if (errno)
+ err(EXIT_FAILURE, "fts_read");
+}
+
+/*
+ * Display() takes a linked list of FTSENT structures and passes the list
+ * along with any other necessary information to the print function. P
+ * points to the parent directory of the display list.
+ */
+static void
+display(FTSENT *p, FTSENT *list)
+{
+ struct stat *sp;
+ DISPLAY d;
+ FTSENT *cur;
+ NAMES *np;
+ u_int64_t btotal, stotal;
+ off_t maxsize;
+ blkcnt_t maxblock;
+ ino_t maxinode;
+ int maxmajor, maxminor;
+ uint32_t maxnlink;
+ int bcfile, entries, flen, glen, ulen, maxflags, maxgroup;
+ unsigned int maxlen;
+ int maxuser, needstats;
+ const char *user, *group;
+ char buf[21]; /* 64 bits == 20 digits, +1 for NUL */
+ char nuser[12], ngroup[12];
+ char *flags = NULL;
+
+ /*
+ * If list is NULL there are two possibilities: that the parent
+ * directory p has no children, or that fts_children() returned an
+ * error. We ignore the error case since it will be replicated
+ * on the next call to fts_read() on the post-order visit to the
+ * directory p, and will be signalled in traverse().
+ */
+ if (list == NULL)
+ return;
+
+ needstats = f_inode || f_longform || f_size;
+ flen = 0;
+ maxinode = maxnlink = 0;
+ bcfile = 0;
+ maxuser = maxgroup = maxflags = maxlen = 0;
+ btotal = stotal = maxblock = maxsize = 0;
+ maxmajor = maxminor = 0;
+ for (cur = list, entries = 0; cur; cur = cur->fts_link) {
+ if (cur->fts_info == FTS_ERR || cur->fts_info == FTS_NS) {
+ warnx("%s: %s",
+ cur->fts_name, strerror(cur->fts_errno));
+ cur->fts_number = NO_PRINT;
+ rval = EXIT_FAILURE;
+ continue;
+ }
+
+ /*
+ * P is NULL if list is the argv list, to which different rules
+ * apply.
+ */
+ if (p == NULL) {
+ /* Directories will be displayed later. */
+ if (cur->fts_info == FTS_D && !f_listdir) {
+ cur->fts_number = NO_PRINT;
+ continue;
+ }
+ } else {
+ /* Only display dot file if -a/-A set. */
+ if (cur->fts_name[0] == '.' && !f_listdot) {
+ cur->fts_number = NO_PRINT;
+ continue;
+ }
+ }
+ if (cur->fts_namelen > maxlen)
+ maxlen = cur->fts_namelen;
+ if (needstats) {
+ sp = cur->fts_statp;
+ if (sp->st_blocks > maxblock)
+ maxblock = sp->st_blocks;
+ if (sp->st_ino > maxinode)
+ maxinode = sp->st_ino;
+ if (sp->st_nlink > maxnlink)
+ maxnlink = sp->st_nlink;
+ if (sp->st_size > maxsize)
+ maxsize = sp->st_size;
+ if (S_ISCHR(sp->st_mode) || S_ISBLK(sp->st_mode)) {
+ bcfile = 1;
+ if (major(sp->st_rdev) > maxmajor)
+ maxmajor = major(sp->st_rdev);
+ if (minor(sp->st_rdev) > maxminor)
+ maxminor = minor(sp->st_rdev);
+ }
+
+ btotal += sp->st_blocks;
+ stotal += sp->st_size;
+ if (f_longform) {
+ if (f_numericonly ||
+ (user = user_from_uid(sp->st_uid, 0)) ==
+ NULL) {
+ (void)snprintf(nuser, sizeof(nuser),
+ "%u", sp->st_uid);
+ user = nuser;
+ }
+ if (f_numericonly ||
+ (group = group_from_gid(sp->st_gid, 0)) ==
+ NULL) {
+ (void)snprintf(ngroup, sizeof(ngroup),
+ "%u", sp->st_gid);
+ group = ngroup;
+ }
+ if ((ulen = strlen(user)) > maxuser)
+ maxuser = ulen;
+ if ((glen = strlen(group)) > maxgroup)
+ maxgroup = glen;
+ if (f_flags) {
+ flags =
+ flags_to_string((u_long)sp->st_flags, "-");
+ if ((flen = strlen(flags)) > maxflags)
+ maxflags = flen;
+ } else
+ flen = 0;
+
+ if ((np = malloc(sizeof(NAMES) +
+ ulen + glen + flen + 2)) == NULL)
+ err(EXIT_FAILURE, NULL);
+
+ np->user = &np->data[0];
+ (void)strcpy(np->user, user);
+ np->group = &np->data[ulen + 1];
+ (void)strcpy(np->group, group);
+
+ if (f_flags) {
+ np->flags = &np->data[ulen + glen + 2];
+ (void)strcpy(np->flags, flags);
+ free(flags);
+ }
+ cur->fts_pointer = np;
+ }
+ }
+ ++entries;
+ }
+
+ if (!entries)
+ return;
+
+ d.list = list;
+ d.entries = entries;
+ d.maxlen = maxlen;
+ if (needstats) {
+ d.btotal = btotal;
+ d.stotal = stotal;
+ if (f_humanize) {
+ d.s_block = 4; /* min buf length for humanize_number */
+ } else {
+ (void)snprintf(buf, sizeof(buf), "%lld",
+ (long long)howmany(maxblock, blocksize));
+ d.s_block = strlen(buf);
+ if (f_commas) /* allow for commas before every third digit */
+ d.s_block += (d.s_block - 1) / 3;
+ }
+ d.s_flags = maxflags;
+ d.s_group = maxgroup;
+ (void)snprintf(buf, sizeof(buf), "%llu",
+ (unsigned long long)maxinode);
+ d.s_inode = strlen(buf);
+ (void)snprintf(buf, sizeof(buf), "%u", maxnlink);
+ d.s_nlink = strlen(buf);
+ if (f_humanize) {
+ d.s_size = 4; /* min buf length for humanize_number */
+ } else {
+ (void)snprintf(buf, sizeof(buf), "%lld",
+ (long long)maxsize);
+ d.s_size = strlen(buf);
+ if (f_commas) /* allow for commas before every third digit */
+ d.s_size += (d.s_size - 1) / 3;
+ }
+ d.s_user = maxuser;
+ if (bcfile) {
+ (void)snprintf(buf, sizeof(buf), "%d", maxmajor);
+ d.s_major = strlen(buf);
+ (void)snprintf(buf, sizeof(buf), "%d", maxminor);
+ d.s_minor = strlen(buf);
+ if (d.s_major + d.s_minor + 2 > d.s_size)
+ d.s_size = d.s_major + d.s_minor + 2;
+ else if (d.s_size - d.s_minor - 2 > d.s_major)
+ d.s_major = d.s_size - d.s_minor - 2;
+ } else {
+ d.s_major = 0;
+ d.s_minor = 0;
+ }
+ }
+
+ printfcn(&d);
+ output = 1;
+
+ if (f_longform)
+ for (cur = list; cur; cur = cur->fts_link)
+ free(cur->fts_pointer);
+}
+
+/*
+ * Ordering for mastercmp:
+ * If ordering the argv (fts_level = FTS_ROOTLEVEL) return non-directories
+ * as larger than directories. Within either group, use the sort function.
+ * All other levels use the sort function. Error entries remain unsorted.
+ */
+static int
+mastercmp(const FTSENT **a, const FTSENT **b)
+{
+ int a_info, b_info;
+
+ a_info = (*a)->fts_info;
+ if (a_info == FTS_ERR)
+ return (0);
+ b_info = (*b)->fts_info;
+ if (b_info == FTS_ERR)
+ return (0);
+
+ if (a_info == FTS_NS || b_info == FTS_NS) {
+ if (b_info != FTS_NS)
+ return (1);
+ else if (a_info != FTS_NS)
+ return (-1);
+ else
+ return (namecmp(*a, *b));
+ }
+
+ if (a_info != b_info && !f_listdir &&
+ (*a)->fts_level == FTS_ROOTLEVEL) {
+ if (a_info == FTS_D)
+ return (1);
+ else if (b_info == FTS_D)
+ return (-1);
+ }
+ return (sortfcn(*a, *b));
+}
diff --git a/bin/ls/ls.h b/bin/ls/ls.h
new file mode 100644
index 0000000..d9b6505
--- /dev/null
+++ b/bin/ls/ls.h
@@ -0,0 +1,81 @@
+/* $NetBSD: ls.h,v 1.19 2014/02/20 18:56:36 christos Exp $ */
+
+/*
+ * Copyright (c) 1989, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Michael Fischbein.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * @(#)ls.h 8.1 (Berkeley) 5/31/93
+ */
+
+#define NO_PRINT 1
+
+extern long blocksize; /* block size units */
+
+extern int f_accesstime; /* use time of last access */
+extern int f_flags; /* show flags associated with a file */
+extern int f_grouponly; /* long listing without owner */
+extern int f_humanize; /* humanize size field */
+extern int f_commas; /* separate size field with commas */
+extern int f_inode; /* print inode */
+extern int f_longform; /* long listing format */
+extern int f_octal; /* print octal escapes for nongraphic characters */
+extern int f_octal_escape; /* like f_octal but use C escapes if possible */
+extern int f_sectime; /* print the real time for all files */
+extern int f_size; /* list size in short listing */
+extern int f_statustime; /* use time of last mode change */
+extern int f_type; /* add type character for non-regular files */
+extern int f_typedir; /* add type character for directories */
+extern int f_nonprint; /* show unprintables as ? */
+extern int f_fullpath; /* print full pathname, not filename */
+extern int f_leafonly; /* when recursing, print leaf names only */
+
+typedef struct {
+ FTSENT *list;
+ u_int64_t btotal;
+ u_int64_t stotal;
+ int entries;
+ unsigned int maxlen;
+ int s_block;
+ int s_flags;
+ int s_group;
+ int s_inode;
+ int s_nlink;
+ int s_size;
+ int s_user;
+ int s_major;
+ int s_minor;
+} DISPLAY;
+
+typedef struct {
+ char *user;
+ char *group;
+ char *flags;
+ char data[1];
+} NAMES;
diff --git a/bin/ls/main.c b/bin/ls/main.c
new file mode 100644
index 0000000..c1316aa
--- /dev/null
+++ b/bin/ls/main.c
@@ -0,0 +1,50 @@
+/* $NetBSD: main.c,v 1.5 2016/09/05 01:00:07 sevan Exp $ */
+
+/*-
+ * Copyright (c) 1999 The NetBSD Foundation, Inc.
+ * All rights reserved.
+ *
+ * This code is derived from software contributed to The NetBSD Foundation
+ * by Luke Mewburn.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
+ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+
+#ifndef lint
+__RCSID("$NetBSD: main.c,v 1.5 2016/09/05 01:00:07 sevan Exp $");
+#endif /* not lint */
+
+#include <sys/types.h>
+#include <fts.h>
+
+#include "ls.h"
+#include "extern.h"
+
+int
+main(int argc, char *argv[])
+{
+
+ return ls_main(argc, argv);
+ /* NOTREACHED */
+}
diff --git a/bin/ls/print.c b/bin/ls/print.c
new file mode 100644
index 0000000..b3aecd8
--- /dev/null
+++ b/bin/ls/print.c
@@ -0,0 +1,497 @@
+/* $NetBSD: print.c,v 1.55 2014/05/10 09:39:18 martin Exp $ */
+
+/*
+ * Copyright (c) 1989, 1993, 1994
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Michael Fischbein.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+#ifndef lint
+#if 0
+static char sccsid[] = "@(#)print.c 8.5 (Berkeley) 7/28/94";
+#else
+__RCSID("$NetBSD: print.c,v 1.55 2014/05/10 09:39:18 martin Exp $");
+#endif
+#endif /* not lint */
+
+#include <sys/param.h>
+#include <sys/stat.h>
+
+#include <err.h>
+#include <errno.h>
+#include <inttypes.h>
+#include <fts.h>
+#include <grp.h>
+#include <pwd.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <tzfile.h>
+#include <unistd.h>
+#include <util.h>
+
+#include "ls.h"
+#include "extern.h"
+
+extern int termwidth;
+
+static int printaname(FTSENT *, int, int);
+static void printlink(FTSENT *);
+static void printtime(time_t);
+static void printtotal(DISPLAY *dp);
+static int printtype(u_int);
+
+static time_t now;
+
+#define IS_NOPRINT(p) ((p)->fts_number == NO_PRINT)
+
+static int
+safe_printpath(const FTSENT *p) {
+ int chcnt;
+
+ if (f_fullpath) {
+ chcnt = safe_print(p->fts_path);
+ chcnt += safe_print("/");
+ } else
+ chcnt = 0;
+ return chcnt + safe_print(p->fts_name);
+}
+
+static int
+printescapedpath(const FTSENT *p) {
+ int chcnt;
+
+ if (f_fullpath) {
+ chcnt = printescaped(p->fts_path);
+ chcnt += printescaped("/");
+ } else
+ chcnt = 0;
+
+ return chcnt + printescaped(p->fts_name);
+}
+
+static int
+printpath(const FTSENT *p) {
+ if (f_fullpath)
+ return printf("%s/%s", p->fts_path, p->fts_name);
+ else
+ return printf("%s", p->fts_name);
+}
+
+void
+printscol(DISPLAY *dp)
+{
+ FTSENT *p;
+
+ for (p = dp->list; p; p = p->fts_link) {
+ if (IS_NOPRINT(p))
+ continue;
+ (void)printaname(p, dp->s_inode, dp->s_block);
+ (void)putchar('\n');
+ }
+}
+
+void
+printlong(DISPLAY *dp)
+{
+ struct stat *sp;
+ FTSENT *p;
+ NAMES *np;
+ char buf[20], szbuf[5];
+
+ now = time(NULL);
+
+ if (!f_leafonly)
+ printtotal(dp); /* "total: %u\n" */
+
+ for (p = dp->list; p; p = p->fts_link) {
+ if (IS_NOPRINT(p))
+ continue;
+ sp = p->fts_statp;
+ if (f_inode)
+ (void)printf("%*"PRIu64" ", dp->s_inode, sp->st_ino);
+ if (f_size) {
+ if (f_humanize) {
+ if ((humanize_number(szbuf, sizeof(szbuf),
+ sp->st_blocks * S_BLKSIZE,
+ "", HN_AUTOSCALE,
+ (HN_DECIMAL | HN_B | HN_NOSPACE))) == -1)
+ err(1, "humanize_number");
+ (void)printf("%*s ", dp->s_block, szbuf);
+ } else {
+ (void)printf(f_commas ? "%'*llu " : "%*llu ",
+ dp->s_block,
+ (unsigned long long)howmany(sp->st_blocks,
+ blocksize));
+ }
+ }
+ (void)strmode(sp->st_mode, buf);
+ np = p->fts_pointer;
+ (void)printf("%s %*lu ", buf, dp->s_nlink,
+ (unsigned long)sp->st_nlink);
+ if (!f_grouponly)
+ (void)printf("%-*s ", dp->s_user, np->user);
+ (void)printf("%-*s ", dp->s_group, np->group);
+ if (f_flags)
+ (void)printf("%-*s ", dp->s_flags, np->flags);
+ if (S_ISCHR(sp->st_mode) || S_ISBLK(sp->st_mode))
+ (void)printf("%*lld, %*lld ",
+ dp->s_major, (long long)major(sp->st_rdev),
+ dp->s_minor, (long long)minor(sp->st_rdev));
+ else
+ if (f_humanize) {
+ if ((humanize_number(szbuf, sizeof(szbuf),
+ sp->st_size, "", HN_AUTOSCALE,
+ (HN_DECIMAL | HN_B | HN_NOSPACE))) == -1)
+ err(1, "humanize_number");
+ (void)printf("%*s ", dp->s_size, szbuf);
+ } else {
+ (void)printf(f_commas ? "%'*llu " : "%*llu ",
+ dp->s_size, (unsigned long long)
+ sp->st_size);
+ }
+ if (f_accesstime)
+ printtime(sp->st_atime);
+ else if (f_statustime)
+ printtime(sp->st_ctime);
+ else
+ printtime(sp->st_mtime);
+ if (f_octal || f_octal_escape)
+ (void)safe_printpath(p);
+ else if (f_nonprint)
+ (void)printescapedpath(p);
+ else
+ (void)printpath(p);
+
+ if (f_type || (f_typedir && S_ISDIR(sp->st_mode)))
+ (void)printtype(sp->st_mode);
+ if (S_ISLNK(sp->st_mode))
+ printlink(p);
+ (void)putchar('\n');
+ }
+}
+
+void
+printcol(DISPLAY *dp)
+{
+ static FTSENT **array;
+ static int lastentries = -1;
+ FTSENT *p;
+ int base, chcnt, col, colwidth, num;
+ int numcols, numrows, row;
+
+ colwidth = dp->maxlen;
+ if (f_inode)
+ colwidth += dp->s_inode + 1;
+ if (f_size) {
+ if (f_humanize)
+ colwidth += dp->s_size + 1;
+ else
+ colwidth += dp->s_block + 1;
+ }
+ if (f_type || f_typedir)
+ colwidth += 1;
+
+ colwidth += 1;
+
+ if (termwidth < 2 * colwidth) {
+ printscol(dp);
+ return;
+ }
+
+ /*
+ * Have to do random access in the linked list -- build a table
+ * of pointers.
+ */
+ if (dp->entries > lastentries) {
+ FTSENT **newarray;
+
+ newarray = realloc(array, dp->entries * sizeof(FTSENT *));
+ if (newarray == NULL) {
+ warn(NULL);
+ printscol(dp);
+ return;
+ }
+ lastentries = dp->entries;
+ array = newarray;
+ }
+ for (p = dp->list, num = 0; p; p = p->fts_link)
+ if (p->fts_number != NO_PRINT)
+ array[num++] = p;
+
+ numcols = termwidth / colwidth;
+ colwidth = termwidth / numcols; /* spread out if possible */
+ numrows = num / numcols;
+ if (num % numcols)
+ ++numrows;
+
+ printtotal(dp); /* "total: %u\n" */
+
+ for (row = 0; row < numrows; ++row) {
+ for (base = row, chcnt = col = 0; col < numcols; ++col) {
+ chcnt = printaname(array[base], dp->s_inode,
+ f_humanize ? dp->s_size : dp->s_block);
+ if ((base += numrows) >= num)
+ break;
+ while (chcnt++ < colwidth)
+ (void)putchar(' ');
+ }
+ (void)putchar('\n');
+ }
+}
+
+void
+printacol(DISPLAY *dp)
+{
+ FTSENT *p;
+ int chcnt, col, colwidth;
+ int numcols;
+
+ colwidth = dp->maxlen;
+ if (f_inode)
+ colwidth += dp->s_inode + 1;
+ if (f_size) {
+ if (f_humanize)
+ colwidth += dp->s_size + 1;
+ else
+ colwidth += dp->s_block + 1;
+ }
+ if (f_type || f_typedir)
+ colwidth += 1;
+
+ colwidth += 1;
+
+ if (termwidth < 2 * colwidth) {
+ printscol(dp);
+ return;
+ }
+
+ numcols = termwidth / colwidth;
+ colwidth = termwidth / numcols; /* spread out if possible */
+
+ printtotal(dp); /* "total: %u\n" */
+
+ chcnt = col = 0;
+ for (p = dp->list; p; p = p->fts_link) {
+ if (IS_NOPRINT(p))
+ continue;
+ if (col >= numcols) {
+ chcnt = col = 0;
+ (void)putchar('\n');
+ }
+ chcnt = printaname(p, dp->s_inode,
+ f_humanize ? dp->s_size : dp->s_block);
+ while (chcnt++ < colwidth)
+ (void)putchar(' ');
+ col++;
+ }
+ (void)putchar('\n');
+}
+
+void
+printstream(DISPLAY *dp)
+{
+ FTSENT *p;
+ int col;
+ int extwidth;
+
+ extwidth = 0;
+ if (f_inode)
+ extwidth += dp->s_inode + 1;
+ if (f_size) {
+ if (f_humanize)
+ extwidth += dp->s_size + 1;
+ else
+ extwidth += dp->s_block + 1;
+ }
+ if (f_type)
+ extwidth += 1;
+
+ for (col = 0, p = dp->list; p != NULL; p = p->fts_link) {
+ if (IS_NOPRINT(p))
+ continue;
+ if (col > 0) {
+ (void)putchar(','), col++;
+ if (col + 1 + extwidth + (int)p->fts_namelen >= termwidth)
+ (void)putchar('\n'), col = 0;
+ else
+ (void)putchar(' '), col++;
+ }
+ col += printaname(p, dp->s_inode,
+ f_humanize ? dp->s_size : dp->s_block);
+ }
+ (void)putchar('\n');
+}
+
+/*
+ * print [inode] [size] name
+ * return # of characters printed, no trailing characters.
+ */
+static int
+printaname(FTSENT *p, int inodefield, int sizefield)
+{
+ struct stat *sp;
+ int chcnt;
+ char szbuf[5];
+
+ sp = p->fts_statp;
+ chcnt = 0;
+ if (f_inode)
+ chcnt += printf("%*"PRIu64" ", inodefield, sp->st_ino);
+ if (f_size) {
+ if (f_humanize) {
+ if ((humanize_number(szbuf, sizeof(szbuf), sp->st_size,
+ "", HN_AUTOSCALE,
+ (HN_DECIMAL | HN_B | HN_NOSPACE))) == -1)
+ err(1, "humanize_number");
+ chcnt += printf("%*s ", sizefield, szbuf);
+ } else {
+ chcnt += printf(f_commas ? "%'*llu " : "%*llu ",
+ sizefield, (unsigned long long)
+ howmany(sp->st_blocks, blocksize));
+ }
+ }
+ if (f_octal || f_octal_escape)
+ chcnt += safe_printpath(p);
+ else if (f_nonprint)
+ chcnt += printescapedpath(p);
+ else
+ chcnt += printpath(p);
+ if (f_type || (f_typedir && S_ISDIR(sp->st_mode)))
+ chcnt += printtype(sp->st_mode);
+ return (chcnt);
+}
+
+static void
+printtime(time_t ftime)
+{
+ int i;
+ const char *longstring;
+
+ if ((longstring = ctime(&ftime)) == NULL) {
+ /* 012345678901234567890123 */
+ longstring = "????????????????????????";
+ }
+ for (i = 4; i < 11; ++i)
+ (void)putchar(longstring[i]);
+
+#define SIXMONTHS ((DAYSPERNYEAR / 2) * SECSPERDAY)
+ if (f_sectime)
+ for (i = 11; i < 24; i++)
+ (void)putchar(longstring[i]);
+ else if (ftime + SIXMONTHS > now && ftime - SIXMONTHS < now)
+ for (i = 11; i < 16; ++i)
+ (void)putchar(longstring[i]);
+ else {
+ (void)putchar(' ');
+ for (i = 20; i < 24; ++i)
+ (void)putchar(longstring[i]);
+ }
+ (void)putchar(' ');
+}
+
+/*
+ * Display total used disk space in the form "total: %u\n".
+ * Note: POSIX (IEEE Std 1003.1-2001) says this should be always in 512 blocks,
+ * but we humanise it with -h, or separate it with commas with -M, and use 1024
+ * with -k.
+ */
+static void
+printtotal(DISPLAY *dp)
+{
+ char szbuf[5];
+
+ if (dp->list->fts_level != FTS_ROOTLEVEL && (f_longform || f_size)) {
+ if (f_humanize) {
+ if ((humanize_number(szbuf, sizeof(szbuf), (int64_t)dp->stotal,
+ "", HN_AUTOSCALE,
+ (HN_DECIMAL | HN_B | HN_NOSPACE))) == -1)
+ err(1, "humanize_number");
+ (void)printf("total %s\n", szbuf);
+ } else {
+ (void)printf(f_commas ? "total %'llu\n" :
+ "total %llu\n", (unsigned long long)
+ howmany(dp->btotal, blocksize));
+ }
+ }
+}
+
+static int
+printtype(u_int mode)
+{
+ switch (mode & S_IFMT) {
+ case S_IFDIR:
+ (void)putchar('/');
+ return (1);
+ case S_IFIFO:
+ (void)putchar('|');
+ return (1);
+ case S_IFLNK:
+ (void)putchar('@');
+ return (1);
+ case S_IFSOCK:
+ (void)putchar('=');
+ return (1);
+ case S_IFWHT:
+ (void)putchar('%');
+ return (1);
+ }
+ if (mode & (S_IXUSR | S_IXGRP | S_IXOTH)) {
+ (void)putchar('*');
+ return (1);
+ }
+ return (0);
+}
+
+static void
+printlink(FTSENT *p)
+{
+ int lnklen;
+ char name[MAXPATHLEN + 1], path[MAXPATHLEN + 1];
+
+ if (p->fts_level == FTS_ROOTLEVEL)
+ (void)snprintf(name, sizeof(name), "%s", p->fts_name);
+ else
+ (void)snprintf(name, sizeof(name),
+ "%s/%s", p->fts_parent->fts_accpath, p->fts_name);
+ if ((lnklen = readlink(name, path, sizeof(path) - 1)) == -1) {
+ (void)fprintf(stderr, "\nls: %s: %s\n", name, strerror(errno));
+ return;
+ }
+ path[lnklen] = '\0';
+ (void)printf(" -> ");
+ if (f_octal || f_octal_escape)
+ (void)safe_print(path);
+ else if (f_nonprint)
+ (void)printescaped(path);
+ else
+ (void)printf("%s", path);
+}
diff --git a/bin/ls/util.c b/bin/ls/util.c
new file mode 100644
index 0000000..61b0fda
--- /dev/null
+++ b/bin/ls/util.c
@@ -0,0 +1,168 @@
+/* $NetBSD: util.c,v 1.34 2011/08/29 14:44:21 joerg Exp $ */
+
+/*
+ * Copyright (c) 1989, 1993, 1994
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Michael Fischbein.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+#ifndef lint
+#if 0
+static char sccsid[] = "@(#)util.c 8.5 (Berkeley) 4/28/95";
+#else
+__RCSID("$NetBSD: util.c,v 1.34 2011/08/29 14:44:21 joerg Exp $");
+#endif
+#endif /* not lint */
+
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#include <err.h>
+#include <fts.h>
+#include <limits.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <vis.h>
+#include <wchar.h>
+#include <wctype.h>
+
+#include "ls.h"
+#include "extern.h"
+
+int
+safe_print(const char *src)
+{
+ size_t len;
+ char *name;
+ int flags;
+
+ flags = VIS_NL | VIS_OCTAL | VIS_WHITE;
+ if (f_octal_escape)
+ flags |= VIS_CSTYLE;
+
+ len = strlen(src);
+ if (len != 0 && SIZE_T_MAX/len <= 4) {
+ errx(EXIT_FAILURE, "%s: name too long", src);
+ /* NOTREACHED */
+ }
+
+ name = (char *)malloc(4*len+1);
+ if (name != NULL) {
+ len = strvis(name, src, flags);
+ (void)printf("%s", name);
+ free(name);
+ return len;
+ } else
+ errx(EXIT_FAILURE, "out of memory!");
+ /* NOTREACHED */
+}
+
+/*
+ * The reasons why we don't use putwchar(wc) here are:
+ * - If wc == L'\0', we need to restore the initial shift state, but
+ * the C language standard doesn't say that putwchar(L'\0') does.
+ * - It isn't portable to mix a wide-oriented function (i.e. getwchar)
+ * with byte-oriented functions (printf et al.) in same FILE.
+ */
+static int
+printwc(wchar_t wc, mbstate_t *pst)
+{
+ size_t size;
+ char buf[MB_LEN_MAX];
+
+ size = wcrtomb(buf, wc, pst);
+ if (size == (size_t)-1) /* This shouldn't happen, but for sure */
+ return 0;
+ if (wc == L'\0') {
+ /* The following condition must be always true, but for sure */
+ if (size > 0 && buf[size - 1] == '\0')
+ --size;
+ }
+ if (size > 0)
+ fwrite(buf, 1, size, stdout);
+ return wc == L'\0' ? 0 : wcwidth(wc);
+}
+
+int
+printescaped(const char *src)
+{
+ int n = 0;
+ mbstate_t src_state, stdout_state;
+ /* The following +1 is to pass '\0' at the end of src to mbrtowc(). */
+ const char *endptr = src + strlen(src) + 1;
+
+ /*
+ * We have to reset src_state each time in this function, because
+ * the codeset of src pathname may not match with current locale.
+ * Note that if we pass NULL instead of src_state to mbrtowc(),
+ * there is no way to reset the state.
+ */
+ memset(&src_state, 0, sizeof(src_state));
+ memset(&stdout_state, 0, sizeof(stdout_state));
+ while (src < endptr) {
+ wchar_t wc;
+ size_t rv, span = endptr - src;
+
+#if 0
+ /*
+ * XXX - we should fix libc instead.
+ * Theoretically this should work, but our current
+ * implementation of iso2022 module doesn't actually work
+ * as expected, if there are redundant escape sequences
+ * which exceed 32 bytes.
+ */
+ if (span > MB_CUR_MAX)
+ span = MB_CUR_MAX;
+#endif
+ rv = mbrtowc(&wc, src, span, &src_state);
+ if (rv == 0) { /* assert(wc == L'\0'); */
+ /* The following may output a shift sequence. */
+ n += printwc(wc, &stdout_state);
+ break;
+ }
+ if (rv == (size_t)-1) { /* probably errno == EILSEQ */
+ n += printwc(L'?', &stdout_state);
+ /* try to skip 1byte, because there is no better way */
+ src++;
+ memset(&src_state, 0, sizeof(src_state));
+ } else if (rv == (size_t)-2) {
+ if (span < MB_CUR_MAX) { /* incomplete char */
+ n += printwc(L'?', &stdout_state);
+ break;
+ }
+ src += span; /* a redundant shift sequence? */
+ } else {
+ n += printwc(iswprint(wc) ? wc : L'?', &stdout_state);
+ src += rv;
+ }
+ }
+ return n;
+}
diff --git a/bin/mkdir/mkdir.1 b/bin/mkdir/mkdir.1
new file mode 100644
index 0000000..ca797ff
--- /dev/null
+++ b/bin/mkdir/mkdir.1
@@ -0,0 +1,97 @@
+.\" $NetBSD: mkdir.1,v 1.21 2017/07/04 06:49:35 wiz Exp $
+.\"
+.\" Copyright (c) 1989, 1990, 1993
+.\" The Regents of the University of California. All rights reserved.
+.\"
+.\" This code is derived from software contributed to Berkeley by
+.\" the Institute of Electrical and Electronics Engineers, Inc.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\" notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must reproduce the above copyright
+.\" notice, this list of conditions and the following disclaimer in the
+.\" documentation and/or other materials provided with the distribution.
+.\" 3. Neither the name of the University nor the names of its contributors
+.\" may be used to endorse or promote products derived from this software
+.\" without specific prior written permission.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+.\" ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+.\" SUCH DAMAGE.
+.\"
+.\" @(#)mkdir.1 8.2 (Berkeley) 1/25/94
+.\"
+.Dd August 10, 2016
+.Dt MKDIR 1
+.Os
+.Sh NAME
+.Nm mkdir
+.Nd make directories
+.Sh SYNOPSIS
+.Nm
+.Op Fl p
+.Op Fl m Ar mode
+.Ar directory_name ...
+.Sh DESCRIPTION
+.Nm
+creates the directories named as operands, in the order specified,
+using mode
+.Li rwxrwxrwx (\&0777)
+as modified by the current
+.Xr umask 2 .
+.Pp
+The options are as follows:
+.Bl -tag -width indent
+.It Fl m
+Set the file permission bits of the final created directory to
+the specified mode.
+The mode argument can be in any of the formats specified to the
+.Xr chmod 1
+utility.
+If a symbolic mode is specified, the operation characters
+.Dq +
+and
+.Dq -
+are interpreted relative to an initial mode of
+.Dq a=rwx .
+.It Fl p
+Create intermediate directories as required.
+If this option is not specified, the full path prefix of each
+operand must already exist.
+Intermediate directories are created with permission bits of
+.Li rwxrwxrwx (\&0777)
+as modified by the current umask, plus write and search
+permission for the owner.
+Do not consider it an error if the argument directory already exists.
+.El
+.Pp
+The user must have write permission in the parent directory.
+.Sh EXIT STATUS
+.Ex -std mkdir
+.Sh SEE ALSO
+.Xr chmod 1 ,
+.Xr rmdir 1 ,
+.Xr mkdir 2 ,
+.Xr umask 2
+.Sh STANDARDS
+The
+.Nm
+utility is expected to be
+.St -p1003.2
+compatible.
+.Sh HISTORY
+A
+.Nm
+utility appeared in
+.At v1 .
diff --git a/bin/mkdir/mkdir.c b/bin/mkdir/mkdir.c
new file mode 100644
index 0000000..cb60b88
--- /dev/null
+++ b/bin/mkdir/mkdir.c
@@ -0,0 +1,223 @@
+/* $NetBSD: mkdir.c,v 1.38 2011/08/29 14:45:28 joerg Exp $ */
+
+/*
+ * Copyright (c) 1983, 1992, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+#ifndef lint
+__COPYRIGHT("@(#) Copyright (c) 1983, 1992, 1993\
+ The Regents of the University of California. All rights reserved.");
+#endif /* not lint */
+
+#ifndef lint
+#if 0
+static char sccsid[] = "@(#)mkdir.c 8.2 (Berkeley) 1/25/94";
+#else
+__RCSID("$NetBSD: mkdir.c,v 1.38 2011/08/29 14:45:28 joerg Exp $");
+#endif
+#endif /* not lint */
+
+#include <sys/param.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+
+#include <err.h>
+#include <errno.h>
+#include <locale.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+static int mkpath(char *, mode_t, mode_t);
+__dead static void usage(void);
+
+int
+main(int argc, char *argv[])
+{
+ int ch, exitval, pflag;
+ void *set;
+ mode_t mode, dir_mode;
+
+ setprogname(argv[0]);
+ (void)setlocale(LC_ALL, "");
+
+ /*
+ * The default file mode is a=rwx (0777) with selected permissions
+ * removed in accordance with the file mode creation mask. For
+ * intermediate path name components, the mode is the default modified
+ * by u+wx so that the subdirectories can always be created.
+ */
+ mode = (S_IRWXU | S_IRWXG | S_IRWXO) & ~umask(0);
+ dir_mode = mode | S_IWUSR | S_IXUSR;
+
+ pflag = 0;
+ while ((ch = getopt(argc, argv, "m:p")) != -1)
+ switch (ch) {
+ case 'p':
+ pflag = 1;
+ break;
+ case 'm':
+ if ((set = setmode(optarg)) == NULL) {
+ err(EXIT_FAILURE, "Cannot set file mode `%s'",
+ optarg);
+ /* NOTREACHED */
+ }
+ mode = getmode(set, S_IRWXU | S_IRWXG | S_IRWXO);
+ free(set);
+ break;
+ case '?':
+ default:
+ usage();
+ /* NOTREACHED */
+ }
+ argc -= optind;
+ argv += optind;
+
+ if (*argv == NULL) {
+ usage();
+ /* NOTREACHED */
+ }
+
+ for (exitval = EXIT_SUCCESS; *argv != NULL; ++argv) {
+#ifdef notdef
+ char *slash;
+
+ /* Kernel takes care of this */
+ /* Remove trailing slashes, per POSIX. */
+ slash = strrchr(*argv, '\0');
+ while (--slash > *argv && *slash == '/')
+ *slash = '\0';
+#endif
+
+ if (pflag) {
+ if (mkpath(*argv, mode, dir_mode) < 0)
+ exitval = EXIT_FAILURE;
+ } else {
+ if (mkdir(*argv, mode) < 0) {
+ warn("%s", *argv);
+ exitval = EXIT_FAILURE;
+ } else {
+ /*
+ * The mkdir() and umask() calls both honor
+ * only the file permission bits, so if you try
+ * to set a mode including the sticky, setuid,
+ * setgid bits you lose them. So chmod().
+ */
+ if ((mode & ~(S_IRWXU|S_IRWXG|S_IRWXO)) != 0 &&
+ chmod(*argv, mode) == -1) {
+ warn("%s", *argv);
+ exitval = EXIT_FAILURE;
+ }
+ }
+ }
+ }
+ exit(exitval);
+ /* NOTREACHED */
+}
+
+/*
+ * mkpath -- create directories.
+ * path - path
+ * mode - file mode of terminal directory
+ * dir_mode - file mode of intermediate directories
+ */
+static int
+mkpath(char *path, mode_t mode, mode_t dir_mode)
+{
+ struct stat sb;
+ char *slash;
+ int done, rv;
+
+ done = 0;
+ slash = path;
+
+ for (;;) {
+ slash += strspn(slash, "/");
+ slash += strcspn(slash, "/");
+
+ done = (*slash == '\0');
+ *slash = '\0';
+
+ rv = mkdir(path, done ? mode : dir_mode);
+ if (rv < 0) {
+ /*
+ * Can't create; path exists or no perms.
+ * stat() path to determine what's there now.
+ */
+ int sverrno;
+
+ sverrno = errno;
+ if (stat(path, &sb) < 0) {
+ /* Not there; use mkdir()s error */
+ errno = sverrno;
+ warn("%s", path);
+ return -1;
+ }
+ if (!S_ISDIR(sb.st_mode)) {
+ /* Is there, but isn't a directory */
+ errno = ENOTDIR;
+ warn("%s", path);
+ return -1;
+ }
+ } else if (done) {
+ /*
+ * Created ok, and this is the last element
+ */
+ /*
+ * The mkdir() and umask() calls both honor only the
+ * file permission bits, so if you try to set a mode
+ * including the sticky, setuid, setgid bits you lose
+ * them. So chmod().
+ */
+ if ((mode & ~(S_IRWXU|S_IRWXG|S_IRWXO)) != 0 &&
+ chmod(path, mode) == -1) {
+ warn("%s", path);
+ return -1;
+ }
+ }
+
+ if (done) {
+ break;
+ }
+ *slash = '/';
+ }
+
+ return 0;
+}
+
+static void
+usage(void)
+{
+
+ (void)fprintf(stderr, "usage: %s [-p] [-m mode] dirname ...\n",
+ getprogname());
+ exit(EXIT_FAILURE);
+ /* NOTREACHED */
+}
diff --git a/bin/mv/mv.1 b/bin/mv/mv.1
new file mode 100644
index 0000000..7cc3d1c
--- /dev/null
+++ b/bin/mv/mv.1
@@ -0,0 +1,149 @@
+.\" $NetBSD: mv.1,v 1.30 2017/07/04 06:50:04 wiz Exp $
+.\"
+.\" Copyright (c) 1989, 1990, 1993
+.\" The Regents of the University of California. All rights reserved.
+.\"
+.\" This code is derived from software contributed to Berkeley by
+.\" the Institute of Electrical and Electronics Engineers, Inc.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\" notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must reproduce the above copyright
+.\" notice, this list of conditions and the following disclaimer in the
+.\" documentation and/or other materials provided with the distribution.
+.\" 3. Neither the name of the University nor the names of its contributors
+.\" may be used to endorse or promote products derived from this software
+.\" without specific prior written permission.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+.\" ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+.\" SUCH DAMAGE.
+.\"
+.\" @(#)mv.1 8.1 (Berkeley) 5/31/93
+.\"
+.Dd August 10, 2016
+.Dt MV 1
+.Os
+.Sh NAME
+.Nm mv
+.Nd move files
+.Sh SYNOPSIS
+.Nm
+.Op Fl fiv
+.Ar source target
+.Nm
+.Op Fl fiv
+.Ar source ... directory
+.Sh DESCRIPTION
+In its first form, the
+.Nm
+utility renames the file named by the
+.Ar source
+operand to the destination path named by the
+.Ar target
+operand.
+This form is assumed when the last operand does not name an already
+existing directory.
+.Pp
+In its second form,
+.Nm
+moves each file named by a
+.Ar source
+operand to a destination file in the existing directory named by the
+.Ar directory
+operand.
+The destination path for each operand is the pathname produced by the
+concatenation of the last operand, a slash, and the final pathname
+component of the named file.
+.Pp
+The following options are available:
+.Bl -tag -width flag
+.It Fl f
+Do not prompt for confirmation before overwriting the destination
+path.
+.It Fl i
+Causes
+.Nm
+to write a prompt to standard error before moving a file that would
+overwrite an existing file.
+If the response from the standard input begins with the character ``y'',
+the move is attempted.
+.It Fl v
+Cause
+.Nm
+to be verbose, showing files as they are processed.
+.El
+.Pp
+The last of any
+.Fl f
+or
+.Fl i
+options is the one which affects
+.Nm Ns 's
+behavior.
+.Pp
+It is an error for any of the
+.Ar source
+operands to specify a nonexistent file or directory.
+.Pp
+It is an error for the
+.Ar source
+operand to specify a directory if the
+.Ar target
+exists and is not a directory.
+.Pp
+If the destination path does not have a mode which permits writing,
+.Nm
+prompts the user for confirmation as specified for the
+.Fl i
+option.
+.Pp
+Should the
+.Xr rename 2
+call fail because
+.Ar source
+and
+.Ar target
+are on different file systems,
+.Nm
+will remove the destination file, copy the source file to the
+destination, and then remove the source.
+The effect is roughly equivalent to:
+.Bd -literal -offset indent
+rm -f destination_path && \e
+cp -PRp source_file destination_path && \e
+rm -rf source_file
+.Ed
+.Sh EXIT STATUS
+.Ex -std mv
+.Sh SEE ALSO
+.Xr cp 1 ,
+.Xr rename 2 ,
+.Xr symlink 7
+.Sh STANDARDS
+The
+.Nm
+utility is expected to be
+.St -p1003.2
+compatible.
+.Pp
+The
+.Fl v
+option is an extension to
+.St -p1003.2 .
+.Sh HISTORY
+An
+.Nm
+utility appeared in
+.At v1 .
diff --git a/bin/mv/mv.c b/bin/mv/mv.c
new file mode 100644
index 0000000..e318d48
--- /dev/null
+++ b/bin/mv/mv.c
@@ -0,0 +1,427 @@
+/* $NetBSD: mv.c,v 1.45 2016/02/28 10:59:29 mrg Exp $ */
+
+/*
+ * Copyright (c) 1989, 1993, 1994
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Ken Smith of The State University of New York at Buffalo.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+#ifndef lint
+__COPYRIGHT("@(#) Copyright (c) 1989, 1993, 1994\
+ The Regents of the University of California. All rights reserved.");
+#endif /* not lint */
+
+#ifndef lint
+#if 0
+static char sccsid[] = "@(#)mv.c 8.2 (Berkeley) 4/2/94";
+#else
+__RCSID("$NetBSD: mv.c,v 1.45 2016/02/28 10:59:29 mrg Exp $");
+#endif
+#endif /* not lint */
+
+#include <sys/param.h>
+#include <sys/time.h>
+#include <sys/wait.h>
+#include <sys/stat.h>
+#include <sys/extattr.h>
+
+#include <err.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <grp.h>
+#include <locale.h>
+#include <pwd.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "pathnames.h"
+
+static int fflg, iflg, vflg;
+static int stdin_ok;
+static sig_atomic_t pinfo;
+
+static int copy(char *, char *);
+static int do_move(char *, char *);
+static int fastcopy(char *, char *, struct stat *);
+__dead static void usage(void);
+
+static void
+progress(int sig __unused)
+{
+
+ pinfo++;
+}
+
+int
+main(int argc, char *argv[])
+{
+ int ch, len, rval;
+ char *p, *endp;
+ struct stat sb;
+ char path[MAXPATHLEN + 1];
+ size_t baselen;
+
+ setprogname(argv[0]);
+ (void)setlocale(LC_ALL, "");
+
+ while ((ch = getopt(argc, argv, "ifv")) != -1)
+ switch (ch) {
+ case 'i':
+ fflg = 0;
+ iflg = 1;
+ break;
+ case 'f':
+ iflg = 0;
+ fflg = 1;
+ break;
+ case 'v':
+ vflg = 1;
+ break;
+ default:
+ usage();
+ }
+ argc -= optind;
+ argv += optind;
+
+ if (argc < 2)
+ usage();
+
+ stdin_ok = isatty(STDIN_FILENO);
+
+ (void)signal(SIGINFO, progress);
+
+ /*
+ * If the stat on the target fails or the target isn't a directory,
+ * try the move. More than 2 arguments is an error in this case.
+ */
+ if (stat(argv[argc - 1], &sb) || !S_ISDIR(sb.st_mode)) {
+ if (argc > 2)
+ usage();
+ exit(do_move(argv[0], argv[1]));
+ }
+
+ /* It's a directory, move each file into it. */
+ baselen = strlcpy(path, argv[argc - 1], sizeof(path));
+ if (baselen >= sizeof(path))
+ errx(1, "%s: destination pathname too long", argv[argc - 1]);
+ endp = &path[baselen];
+ if (!baselen || *(endp - 1) != '/') {
+ *endp++ = '/';
+ ++baselen;
+ }
+ for (rval = 0; --argc; ++argv) {
+ p = *argv + strlen(*argv) - 1;
+ while (*p == '/' && p != *argv)
+ *p-- = '\0';
+ if ((p = strrchr(*argv, '/')) == NULL)
+ p = *argv;
+ else
+ ++p;
+
+ if ((baselen + (len = strlen(p))) >= MAXPATHLEN) {
+ warnx("%s: destination pathname too long", *argv);
+ rval = 1;
+ } else {
+ memmove(endp, p, len + 1);
+ if (do_move(*argv, path))
+ rval = 1;
+ }
+ }
+ exit(rval);
+ /* NOTREACHED */
+}
+
+static int
+do_move(char *from, char *to)
+{
+ struct stat sb;
+ char modep[15];
+
+ /*
+ * (1) If the destination path exists, the -f option is not specified
+ * and either of the following conditions are true:
+ *
+ * (a) The permissions of the destination path do not permit
+ * writing and the standard input is a terminal.
+ * (b) The -i option is specified.
+ *
+ * the mv utility shall write a prompt to standard error and
+ * read a line from standard input. If the response is not
+ * affirmative, mv shall do nothing more with the current
+ * source file...
+ */
+ if (!fflg && !access(to, F_OK)) {
+ int ask = 1;
+ int ch;
+
+ if (iflg) {
+ if (access(from, F_OK)) {
+ warn("rename %s", from);
+ return (1);
+ }
+ (void)fprintf(stderr, "overwrite %s? ", to);
+ } else if (stdin_ok && access(to, W_OK) && !stat(to, &sb)) {
+ if (access(from, F_OK)) {
+ warn("rename %s", from);
+ return (1);
+ }
+ strmode(sb.st_mode, modep);
+ (void)fprintf(stderr, "override %s%s%s/%s for %s? ",
+ modep + 1, modep[9] == ' ' ? "" : " ",
+ user_from_uid(sb.st_uid, 0),
+ group_from_gid(sb.st_gid, 0), to);
+ } else
+ ask = 0;
+ if (ask) {
+ if ((ch = getchar()) != EOF && ch != '\n') {
+ int ch2;
+ while ((ch2 = getchar()) != EOF && ch2 != '\n')
+ continue;
+ }
+ if (ch != 'y' && ch != 'Y')
+ return (0);
+ }
+ }
+
+ /*
+ * (2) If rename() succeeds, mv shall do nothing more with the
+ * current source file. If it fails for any other reason than
+ * EXDEV, mv shall write a diagnostic message to the standard
+ * error and do nothing more with the current source file.
+ *
+ * (3) If the destination path exists, and it is a file of type
+ * directory and source_file is not a file of type directory,
+ * or it is a file not of type directory, and source file is
+ * a file of type directory, mv shall write a diagnostic
+ * message to standard error, and do nothing more with the
+ * current source file...
+ */
+ if (!rename(from, to)) {
+ if (vflg)
+ printf("%s -> %s\n", from, to);
+ return (0);
+ }
+
+ if (errno != EXDEV) {
+ warn("rename %s to %s", from, to);
+ return (1);
+ }
+
+ /*
+ * (4) If the destination path exists, mv shall attempt to remove it.
+ * If this fails for any reason, mv shall write a diagnostic
+ * message to the standard error and do nothing more with the
+ * current source file...
+ */
+ if (!lstat(to, &sb)) {
+ if ((S_ISDIR(sb.st_mode)) ? rmdir(to) : unlink(to)) {
+ warn("can't remove %s", to);
+ return (1);
+ }
+ }
+
+ /*
+ * (5) The file hierarchy rooted in source_file shall be duplicated
+ * as a file hierarchy rooted in the destination path...
+ */
+ if (lstat(from, &sb)) {
+ warn("%s", from);
+ return (1);
+ }
+
+ return (S_ISREG(sb.st_mode) ?
+ fastcopy(from, to, &sb) : copy(from, to));
+}
+
+static int
+fastcopy(char *from, char *to, struct stat *sbp)
+{
+#if defined(__NetBSD__)
+ struct timespec ts[2];
+#else
+ struct timeval tval[2];
+#endif
+ static blksize_t blen;
+ static char *bp;
+ int from_fd, to_fd;
+ ssize_t nread;
+ off_t total = 0;
+
+ if ((from_fd = open(from, O_RDONLY, 0)) < 0) {
+ warn("%s", from);
+ return (1);
+ }
+ if ((to_fd =
+ open(to, O_CREAT | O_TRUNC | O_WRONLY, sbp->st_mode)) < 0) {
+ warn("%s", to);
+ (void)close(from_fd);
+ return (1);
+ }
+ if (!blen && !(bp = malloc(blen = sbp->st_blksize))) {
+ warn(NULL);
+ blen = 0;
+ (void)close(from_fd);
+ (void)close(to_fd);
+ return (1);
+ }
+ while ((nread = read(from_fd, bp, blen)) > 0) {
+ if (write(to_fd, bp, nread) != nread) {
+ warn("%s", to);
+ goto err;
+ }
+ total += nread;
+ if (pinfo) {
+ int pcent = (int)((100.0 * total) / sbp->st_size);
+
+ pinfo = 0;
+ (void)fprintf(stderr, "%s => %s %llu/%llu bytes %d%% "
+ "written\n", from, to, (unsigned long long)total,
+ (unsigned long long)sbp->st_size, pcent);
+ }
+ }
+ if (nread < 0) {
+ warn("%s", from);
+err: if (unlink(to))
+ warn("%s: remove", to);
+ (void)close(from_fd);
+ (void)close(to_fd);
+ return (1);
+ }
+
+ if (fcpxattr(from_fd, to_fd) == -1)
+ warn("%s: error copying extended attributes", to);
+
+ (void)close(from_fd);
+#ifdef BSD4_4
+#if defined(__NetBSD__)
+ ts[0] = sbp->st_atimespec;
+ ts[1] = sbp->st_mtimespec;
+#else
+ TIMESPEC_TO_TIMEVAL(&tval[0], &sbp->st_atimespec);
+ TIMESPEC_TO_TIMEVAL(&tval[1], &sbp->st_mtimespec);
+#endif
+#else
+ tval[0].tv_sec = sbp->st_atime;
+ tval[1].tv_sec = sbp->st_mtime;
+ tval[0].tv_usec = 0;
+ tval[1].tv_usec = 0;
+#endif
+#ifdef __SVR4
+ if (utimes(to, tval))
+#else
+#if defined(__NetBSD__)
+ if (futimens(to_fd, ts))
+#else
+ if (futimes(to_fd, tval))
+#endif
+#endif
+ warn("%s: set times", to);
+ if (fchown(to_fd, sbp->st_uid, sbp->st_gid)) {
+ if (errno != EPERM)
+ warn("%s: set owner/group", to);
+ sbp->st_mode &= ~(S_ISUID | S_ISGID);
+ }
+ if (fchmod(to_fd, sbp->st_mode))
+ warn("%s: set mode", to);
+ if (fchflags(to_fd, sbp->st_flags) && (errno != EOPNOTSUPP))
+ warn("%s: set flags (was: 0%07o)", to, sbp->st_flags);
+
+ if (close(to_fd)) {
+ warn("%s", to);
+ return (1);
+ }
+
+ if (unlink(from)) {
+ warn("%s: remove", from);
+ return (1);
+ }
+
+ if (vflg)
+ printf("%s -> %s\n", from, to);
+
+ return (0);
+}
+
+static int
+copy(char *from, char *to)
+{
+ pid_t pid;
+ int status;
+
+ if ((pid = vfork()) == 0) {
+ execl(_PATH_CP, "mv", vflg ? "-PRpv" : "-PRp", "--", from, to, NULL);
+ warn("%s", _PATH_CP);
+ _exit(1);
+ }
+ if (waitpid(pid, &status, 0) == -1) {
+ warn("%s: waitpid", _PATH_CP);
+ return (1);
+ }
+ if (!WIFEXITED(status)) {
+ warnx("%s: did not terminate normally", _PATH_CP);
+ return (1);
+ }
+ if (WEXITSTATUS(status)) {
+ warnx("%s: terminated with %d (non-zero) status",
+ _PATH_CP, WEXITSTATUS(status));
+ return (1);
+ }
+ if (!(pid = vfork())) {
+ execl(_PATH_RM, "mv", "-rf", "--", from, NULL);
+ warn("%s", _PATH_RM);
+ _exit(1);
+ }
+ if (waitpid(pid, &status, 0) == -1) {
+ warn("%s: waitpid", _PATH_RM);
+ return (1);
+ }
+ if (!WIFEXITED(status)) {
+ warnx("%s: did not terminate normally", _PATH_RM);
+ return (1);
+ }
+ if (WEXITSTATUS(status)) {
+ warnx("%s: terminated with %d (non-zero) status",
+ _PATH_RM, WEXITSTATUS(status));
+ return (1);
+ }
+ return (0);
+}
+
+static void
+usage(void)
+{
+ (void)fprintf(stderr, "usage: %s [-fiv] source target\n"
+ " %s [-fiv] source ... directory\n", getprogname(),
+ getprogname());
+ exit(1);
+ /* NOTREACHED */
+}
diff --git a/bin/mv/pathnames.h b/bin/mv/pathnames.h
new file mode 100644
index 0000000..a38e4e9
--- /dev/null
+++ b/bin/mv/pathnames.h
@@ -0,0 +1,40 @@
+/* $NetBSD: pathnames.h,v 1.8 2004/08/19 22:26:07 christos Exp $ */
+
+/*
+ * Copyright (c) 1989, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * @(#)pathnames.h 8.1 (Berkeley) 5/31/93
+ */
+
+#ifdef RESCUEDIR
+#define _PATH_RM RESCUEDIR "/rm"
+#define _PATH_CP RESCUEDIR "/cp"
+#else
+#define _PATH_RM "/bin/rm"
+#define _PATH_CP "/bin/cp"
+#endif
diff --git a/bin/pax/ar_io.c b/bin/pax/ar_io.c
new file mode 100644
index 0000000..d839fa0
--- /dev/null
+++ b/bin/pax/ar_io.c
@@ -0,0 +1,1725 @@
+/* $NetBSD: ar_io.c,v 1.58 2017/10/02 21:57:59 joerg Exp $ */
+
+/*-
+ * Copyright (c) 1992 Keith Muller.
+ * Copyright (c) 1992, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Keith Muller of the University of California, San Diego.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#if HAVE_NBTOOL_CONFIG_H
+#include "nbtool_config.h"
+#endif
+
+#include <sys/cdefs.h>
+#if !defined(lint)
+#if 0
+static char sccsid[] = "@(#)ar_io.c 8.2 (Berkeley) 4/18/94";
+#else
+__RCSID("$NetBSD: ar_io.c,v 1.58 2017/10/02 21:57:59 joerg Exp $");
+#endif
+#endif /* not lint */
+
+#include <sys/param.h>
+#include <sys/ioctl.h>
+#ifdef HAVE_SYS_MTIO_H
+#include <sys/mtio.h>
+#endif
+#include <sys/stat.h>
+#include <sys/time.h>
+#include <sys/wait.h>
+#include <ctype.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <unistd.h>
+#ifdef SUPPORT_RMT
+#define __RMTLIB_PRIVATE
+#include <rmt.h>
+#endif /* SUPPORT_RMT */
+#include "pax.h"
+#include "options.h"
+#include "extern.h"
+
+/*
+ * Routines which deal directly with the archive I/O device/file.
+ */
+
+#define DMOD 0666 /* default mode of created archives */
+#define EXT_MODE O_RDONLY /* open mode for list/extract */
+#define AR_MODE (O_WRONLY | O_CREAT | O_TRUNC) /* mode for archive */
+#define APP_MODE O_RDWR /* mode for append */
+static char STDO[] = "<STDOUT>"; /* pseudo name for stdout */
+static char STDN[] = "<STDIN>"; /* pseudo name for stdin */
+static char NONE[] = "<NONE>"; /* pseudo name for none */
+static int arfd = -1; /* archive file descriptor */
+static int artyp = ISREG; /* archive type: file/FIFO/tape */
+static int arvol = 1; /* archive volume number */
+static int lstrval = -1; /* return value from last i/o */
+static int io_ok; /* i/o worked on volume after resync */
+static int did_io; /* did i/o ever occur on volume? */
+static int done; /* set via tty termination */
+static struct stat arsb; /* stat of archive device at open */
+static int invld_rec; /* tape has out of spec record size */
+static int wr_trail = 1; /* trailer was rewritten in append */
+static int can_unlnk = 0; /* do we unlink null archives? */
+const char *arcname; /* printable name of archive */
+const char *gzip_program; /* name of gzip program */
+static pid_t zpid = -1; /* pid of child process */
+time_t starttime; /* time the run started */
+int force_one_volume; /* 1 if we ignore volume changes */
+
+#ifdef HAVE_SYS_MTIO_H
+static int get_phys(void);
+#endif
+extern sigset_t s_mask;
+static void ar_start_gzip(int, const char *, int);
+static const char *timefmt(char *, size_t, off_t, time_t, const char *);
+static const char *sizefmt(char *, size_t, off_t);
+
+#ifdef SUPPORT_RMT
+#ifdef SYS_NO_RESTART
+static int rmtread_with_restart(int, void *, int);
+static int rmtwrite_with_restart(int, void *, int);
+#else
+#define rmtread_with_restart(a, b, c) rmtread((a), (b), (c))
+#define rmtwrite_with_restart(a, b, c) rmtwrite((a), (b), (c))
+#endif
+#endif /* SUPPORT_RMT */
+
+/*
+ * ar_open()
+ * Opens the next archive volume. Determines the type of the device and
+ * sets up block sizes as required by the archive device and the format.
+ * Note: we may be called with name == NULL on the first open only.
+ * Return:
+ * -1 on failure, 0 otherwise
+ */
+
+int
+ar_open(const char *name)
+{
+#ifdef HAVE_SYS_MTIO_H
+ struct mtget mb;
+#endif
+
+ if (arfd != -1)
+ (void)close(arfd);
+ arfd = -1;
+ can_unlnk = did_io = io_ok = invld_rec = 0;
+ artyp = ISREG;
+ flcnt = 0;
+
+#ifdef SUPPORT_RMT
+ if (name && strchr(name, ':') != NULL && !forcelocal) {
+ artyp = ISRMT;
+ if ((arfd = rmtopen(name, O_RDWR, DMOD)) == -1) {
+ syswarn(0, errno, "Failed open on %s", name);
+ return -1;
+ }
+ if (!isrmt(arfd)) {
+ rmtclose(arfd);
+ tty_warn(0, "Not a remote file: %s", name);
+ return -1;
+ }
+ blksz = rdblksz = 8192;
+ lstrval = 1;
+ return 0;
+ }
+#endif /* SUPPORT_RMT */
+
+ /*
+ * open based on overall operation mode
+ */
+ switch (act) {
+ case LIST:
+ case EXTRACT:
+ if (name == NULL) {
+ arfd = STDIN_FILENO;
+ arcname = STDN;
+ } else if ((arfd = open(name, EXT_MODE, DMOD)) < 0)
+ syswarn(0, errno, "Failed open to read on %s", name);
+ if (arfd != -1 && gzip_program != NULL)
+ ar_start_gzip(arfd, gzip_program, 0);
+ break;
+ case ARCHIVE:
+ if (name == NULL) {
+ arfd = STDOUT_FILENO;
+ arcname = STDO;
+ } else if ((arfd = open(name, AR_MODE, DMOD)) < 0)
+ syswarn(0, errno, "Failed open to write on %s", name);
+ else
+ can_unlnk = 1;
+ if (arfd != -1 && gzip_program != NULL)
+ ar_start_gzip(arfd, gzip_program, 1);
+ break;
+ case APPND:
+ if (name == NULL) {
+ arfd = STDOUT_FILENO;
+ arcname = STDO;
+ } else if ((arfd = open(name, APP_MODE, DMOD)) < 0)
+ syswarn(0, errno, "Failed open to read/write on %s",
+ name);
+ break;
+ case COPY:
+ /*
+ * arfd not used in COPY mode
+ */
+ arcname = NONE;
+ lstrval = 1;
+ return 0;
+ }
+ if (arfd < 0)
+ return -1;
+
+ if (chdname != NULL)
+ if (dochdir(chdname) == -1)
+ return -1;
+ /*
+ * set up is based on device type
+ */
+ if (fstat(arfd, &arsb) < 0) {
+ syswarn(0, errno, "Failed stat on %s", arcname);
+ (void)close(arfd);
+ arfd = -1;
+ can_unlnk = 0;
+ return -1;
+ }
+ if (S_ISDIR(arsb.st_mode)) {
+ tty_warn(0, "Cannot write an archive on top of a directory %s",
+ arcname);
+ (void)close(arfd);
+ arfd = -1;
+ can_unlnk = 0;
+ return -1;
+ }
+
+ if (S_ISCHR(arsb.st_mode)) {
+#ifdef HAVE_SYS_MTIO_H
+ artyp = ioctl(arfd, MTIOCGET, &mb) ? ISCHR : ISTAPE;
+#else
+ tty_warn(1, "System does not have tape support");
+ artyp = ISREG;
+#endif
+ } else if (S_ISBLK(arsb.st_mode))
+ artyp = ISBLK;
+ else if ((lseek(arfd, (off_t)0L, SEEK_CUR) == -1) && (errno == ESPIPE))
+ artyp = ISPIPE;
+ else
+ artyp = ISREG;
+
+ /*
+ * Special handling for empty files.
+ */
+ if (artyp == ISREG && arsb.st_size == 0) {
+ switch (act) {
+ case LIST:
+ case EXTRACT:
+ return -1;
+ case APPND:
+ act = -ARCHIVE;
+ return -1;
+ case ARCHIVE:
+ break;
+ }
+ }
+
+ /*
+ * make sure beyond any doubt that we can unlink only regular files
+ * we created
+ */
+ if (artyp != ISREG)
+ can_unlnk = 0;
+
+ /*
+ * if we are writing, we are done
+ */
+ if (act == ARCHIVE) {
+ blksz = rdblksz = wrblksz;
+ lstrval = 1;
+ return 0;
+ }
+
+ /*
+ * set default blksz on read. APPNDs writes rdblksz on the last volume
+ * On all new archive volumes, we shift to wrblksz (if the user
+ * specified one, otherwize we will continue to use rdblksz). We
+ * must set blocksize based on what kind of device the archive is
+ * stored.
+ */
+ switch(artyp) {
+ case ISTAPE:
+ /*
+ * Tape drives come in at least two flavors. Those that support
+ * variable sized records and those that have fixed sized
+ * records. They must be treated differently. For tape drives
+ * that support variable sized records, we must make large
+ * reads to make sure we get the entire record, otherwise we
+ * will just get the first part of the record (up to size we
+ * asked). Tapes with fixed sized records may or may not return
+ * multiple records in a single read. We really do not care
+ * what the physical record size is UNLESS we are going to
+ * append. (We will need the physical block size to rewrite
+ * the trailer). Only when we are appending do we go to the
+ * effort to figure out the true PHYSICAL record size.
+ */
+ blksz = rdblksz = MAXBLK;
+ break;
+ case ISPIPE:
+ case ISBLK:
+ case ISCHR:
+ /*
+ * Blocksize is not a major issue with these devices (but must
+ * be kept a multiple of 512). If the user specified a write
+ * block size, we use that to read. Under append, we must
+ * always keep blksz == rdblksz. Otherwise we go ahead and use
+ * the device optimal blocksize as (and if) returned by stat
+ * and if it is within pax specs.
+ */
+ if ((act == APPND) && wrblksz) {
+ blksz = rdblksz = wrblksz;
+ break;
+ }
+
+ if ((arsb.st_blksize > 0) && (arsb.st_blksize < MAXBLK) &&
+ ((arsb.st_blksize % BLKMULT) == 0))
+ rdblksz = arsb.st_blksize;
+ else
+ rdblksz = DEVBLK;
+ /*
+ * For performance go for large reads when we can without harm
+ */
+ if ((act == APPND) || (artyp == ISCHR))
+ blksz = rdblksz;
+ else
+ blksz = MAXBLK;
+ break;
+ case ISREG:
+ /*
+ * if the user specified wrblksz works, use it. Under appends
+ * we must always keep blksz == rdblksz
+ */
+ if ((act == APPND) && wrblksz && ((arsb.st_size%wrblksz)==0)){
+ blksz = rdblksz = wrblksz;
+ break;
+ }
+ /*
+ * See if we can find the blocking factor from the file size
+ */
+ for (rdblksz = MAXBLK; rdblksz > 0; rdblksz -= BLKMULT)
+ if ((arsb.st_size % rdblksz) == 0)
+ break;
+ /*
+ * When we cannot find a match, we may have a flawed archive.
+ */
+ if (rdblksz <= 0)
+ rdblksz = FILEBLK;
+ /*
+ * for performance go for large reads when we can
+ */
+ if (act == APPND)
+ blksz = rdblksz;
+ else
+ blksz = MAXBLK;
+ break;
+ default:
+ /*
+ * should never happen, worst case, slow...
+ */
+ blksz = rdblksz = BLKMULT;
+ break;
+ }
+ lstrval = 1;
+ return 0;
+}
+
+/*
+ * ar_close()
+ * closes archive device, increments volume number, and prints i/o summary
+ */
+void
+ar_close(void)
+{
+ int status;
+
+ if (arfd < 0) {
+ did_io = io_ok = flcnt = 0;
+ return;
+ }
+
+
+ /*
+ * Close archive file. This may take a LONG while on tapes (we may be
+ * forced to wait for the rewind to complete) so tell the user what is
+ * going on (this avoids the user hitting control-c thinking pax is
+ * broken).
+ */
+ if ((vflag || Vflag) && (artyp == ISTAPE)) {
+ if (vfpart)
+ (void)putc('\n', listf);
+ (void)fprintf(listf,
+ "%s: Waiting for tape drive close to complete...",
+ argv0);
+ (void)fflush(listf);
+ }
+
+ /*
+ * if nothing was written to the archive (and we created it), we remove
+ * it
+ */
+ if (can_unlnk && (fstat(arfd, &arsb) == 0) && (S_ISREG(arsb.st_mode)) &&
+ (arsb.st_size == 0)) {
+ (void)unlink(arcname);
+ can_unlnk = 0;
+ }
+
+ /*
+ * for a quick extract/list, pax frequently exits before the child
+ * process is done
+ */
+ if ((act == LIST || act == EXTRACT) && nflag && zpid > 0)
+ kill(zpid, SIGINT);
+
+#ifdef SUPPORT_RMT
+ if (artyp == ISRMT)
+ (void)rmtclose(arfd);
+ else
+#endif /* SUPPORT_RMT */
+ (void)close(arfd);
+
+ /* Do not exit before child to ensure data integrity */
+ if (zpid > 0)
+ waitpid(zpid, &status, 0);
+
+ if ((vflag || Vflag) && (artyp == ISTAPE)) {
+ (void)fputs("done.\n", listf);
+ vfpart = 0;
+ (void)fflush(listf);
+ }
+ arfd = -1;
+
+ if (!io_ok && !did_io) {
+ flcnt = 0;
+ return;
+ }
+ did_io = io_ok = 0;
+
+ /*
+ * The volume number is only increased when the last device has data
+ * and we have already determined the archive format.
+ */
+ if (frmt != NULL)
+ ++arvol;
+
+ if (!vflag && !Vflag) {
+ flcnt = 0;
+ return;
+ }
+
+ /*
+ * Print out a summary of I/O for this archive volume.
+ */
+ if (vfpart) {
+ (void)putc('\n', listf);
+ vfpart = 0;
+ }
+
+ /* mimic cpio's block count first */
+ if (frmt && strcmp(NM_CPIO, argv0) == 0) {
+ (void)fprintf(listf, OFFT_F " blocks\n",
+ (rdcnt ? rdcnt : wrcnt) / 5120);
+ }
+
+ ar_summary(0);
+
+ (void)fflush(listf);
+ flcnt = 0;
+}
+
+/*
+ * ar_drain()
+ * drain any archive format independent padding from an archive read
+ * from a socket or a pipe. This is to prevent the process on the
+ * other side of the pipe from getting a SIGPIPE (pax will stop
+ * reading an archive once a format dependent trailer is detected).
+ */
+void
+ar_drain(void)
+{
+ int res;
+ char drbuf[MAXBLK];
+
+ /*
+ * we only drain from a pipe/socket. Other devices can be closed
+ * without reading up to end of file. We sure hope that pipe is closed
+ * on the other side so we will get an EOF.
+ */
+ if ((artyp != ISPIPE) || (lstrval <= 0))
+ return;
+
+ /*
+ * keep reading until pipe is drained
+ */
+#ifdef SUPPORT_RMT
+ if (artyp == ISRMT) {
+ while ((res = rmtread_with_restart(arfd,
+ drbuf, sizeof(drbuf))) > 0)
+ continue;
+ } else {
+#endif /* SUPPORT_RMT */
+ while ((res = read_with_restart(arfd,
+ drbuf, sizeof(drbuf))) > 0)
+ continue;
+#ifdef SUPPORT_RMT
+ }
+#endif /* SUPPORT_RMT */
+ lstrval = res;
+}
+
+/*
+ * ar_set_wr()
+ * Set up device right before switching from read to write in an append.
+ * device dependent code (if required) to do this should be added here.
+ * For all archive devices we are already positioned at the place we want
+ * to start writing when this routine is called.
+ * Return:
+ * 0 if all ready to write, -1 otherwise
+ */
+
+int
+ar_set_wr(void)
+{
+ off_t cpos;
+
+ /*
+ * we must make sure the trailer is rewritten on append, ar_next()
+ * will stop us if the archive containing the trailer was not written
+ */
+ wr_trail = 0;
+
+ /*
+ * Add any device dependent code as required here
+ */
+ if (artyp != ISREG)
+ return 0;
+ /*
+ * Ok we have an archive in a regular file. If we were rewriting a
+ * file, we must get rid of all the stuff after the current offset
+ * (it was not written by pax).
+ */
+ if (((cpos = lseek(arfd, (off_t)0L, SEEK_CUR)) < 0) ||
+ (ftruncate(arfd, cpos) < 0)) {
+ syswarn(1, errno, "Unable to truncate archive file");
+ return -1;
+ }
+ return 0;
+}
+
+/*
+ * ar_app_ok()
+ * check if the last volume in the archive allows appends. We cannot check
+ * this until we are ready to write since there is no spec that says all
+ * volumes in a single archive have to be of the same type...
+ * Return:
+ * 0 if we can append, -1 otherwise.
+ */
+
+int
+ar_app_ok(void)
+{
+ if (artyp == ISPIPE) {
+ tty_warn(1,
+ "Cannot append to an archive obtained from a pipe.");
+ return -1;
+ }
+
+ if (!invld_rec)
+ return 0;
+ tty_warn(1,
+ "Cannot append, device record size %d does not support %s spec",
+ rdblksz, argv0);
+ return -1;
+}
+
+#ifdef SYS_NO_RESTART
+/*
+ * read_with_restart()
+ * Equivalent to read() but does retry on signals.
+ * This function is not needed on 4.2BSD and later.
+ * Return:
+ * Number of bytes written. -1 indicates an error.
+ */
+
+int
+read_with_restart(int fd, void *buf, int bsz)
+{
+ int r;
+
+ while (((r = read(fd, buf, bsz)) < 0) && errno == EINTR)
+ continue;
+
+ return r;
+}
+
+/*
+ * rmtread_with_restart()
+ * Equivalent to rmtread() but does retry on signals.
+ * This function is not needed on 4.2BSD and later.
+ * Return:
+ * Number of bytes written. -1 indicates an error.
+ */
+static int
+rmtread_with_restart(int fd, void *buf, int bsz)
+{
+ int r;
+
+ while (((r = rmtread(fd, buf, bsz)) < 0) && errno == EINTR)
+ continue;
+
+ return r;
+}
+#endif
+
+/*
+ * xread()
+ * Equivalent to read() but does retry on partial read, which may occur
+ * on signals.
+ * Return:
+ * Number of bytes read. 0 for end of file, -1 for an error.
+ */
+
+int
+xread(int fd, void *buf, int bsz)
+{
+ char *b = buf;
+ int nread = 0;
+ int r;
+
+ do {
+#ifdef SUPPORT_RMT
+ if ((r = rmtread_with_restart(fd, b, bsz)) <= 0)
+ break;
+#else
+ if ((r = read_with_restart(fd, b, bsz)) <= 0)
+ break;
+#endif /* SUPPORT_RMT */
+ b += r;
+ bsz -= r;
+ nread += r;
+ } while (bsz > 0);
+
+ return nread ? nread : r;
+}
+
+#ifdef SYS_NO_RESTART
+/*
+ * write_with_restart()
+ * Equivalent to write() but does retry on signals.
+ * This function is not needed on 4.2BSD and later.
+ * Return:
+ * Number of bytes written. -1 indicates an error.
+ */
+
+int
+write_with_restart(int fd, void *buf, int bsz)
+{
+ int r;
+
+ while (((r = write(fd, buf, bsz)) < 0) && errno == EINTR)
+ ;
+
+ return r;
+}
+
+/*
+ * rmtwrite_with_restart()
+ * Equivalent to write() but does retry on signals.
+ * This function is not needed on 4.2BSD and later.
+ * Return:
+ * Number of bytes written. -1 indicates an error.
+ */
+
+static int
+rmtwrite_with_restart(int fd, void *buf, int bsz)
+{
+ int r;
+
+ while (((r = rmtwrite(fd, buf, bsz)) < 0) && errno == EINTR)
+ ;
+
+ return r;
+}
+#endif
+
+/*
+ * xwrite()
+ * Equivalent to write() but does retry on partial write, which may occur
+ * on signals.
+ * Return:
+ * Number of bytes written. -1 indicates an error.
+ */
+
+int
+xwrite(int fd, void *buf, int bsz)
+{
+ char *b = buf;
+ int written = 0;
+ int r;
+
+ do {
+#ifdef SUPPORT_RMT
+ if ((r = rmtwrite_with_restart(fd, b, bsz)) <= 0)
+ break;
+#else
+ if ((r = write_with_restart(fd, b, bsz)) <= 0)
+ break;
+#endif /* SUPPORT_RMT */
+ b += r;
+ bsz -= r;
+ written += r;
+ } while (bsz > 0);
+
+ return written ? written : r;
+}
+
+/*
+ * ar_read()
+ * read up to a specified number of bytes from the archive into the
+ * supplied buffer. When dealing with tapes we may not always be able to
+ * read what we want.
+ * Return:
+ * Number of bytes in buffer. 0 for end of file, -1 for a read error.
+ */
+
+int
+ar_read(char *buf, int cnt)
+{
+ int res = 0;
+
+ /*
+ * if last i/o was in error, no more reads until reset or new volume
+ */
+ if (lstrval <= 0)
+ return lstrval;
+
+ /*
+ * how we read must be based on device type
+ */
+ switch (artyp) {
+#ifdef SUPPORT_RMT
+ case ISRMT:
+ if ((res = rmtread_with_restart(arfd, buf, cnt)) > 0) {
+ io_ok = 1;
+ return res;
+ }
+ break;
+#endif /* SUPPORT_RMT */
+ case ISTAPE:
+ if ((res = read_with_restart(arfd, buf, cnt)) > 0) {
+ /*
+ * CAUTION: tape systems may not always return the same
+ * sized records so we leave blksz == MAXBLK. The
+ * physical record size that a tape drive supports is
+ * very hard to determine in a uniform and portable
+ * manner.
+ */
+ io_ok = 1;
+ if (res != rdblksz) {
+ /*
+ * Record size changed. If this happens on
+ * any record after the first, we probably have
+ * a tape drive which has a fixed record size
+ * (we are getting multiple records in a single
+ * read). Watch out for record blocking that
+ * violates pax spec (must be a multiple of
+ * BLKMULT).
+ */
+ rdblksz = res;
+ if (rdblksz % BLKMULT)
+ invld_rec = 1;
+ }
+ return res;
+ }
+ break;
+ case ISREG:
+ case ISBLK:
+ case ISCHR:
+ case ISPIPE:
+ default:
+ /*
+ * Files are so easy to deal with. These other things cannot
+ * be trusted at all. So when we are dealing with character
+ * devices and pipes we just take what they have ready for us
+ * and return. Trying to do anything else with them runs the
+ * risk of failure.
+ */
+ if ((res = read_with_restart(arfd, buf, cnt)) > 0) {
+ io_ok = 1;
+ return res;
+ }
+ break;
+ }
+
+ /*
+ * We are in trouble at this point, something is broken...
+ */
+ lstrval = res;
+ if (res < 0)
+ syswarn(1, errno, "Failed read on archive volume %d", arvol);
+ else
+ tty_warn(0, "End of archive volume %d reached", arvol);
+ return res;
+}
+
+/*
+ * ar_write()
+ * Write a specified number of bytes in supplied buffer to the archive
+ * device so it appears as a single "block". Deals with errors and tries
+ * to recover when faced with short writes.
+ * Return:
+ * Number of bytes written. 0 indicates end of volume reached and with no
+ * flaws (as best that can be detected). A -1 indicates an unrecoverable
+ * error in the archive occurred.
+ */
+
+int
+ar_write(char *buf, int bsz)
+{
+ int res;
+ off_t cpos;
+
+ /*
+ * do not allow pax to create a "bad" archive. Once a write fails on
+ * an archive volume prevent further writes to it.
+ */
+ if (lstrval <= 0)
+ return lstrval;
+
+ if ((res = xwrite(arfd, buf, bsz)) == bsz) {
+ wr_trail = 1;
+ io_ok = 1;
+ return bsz;
+ }
+ /*
+ * write broke, see what we can do with it. We try to send any partial
+ * writes that may violate pax spec to the next archive volume.
+ */
+ if (res < 0)
+ lstrval = res;
+ else
+ lstrval = 0;
+
+ switch (artyp) {
+ case ISREG:
+ if ((res > 0) && (res % BLKMULT)) {
+ /*
+ * try to fix up partial writes which are not BLKMULT
+ * in size by forcing the runt record to next archive
+ * volume
+ */
+ if ((cpos = lseek(arfd, (off_t)0L, SEEK_CUR)) < 0)
+ break;
+ cpos -= (off_t)res;
+ if (ftruncate(arfd, cpos) < 0)
+ break;
+ res = lstrval = 0;
+ break;
+ }
+ if (res >= 0)
+ break;
+ /*
+ * if file is out of space, handle it like a return of 0
+ */
+ if ((errno == ENOSPC) || (errno == EFBIG))
+ res = lstrval = 0;
+#ifdef EDQUOT
+ if (errno == EDQUOT)
+ res = lstrval = 0;
+#endif
+ break;
+ case ISTAPE:
+ case ISCHR:
+ case ISBLK:
+#ifdef SUPPORT_RMT
+ case ISRMT:
+#endif /* SUPPORT_RMT */
+ if (res >= 0)
+ break;
+ if (errno == EACCES) {
+ tty_warn(0,
+ "Write failed, archive is write protected.");
+ res = lstrval = 0;
+ return 0;
+ }
+ /*
+ * see if we reached the end of media, if so force a change to
+ * the next volume
+ */
+ if ((errno == ENOSPC) || (errno == EIO) || (errno == ENXIO))
+ res = lstrval = 0;
+ break;
+ case ISPIPE:
+ default:
+ /*
+ * we cannot fix errors to these devices
+ */
+ break;
+ }
+
+ /*
+ * Better tell the user the bad news...
+ * if this is a block aligned archive format, we may have a bad archive
+ * if the format wants the header to start at a BLKMULT boundary. While
+ * we can deal with the mis-aligned data, it violates spec and other
+ * archive readers will likely fail. if the format is not block
+ * aligned, the user may be lucky (and the archive is ok).
+ */
+ if (res >= 0) {
+ if (res > 0)
+ wr_trail = 1;
+ io_ok = 1;
+ }
+
+ /*
+ * If we were trying to rewrite the trailer and it didn't work, we
+ * must quit right away.
+ */
+ if (!wr_trail && (res <= 0)) {
+ tty_warn(1,
+ "Unable to append, trailer re-write failed. Quitting.");
+ return res;
+ }
+
+ if (res == 0)
+ tty_warn(0, "End of archive volume %d reached", arvol);
+ else if (res < 0)
+ syswarn(1, errno, "Failed write to archive volume: %d", arvol);
+ else if (!frmt->blkalgn || ((res % frmt->blkalgn) == 0))
+ tty_warn(0,
+ "WARNING: partial archive write. Archive MAY BE FLAWED");
+ else
+ tty_warn(1,"WARNING: partial archive write. Archive IS FLAWED");
+ return res;
+}
+
+/*
+ * ar_rdsync()
+ * Try to move past a bad spot on a flawed archive as needed to continue
+ * I/O. Clears error flags to allow I/O to continue.
+ * Return:
+ * 0 when ok to try i/o again, -1 otherwise.
+ */
+
+int
+ar_rdsync(void)
+{
+ long fsbz;
+ off_t cpos;
+ off_t mpos;
+#ifdef HAVE_SYS_MTIO_H
+ struct mtop mb;
+#endif
+
+ /*
+ * Fail resync attempts at user request (done) or if this is going to be
+ * an update/append to a existing archive. if last i/o hit media end,
+ * we need to go to the next volume not try a resync
+ */
+ if ((done > 0) || (lstrval == 0))
+ return -1;
+
+ if ((act == APPND) || (act == ARCHIVE)) {
+ tty_warn(1, "Cannot allow updates to an archive with flaws.");
+ return -1;
+ }
+ if (io_ok)
+ did_io = 1;
+
+ switch(artyp) {
+#ifdef SUPPORT_RMT
+ case ISRMT:
+#endif /* SUPPORT_RMT */
+ case ISTAPE:
+#ifdef HAVE_SYS_MTIO_H
+ /*
+ * if the last i/o was a successful data transfer, we assume
+ * the fault is just a bad record on the tape that we are now
+ * past. If we did not get any data since the last resync try
+ * to move the tape forward one PHYSICAL record past any
+ * damaged tape section. Some tape drives are stubborn and need
+ * to be pushed.
+ */
+ if (io_ok) {
+ io_ok = 0;
+ lstrval = 1;
+ break;
+ }
+ mb.mt_op = MTFSR;
+ mb.mt_count = 1;
+#ifdef SUPPORT_RMT
+ if (artyp == ISRMT) {
+ if (rmtioctl(arfd, MTIOCTOP, &mb) < 0)
+ break;
+ } else {
+#endif /* SUPPORT_RMT */
+ if (ioctl(arfd, MTIOCTOP, &mb) < 0)
+ break;
+#ifdef SUPPORT_RMT
+ }
+#endif /* SUPPORT_RMT */
+ lstrval = 1;
+#else
+ tty_warn(1, "System does not have tape support");
+#endif
+ break;
+ case ISREG:
+ case ISCHR:
+ case ISBLK:
+ /*
+ * try to step over the bad part of the device.
+ */
+ io_ok = 0;
+ if (((fsbz = arsb.st_blksize) <= 0) || (artyp != ISREG))
+ fsbz = BLKMULT;
+ if ((cpos = lseek(arfd, (off_t)0L, SEEK_CUR)) < 0)
+ break;
+ mpos = fsbz - (cpos % (off_t)fsbz);
+ if (lseek(arfd, mpos, SEEK_CUR) < 0)
+ break;
+ lstrval = 1;
+ break;
+ case ISPIPE:
+ default:
+ /*
+ * cannot recover on these archive device types
+ */
+ io_ok = 0;
+ break;
+ }
+ if (lstrval <= 0) {
+ tty_warn(1, "Unable to recover from an archive read failure.");
+ return -1;
+ }
+ tty_warn(0, "Attempting to recover from an archive read failure.");
+ return 0;
+}
+
+/*
+ * ar_fow()
+ * Move the I/O position within the archive forward the specified number of
+ * bytes as supported by the device. If we cannot move the requested
+ * number of bytes, return the actual number of bytes moved in skipped.
+ * Return:
+ * 0 if moved the requested distance, -1 on complete failure, 1 on
+ * partial move (the amount moved is in skipped)
+ */
+
+int
+ar_fow(off_t sksz, off_t *skipped)
+{
+ off_t cpos;
+ off_t mpos;
+
+ *skipped = 0;
+ if (sksz <= 0)
+ return 0;
+
+ /*
+ * we cannot move forward at EOF or error
+ */
+ if (lstrval <= 0)
+ return lstrval;
+
+ /*
+ * Safer to read forward on devices where it is hard to find the end of
+ * the media without reading to it. With tapes we cannot be sure of the
+ * number of physical blocks to skip (we do not know physical block
+ * size at this point), so we must only read forward on tapes!
+ */
+ if (artyp == ISTAPE || artyp == ISPIPE
+#ifdef SUPPORT_RMT
+ || artyp == ISRMT
+#endif /* SUPPORT_RMT */
+ )
+ return 0;
+
+ /*
+ * figure out where we are in the archive
+ */
+ if ((cpos = lseek(arfd, (off_t)0L, SEEK_CUR)) >= 0) {
+ /*
+ * we can be asked to move farther than there are bytes in this
+ * volume, if so, just go to file end and let normal buf_fill()
+ * deal with the end of file (it will go to next volume by
+ * itself)
+ */
+ mpos = cpos + sksz;
+ if (artyp == ISREG && mpos > arsb.st_size)
+ mpos = arsb.st_size;
+ if ((mpos = lseek(arfd, mpos, SEEK_SET)) >= 0) {
+ *skipped = mpos - cpos;
+ return 0;
+ }
+ } else {
+ if (artyp != ISREG)
+ return 0; /* non-seekable device */
+ }
+ syswarn(1, errno, "Forward positioning operation on archive failed");
+ lstrval = -1;
+ return -1;
+}
+
+/*
+ * ar_rev()
+ * move the i/o position within the archive backwards the specified byte
+ * count as supported by the device. With tapes drives we RESET rdblksz to
+ * the PHYSICAL blocksize.
+ * NOTE: We should only be called to move backwards so we can rewrite the
+ * last records (the trailer) of an archive (APPEND).
+ * Return:
+ * 0 if moved the requested distance, -1 on complete failure
+ */
+
+int
+ar_rev(off_t sksz)
+{
+ off_t cpos;
+#ifdef HAVE_SYS_MTIO_H
+ int phyblk;
+ struct mtop mb;
+#endif
+
+ /*
+ * make sure we do not have try to reverse on a flawed archive
+ */
+ if (lstrval < 0)
+ return lstrval;
+
+ switch(artyp) {
+ case ISPIPE:
+ if (sksz <= 0)
+ break;
+ /*
+ * cannot go backwards on these critters
+ */
+ tty_warn(1, "Reverse positioning on pipes is not supported.");
+ lstrval = -1;
+ return -1;
+ case ISREG:
+ case ISBLK:
+ case ISCHR:
+ default:
+ if (sksz <= 0)
+ break;
+
+ /*
+ * For things other than files, backwards movement has a very
+ * high probability of failure as we really do not know the
+ * true attributes of the device we are talking to (the device
+ * may not even have the ability to lseek() in any direction).
+ * First we figure out where we are in the archive.
+ */
+ if ((cpos = lseek(arfd, (off_t)0L, SEEK_CUR)) < 0) {
+ syswarn(1, errno,
+ "Unable to obtain current archive byte offset");
+ lstrval = -1;
+ return -1;
+ }
+
+ /*
+ * we may try to go backwards past the start when the archive
+ * is only a single record. If this happens and we are on a
+ * multi-volume archive, we need to go to the end of the
+ * previous volume and continue our movement backwards from
+ * there.
+ */
+ if ((cpos -= sksz) < (off_t)0L) {
+ if (arvol > 1) {
+ /*
+ * this should never happen
+ */
+ tty_warn(1,
+ "Reverse position on previous volume.");
+ lstrval = -1;
+ return -1;
+ }
+ cpos = (off_t)0L;
+ }
+ if (lseek(arfd, cpos, SEEK_SET) < 0) {
+ syswarn(1, errno, "Unable to seek archive backwards");
+ lstrval = -1;
+ return -1;
+ }
+ break;
+ case ISTAPE:
+#ifdef SUPPORT_RMT
+ case ISRMT:
+#endif /* SUPPORT_RMT */
+#ifdef HAVE_SYS_MTIO_H
+ /*
+ * Calculate and move the proper number of PHYSICAL tape
+ * blocks. If the sksz is not an even multiple of the physical
+ * tape size, we cannot do the move (this should never happen).
+ * (We also cannot handle trailers spread over two vols).
+ * get_phys() also makes sure we are in front of the filemark.
+ */
+ if ((phyblk = get_phys()) <= 0) {
+ lstrval = -1;
+ return -1;
+ }
+
+ /*
+ * make sure future tape reads only go by physical tape block
+ * size (set rdblksz to the real size).
+ */
+ rdblksz = phyblk;
+
+ /*
+ * if no movement is required, just return (we must be after
+ * get_phys() so the physical blocksize is properly set)
+ */
+ if (sksz <= 0)
+ break;
+
+ /*
+ * ok we have to move. Make sure the tape drive can do it.
+ */
+ if (sksz % phyblk) {
+ tty_warn(1,
+ "Tape drive unable to backspace requested amount");
+ lstrval = -1;
+ return -1;
+ }
+
+ /*
+ * move backwards the requested number of bytes
+ */
+ mb.mt_op = MTBSR;
+ mb.mt_count = sksz/phyblk;
+ if (
+#ifdef SUPPORT_RMT
+ rmtioctl(arfd, MTIOCTOP, &mb)
+#else
+ ioctl(arfd, MTIOCTOP, &mb)
+#endif /* SUPPORT_RMT */
+ < 0) {
+ syswarn(1, errno, "Unable to backspace tape %ld blocks.",
+ (long) mb.mt_count);
+ lstrval = -1;
+ return -1;
+ }
+#else
+ tty_warn(1, "System does not have tape support");
+#endif
+ break;
+ }
+ lstrval = 1;
+ return 0;
+}
+
+#ifdef HAVE_SYS_MTIO_H
+/*
+ * get_phys()
+ * Determine the physical block size on a tape drive. We need the physical
+ * block size so we know how many bytes we skip over when we move with
+ * mtio commands. We also make sure we are BEFORE THE TAPE FILEMARK when
+ * return.
+ * This is one really SLOW routine...
+ * Return:
+ * physical block size if ok (ok > 0), -1 otherwise
+ */
+
+static int
+get_phys(void)
+{
+ int padsz = 0;
+ int res;
+ int phyblk;
+ struct mtop mb;
+ char scbuf[MAXBLK];
+
+ /*
+ * move to the file mark, and then back up one record and read it.
+ * this should tell us the physical record size the tape is using.
+ */
+ if (lstrval == 1) {
+ /*
+ * we know we are at file mark when we get back a 0 from
+ * read()
+ */
+#ifdef SUPPORT_RMT
+ while ((res = rmtread_with_restart(arfd,
+ scbuf, sizeof(scbuf))) > 0)
+#else
+ while ((res = read_with_restart(arfd,
+ scbuf, sizeof(scbuf))) > 0)
+#endif /* SUPPORT_RMT */
+ padsz += res;
+ if (res < 0) {
+ syswarn(1, errno, "Unable to locate tape filemark.");
+ return -1;
+ }
+ }
+
+ /*
+ * move backwards over the file mark so we are at the end of the
+ * last record.
+ */
+ mb.mt_op = MTBSF;
+ mb.mt_count = 1;
+ if (
+#ifdef SUPPORT_RMT
+ rmtioctl(arfd, MTIOCTOP, &mb)
+#else
+ ioctl(arfd, MTIOCTOP, &mb)
+#endif /* SUPPORT_RMT */
+ < 0) {
+ syswarn(1, errno, "Unable to backspace over tape filemark.");
+ return -1;
+ }
+
+ /*
+ * move backwards so we are in front of the last record and read it to
+ * get physical tape blocksize.
+ */
+ mb.mt_op = MTBSR;
+ mb.mt_count = 1;
+ if (
+#ifdef SUPPORT_RMT
+ rmtioctl(arfd, MTIOCTOP, &mb)
+#else
+ ioctl(arfd, MTIOCTOP, &mb)
+#endif /* SUPPORT_RMT */
+ < 0) {
+ syswarn(1, errno, "Unable to backspace over last tape block.");
+ return -1;
+ }
+ if ((phyblk =
+#ifdef SUPPORT_RMT
+ rmtread_with_restart(arfd, scbuf, sizeof(scbuf))
+#else
+ read_with_restart(arfd, scbuf, sizeof(scbuf))
+#endif /* SUPPORT_RMT */
+ ) <= 0) {
+ syswarn(1, errno, "Cannot determine archive tape blocksize.");
+ return -1;
+ }
+
+ /*
+ * read forward to the file mark, then back up in front of the filemark
+ * (this is a bit paranoid, but should be safe to do).
+ */
+ while ((res =
+#ifdef SUPPORT_RMT
+ rmtread_with_restart(arfd, scbuf, sizeof(scbuf))
+#else
+ read_with_restart(arfd, scbuf, sizeof(scbuf))
+#endif /* SUPPORT_RMT */
+ ) > 0)
+ ;
+ if (res < 0) {
+ syswarn(1, errno, "Unable to locate tape filemark.");
+ return -1;
+ }
+ mb.mt_op = MTBSF;
+ mb.mt_count = 1;
+ if (
+#ifdef SUPPORT_RMT
+ rmtioctl(arfd, MTIOCTOP, &mb)
+#else
+ ioctl(arfd, MTIOCTOP, &mb)
+#endif /* SUPPORT_RMT */
+ < 0) {
+ syswarn(1, errno, "Unable to backspace over tape filemark.");
+ return -1;
+ }
+
+ /*
+ * set lstrval so we know that the filemark has not been seen
+ */
+ lstrval = 1;
+
+ /*
+ * return if there was no padding
+ */
+ if (padsz == 0)
+ return phyblk;
+
+ /*
+ * make sure we can move backwards over the padding. (this should
+ * never fail).
+ */
+ if (padsz % phyblk) {
+ tty_warn(1, "Tape drive unable to backspace requested amount");
+ return -1;
+ }
+
+ /*
+ * move backwards over the padding so the head is where it was when
+ * we were first called (if required).
+ */
+ mb.mt_op = MTBSR;
+ mb.mt_count = padsz/phyblk;
+ if (
+#ifdef SUPPORT_RMT
+ rmtioctl(arfd, MTIOCTOP, &mb)
+#else
+ ioctl(arfd, MTIOCTOP, &mb)
+#endif /* SUPPORT_RMT */
+ < 0) {
+ syswarn(1, errno,
+ "Unable to backspace tape over %ld pad blocks",
+ (long)mb.mt_count);
+ return -1;
+ }
+ return phyblk;
+}
+#endif
+
+/*
+ * ar_next()
+ * prompts the user for the next volume in this archive. For some devices
+ * we may allow the media to be changed. Otherwise a new archive is
+ * prompted for. By pax spec, if there is no controlling tty or an eof is
+ * read on tty input, we must quit pax.
+ * Return:
+ * 0 when ready to continue, -1 when all done
+ */
+
+int
+ar_next(void)
+{
+ char buf[PAXPATHLEN+2];
+ static char *arcfree = NULL;
+ sigset_t o_mask;
+
+ /*
+ * WE MUST CLOSE THE DEVICE. A lot of devices must see last close, (so
+ * things like writing EOF etc will be done) (Watch out ar_close() can
+ * also be called via a signal handler, so we must prevent a race.
+ */
+ if (sigprocmask(SIG_BLOCK, &s_mask, &o_mask) < 0)
+ syswarn(0, errno, "Unable to set signal mask");
+ ar_close();
+ if (sigprocmask(SIG_SETMASK, &o_mask, NULL) < 0)
+ syswarn(0, errno, "Unable to restore signal mask");
+
+ if (done || !wr_trail || force_one_volume)
+ return -1;
+
+ if (!is_gnutar)
+ tty_prnt("\nATTENTION! %s archive volume change required.\n",
+ argv0);
+
+ /*
+ * if i/o is on stdin or stdout, we cannot reopen it (we do not know
+ * the name), the user will be forced to type it in.
+ */
+ if (strcmp(arcname, STDO) && strcmp(arcname, STDN) && (artyp != ISREG)
+ && (artyp != ISPIPE)) {
+ if (artyp == ISTAPE
+#ifdef SUPPORT_RMT
+ || artyp == ISRMT
+#endif /* SUPPORT_RMT */
+ ) {
+ tty_prnt("%s ready for archive tape volume: %d\n",
+ arcname, arvol);
+ tty_prnt("Load the NEXT TAPE on the tape drive");
+ } else {
+ tty_prnt("%s ready for archive volume: %d\n",
+ arcname, arvol);
+ tty_prnt("Load the NEXT STORAGE MEDIA (if required)");
+ }
+
+ if ((act == ARCHIVE) || (act == APPND))
+ tty_prnt(" and make sure it is WRITE ENABLED.\n");
+ else
+ tty_prnt("\n");
+
+ for(;;) {
+ tty_prnt("Type \"y\" to continue, \".\" to quit %s,",
+ argv0);
+ tty_prnt(" or \"s\" to switch to new device.\nIf you");
+ tty_prnt(" cannot change storage media, type \"s\"\n");
+ tty_prnt("Is the device ready and online? > ");
+
+ if ((tty_read(buf,sizeof(buf))<0) || !strcmp(buf,".")){
+ done = 1;
+ lstrval = -1;
+ tty_prnt("Quitting %s!\n", argv0);
+ vfpart = 0;
+ return -1;
+ }
+
+ if ((buf[0] == '\0') || (buf[1] != '\0')) {
+ tty_prnt("%s unknown command, try again\n",buf);
+ continue;
+ }
+
+ switch (buf[0]) {
+ case 'y':
+ case 'Y':
+ /*
+ * we are to continue with the same device
+ */
+ if (ar_open(arcname) >= 0)
+ return 0;
+ tty_prnt("Cannot re-open %s, try again\n",
+ arcname);
+ continue;
+ case 's':
+ case 'S':
+ /*
+ * user wants to open a different device
+ */
+ tty_prnt("Switching to a different archive\n");
+ break;
+ default:
+ tty_prnt("%s unknown command, try again\n",buf);
+ continue;
+ }
+ break;
+ }
+ } else {
+ if (is_gnutar) {
+ tty_warn(1, "Unexpected EOF on archive file");
+ return -1;
+ }
+ tty_prnt("Ready for archive volume: %d\n", arvol);
+ }
+
+ /*
+ * have to go to a different archive
+ */
+ for (;;) {
+ tty_prnt("Input archive name or \".\" to quit %s.\n", argv0);
+ tty_prnt("Archive name > ");
+
+ if ((tty_read(buf, sizeof(buf)) < 0) || !strcmp(buf, ".")) {
+ done = 1;
+ lstrval = -1;
+ tty_prnt("Quitting %s!\n", argv0);
+ vfpart = 0;
+ return -1;
+ }
+ if (buf[0] == '\0') {
+ tty_prnt("Empty file name, try again\n");
+ continue;
+ }
+ if (!strcmp(buf, "..")) {
+ tty_prnt("Illegal file name: .. try again\n");
+ continue;
+ }
+ if (strlen(buf) > PAXPATHLEN) {
+ tty_prnt("File name too long, try again\n");
+ continue;
+ }
+
+ /*
+ * try to open new archive
+ */
+ if (ar_open(buf) >= 0) {
+ if (arcfree) {
+ (void)free(arcfree);
+ arcfree = NULL;
+ }
+ if ((arcfree = strdup(buf)) == NULL) {
+ done = 1;
+ lstrval = -1;
+ tty_warn(0, "Cannot save archive name.");
+ return -1;
+ }
+ arcname = arcfree;
+ break;
+ }
+ tty_prnt("Cannot open %s, try again\n", buf);
+ continue;
+ }
+ return 0;
+}
+
+/*
+ * ar_start_gzip()
+ * starts the compression/decompression process as a child, using magic
+ * to keep the fd the same in the calling function (parent). possible
+ * programs are GZIP_CMD, BZIP2_CMD, and COMPRESS_CMD.
+ */
+void
+ar_start_gzip(int fd, const char *gzp, int wr)
+{
+ int fds[2];
+ const char *gzip_flags;
+
+ if (pipe(fds) < 0)
+ err(1, "could not pipe");
+ zpid = fork();
+ if (zpid < 0)
+ err(1, "could not fork");
+
+ /* parent */
+ if (zpid) {
+ if (wr)
+ dup2(fds[1], fd);
+ else
+ dup2(fds[0], fd);
+ close(fds[0]);
+ close(fds[1]);
+ } else {
+ if (wr) {
+ dup2(fds[0], STDIN_FILENO);
+ dup2(fd, STDOUT_FILENO);
+ gzip_flags = "-c";
+ } else {
+ dup2(fds[1], STDOUT_FILENO);
+ dup2(fd, STDIN_FILENO);
+ gzip_flags = "-dc";
+ }
+ close(fds[0]);
+ close(fds[1]);
+ if (execlp(gzp, gzp, gzip_flags, NULL) < 0)
+ err(1, "could not exec");
+ /* NOTREACHED */
+ }
+}
+
+static const char *
+timefmt(char *buf, size_t size, off_t sz, time_t tm, const char *unitstr)
+{
+ (void)snprintf(buf, size, "%lu secs (" OFFT_F " %s/sec)",
+ (unsigned long)tm, (OFFT_T)(sz / tm), unitstr);
+ return buf;
+}
+
+static const char *
+sizefmt(char *buf, size_t size, off_t sz)
+{
+ (void)snprintf(buf, size, OFFT_F " bytes", (OFFT_T)sz);
+ return buf;
+}
+
+void
+ar_summary(int n)
+{
+ time_t secs;
+ char buf[BUFSIZ];
+ char tbuf[MAXPATHLEN/4]; /* XXX silly size! */
+ char s1buf[MAXPATHLEN/8]; /* XXX very silly size! */
+ char s2buf[MAXPATHLEN/8]; /* XXX very silly size! */
+ FILE *outf;
+
+ if (act == LIST)
+ outf = stdout;
+ else
+ outf = stderr;
+
+ /*
+ * If we are called from a signal (n != 0), use snprintf(3) so that we
+ * don't reenter stdio(3).
+ */
+ (void)time(&secs);
+ if ((secs -= starttime) == 0)
+ secs = 1;
+
+ /*
+ * If we have not determined the format yet, we just say how many bytes
+ * we have skipped over looking for a header to id. there is no way we
+ * could have written anything yet.
+ */
+ if (frmt == NULL && act != COPY) {
+ snprintf(buf, sizeof(buf),
+ "unknown format, %s skipped in %s\n",
+ sizefmt(s1buf, sizeof(s1buf), rdcnt),
+ timefmt(tbuf, sizeof(tbuf), rdcnt, secs, "bytes"));
+ if (n == 0)
+ (void)fprintf(outf, "%s: %s", argv0, buf);
+ else
+ (void)write(STDERR_FILENO, buf, strlen(buf));
+ return;
+ }
+
+
+ if (n != 0 && *archd.name) {
+ snprintf(buf, sizeof(buf), "Working on `%s' (%s)\n",
+ archd.name, sizefmt(s1buf, sizeof(s1buf), archd.sb.st_size));
+ (void)write(STDERR_FILENO, buf, strlen(buf));
+ }
+
+
+ if (act == COPY) {
+ snprintf(buf, sizeof(buf),
+ "%lu files in %s\n",
+ (unsigned long)flcnt,
+ timefmt(tbuf, sizeof(tbuf), flcnt, secs, "files"));
+ } else {
+ snprintf(buf, sizeof(buf),
+ "%s vol %d, %lu files, %s read, %s written in %s\n",
+ frmt->name, arvol-1, (unsigned long)flcnt,
+ sizefmt(s1buf, sizeof(s1buf), rdcnt),
+ sizefmt(s2buf, sizeof(s2buf), wrcnt),
+ timefmt(tbuf, sizeof(tbuf), rdcnt + wrcnt, secs, "bytes"));
+ }
+ if (n == 0)
+ (void)fprintf(outf, "%s: %s", argv0, buf);
+ else
+ (void)write(STDERR_FILENO, buf, strlen(buf));
+}
+
+/*
+ * ar_dochdir(name)
+ * change directory to name, and remember where we came from and
+ * where we change to (for ar_open).
+ *
+ * Maybe we could try to be smart and only do the actual chdir
+ * when necessary to write a file read from the archive, but this
+ * is not easy to get right given the pax code structure.
+ *
+ * Be sure to not leak descriptors!
+ *
+ * We are called N * M times when extracting, and N times when
+ * writing archives, where
+ * N: number of -C options
+ * M: number of files in archive
+ *
+ * Returns 0 if all went well, else -1.
+ */
+
+int
+ar_dochdir(const char *name)
+{
+ /* First fdochdir() back... */
+ if (fdochdir(cwdfd) == -1)
+ return -1;
+ if (dochdir(name) == -1)
+ return -1;
+ return 0;
+}
diff --git a/bin/pax/ar_subs.c b/bin/pax/ar_subs.c
new file mode 100644
index 0000000..d4ba54b
--- /dev/null
+++ b/bin/pax/ar_subs.c
@@ -0,0 +1,1449 @@
+/* $NetBSD: ar_subs.c,v 1.56 2011/08/31 16:24:54 plunky Exp $ */
+
+/*-
+ * Copyright (c) 1992 Keith Muller.
+ * Copyright (c) 1992, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Keith Muller of the University of California, San Diego.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#if HAVE_NBTOOL_CONFIG_H
+#include "nbtool_config.h"
+#endif
+
+#include <sys/cdefs.h>
+#if !defined(lint)
+#if 0
+static char sccsid[] = "@(#)ar_subs.c 8.2 (Berkeley) 4/18/94";
+#else
+__RCSID("$NetBSD: ar_subs.c,v 1.56 2011/08/31 16:24:54 plunky Exp $");
+#endif
+#endif /* not lint */
+
+#include <sys/types.h>
+#include <sys/time.h>
+#include <sys/stat.h>
+#include <sys/param.h>
+#include <signal.h>
+#include <string.h>
+#include <stdio.h>
+#include <ctype.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <time.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include "pax.h"
+#include "pat_rep.h"
+#include "extern.h"
+
+static int path_check(ARCHD *, int);
+static int wr_archive(ARCHD *, int is_app);
+static int get_arc(void);
+static int next_head(ARCHD *);
+#if !HAVE_NBTOOL_CONFIG_H
+static int fdochroot(int);
+#endif
+extern sigset_t s_mask;
+
+/*
+ * Routines which control the overall operation modes of pax as specified by
+ * the user: list, append, read ...
+ */
+
+static char hdbuf[BLKMULT]; /* space for archive header on read */
+u_long flcnt; /* number of files processed */
+ARCHD archd;
+
+static char cwdpath[MAXPATHLEN]; /* current working directory path */
+static size_t cwdpathlen; /* current working directory path len */
+
+int
+updatepath(void)
+{
+ if (getcwd(cwdpath, sizeof(cwdpath)) == NULL) {
+ syswarn(1, errno, "Cannot get working directory");
+ return -1;
+ }
+ cwdpathlen = strlen(cwdpath);
+ return 0;
+}
+
+int
+fdochdir(int fcwd)
+{
+ if (fchdir(fcwd) == -1) {
+ syswarn(1, errno, "Cannot chdir to `.'");
+ return -1;
+ }
+ return updatepath();
+}
+
+int
+dochdir(const char *name)
+{
+ if (chdir(name) == -1)
+ syswarn(1, errno, "Cannot chdir to `%s'", name);
+ return updatepath();
+}
+
+#if !HAVE_NBTOOL_CONFIG_H
+static int
+fdochroot(int fcwd)
+{
+ if (fchroot(fcwd) != 0) {
+ syswarn(1, errno, "Can't fchroot to \".\"");
+ return -1;
+ }
+ return updatepath();
+}
+#endif
+
+/*
+ * mkdir(), but if we failed, check if someone else made it for us
+ * already and don't error out.
+ */
+int
+domkdir(const char *fname, mode_t mode)
+{
+ int error;
+ struct stat sb;
+
+ if ((error = mkdir(fname, mode)) != -1)
+ return error;
+
+ switch (errno) {
+ case EISDIR:
+ return 0;
+ case EEXIST:
+ case EACCES:
+ case ENOSYS: /* Grr Solaris */
+ case EROFS:
+ error = errno;
+ if (stat(fname, &sb) != -1 && S_ISDIR(sb.st_mode))
+ return 0;
+ errno = error;
+ /*FALLTHROUGH*/
+ default:
+ return -1;
+ }
+}
+
+static int
+path_check(ARCHD *arcn, int level)
+{
+ char buf[MAXPATHLEN];
+ char *p;
+
+ if ((p = strrchr(arcn->name, '/')) == NULL)
+ return 0;
+ *p = '\0';
+
+ if (realpath(arcn->name, buf) == NULL) {
+ int error;
+ error = path_check(arcn, level + 1);
+ *p = '/';
+ if (error == 0)
+ return 0;
+ if (level == 0)
+ syswarn(1, 0, "Cannot resolve `%s'", arcn->name);
+ return -1;
+ }
+ if (strncmp(buf, cwdpath, cwdpathlen) != 0) {
+ *p = '/';
+ syswarn(1, 0, "Attempt to write file `%s' that resolves into "
+ "`%s/%s' outside current working directory `%s' ignored",
+ arcn->name, buf, p + 1, cwdpath);
+ return -1;
+ }
+ *p = '/';
+ return 0;
+}
+
+/*
+ * list()
+ * list the contents of an archive which match user supplied pattern(s)
+ * (if no pattern is supplied, list entire contents).
+ */
+
+int
+list(void)
+{
+ ARCHD *arcn;
+ int res;
+ time_t now;
+
+ arcn = &archd;
+ /*
+ * figure out archive type; pass any format specific options to the
+ * archive option processing routine; call the format init routine. We
+ * also save current time for ls_list() so we do not make a system
+ * call for each file we need to print. If verbose (vflag) start up
+ * the name and group caches.
+ */
+ if ((get_arc() < 0) || ((*frmt->options)() < 0) ||
+ ((*frmt->st_rd)() < 0))
+ return 1;
+
+ now = time(NULL);
+
+ /*
+ * step through the archive until the format says it is done
+ */
+ while (next_head(arcn) == 0) {
+ if (arcn->type == PAX_GLL || arcn->type == PAX_GLF) {
+ /*
+ * we need to read, to get the real filename
+ */
+ off_t cnt;
+ if (!(*frmt->rd_data)(arcn, -arcn->type, &cnt))
+ (void)rd_skip(cnt + arcn->pad);
+ continue;
+ }
+
+ /*
+ * check for pattern, and user specified options match.
+ * When all patterns are matched we are done.
+ */
+ if ((res = pat_match(arcn)) < 0)
+ break;
+
+ if ((res == 0) && (sel_chk(arcn) == 0)) {
+ /*
+ * pattern resulted in a selected file
+ */
+ if (pat_sel(arcn) < 0)
+ break;
+
+ /*
+ * modify the name as requested by the user if name
+ * survives modification, do a listing of the file
+ */
+ if ((res = mod_name(arcn, RENM)) < 0)
+ break;
+ if (res == 0) {
+ if (arcn->name[0] == '/' && !check_Aflag()) {
+ memmove(arcn->name, arcn->name + 1,
+ strlen(arcn->name));
+ }
+ ls_list(arcn, now, stdout);
+ }
+ /*
+ * if there's an error writing to stdout then we must
+ * stop now -- we're probably writing to a pipe that
+ * has been closed by the reader.
+ */
+ if (ferror(stdout)) {
+ syswarn(1, errno, "Listing incomplete.");
+ break;
+ }
+ }
+ /*
+ * skip to next archive format header using values calculated
+ * by the format header read routine
+ */
+ if (rd_skip(arcn->skip + arcn->pad) == 1)
+ break;
+ }
+
+ /*
+ * all done, let format have a chance to cleanup, and make sure that
+ * the patterns supplied by the user were all matched
+ */
+ (void)(*frmt->end_rd)();
+ (void)sigprocmask(SIG_BLOCK, &s_mask, NULL);
+ ar_close();
+ pat_chk();
+
+ return 0;
+}
+
+/*
+ * extract()
+ * extract the member(s) of an archive as specified by user supplied
+ * pattern(s) (no patterns extracts all members)
+ */
+
+int
+extract(void)
+{
+ ARCHD *arcn;
+ int res;
+ off_t cnt;
+ struct stat sb;
+ int fd;
+ time_t now;
+
+ arcn = &archd;
+ /*
+ * figure out archive type; pass any format specific options to the
+ * archive option processing routine; call the format init routine;
+ * start up the directory modification time and access mode database
+ */
+ if ((get_arc() < 0) || ((*frmt->options)() < 0) ||
+ ((*frmt->st_rd)() < 0) || (dir_start() < 0))
+ return 1;
+
+ now = time(NULL);
+#if !HAVE_NBTOOL_CONFIG_H
+ if (do_chroot)
+ (void)fdochroot(cwdfd);
+#endif
+
+ /*
+ * When we are doing interactive rename, we store the mapping of names
+ * so we can fix up hard links files later in the archive.
+ */
+ if (iflag && (name_start() < 0))
+ return 1;
+
+ /*
+ * step through each entry on the archive until the format read routine
+ * says it is done
+ */
+ while (next_head(arcn) == 0) {
+ int write_to_hard_link = 0;
+
+ if (arcn->type == PAX_GLL || arcn->type == PAX_GLF) {
+ /*
+ * we need to read, to get the real filename
+ */
+ if (!(*frmt->rd_data)(arcn, -arcn->type, &cnt))
+ (void)rd_skip(cnt + arcn->pad);
+ continue;
+ }
+
+ /*
+ * check for pattern, and user specified options match. When
+ * all the patterns are matched we are done
+ */
+ if ((res = pat_match(arcn)) < 0)
+ break;
+
+ if ((res > 0) || (sel_chk(arcn) != 0)) {
+ /*
+ * file is not selected. skip past any file
+ * data and padding and go back for the next
+ * archive member
+ */
+ (void)rd_skip(arcn->skip + arcn->pad);
+ continue;
+ }
+
+ if (kflag && (lstat(arcn->name, &sb) == 0)) {
+ (void)rd_skip(arcn->skip + arcn->pad);
+ continue;
+ }
+
+ /*
+ * with -u or -D only extract when the archive member is newer
+ * than the file with the same name in the file system (no
+ * test of being the same type is required).
+ * NOTE: this test is done BEFORE name modifications as
+ * specified by pax. this operation can be confusing to the
+ * user who might expect the test to be done on an existing
+ * file AFTER the name mod. In honesty the pax spec is probably
+ * flawed in this respect. ignore this for GNU long links.
+ */
+ if ((uflag || Dflag) && ((lstat(arcn->name, &sb) == 0))) {
+ if (uflag && Dflag) {
+ if ((arcn->sb.st_mtime <= sb.st_mtime) &&
+ (arcn->sb.st_ctime <= sb.st_ctime)) {
+ (void)rd_skip(arcn->skip + arcn->pad);
+ continue;
+ }
+ } else if (Dflag) {
+ if (arcn->sb.st_ctime <= sb.st_ctime) {
+ (void)rd_skip(arcn->skip + arcn->pad);
+ continue;
+ }
+ } else if (arcn->sb.st_mtime <= sb.st_mtime) {
+ (void)rd_skip(arcn->skip + arcn->pad);
+ continue;
+ }
+ }
+
+ /*
+ * this archive member is now been selected. modify the name.
+ */
+ if ((pat_sel(arcn) < 0) || ((res = mod_name(arcn, RENM)) < 0))
+ break;
+ if (res > 0) {
+ /*
+ * a bad name mod, skip and purge name from link table
+ */
+ purg_lnk(arcn);
+ (void)rd_skip(arcn->skip + arcn->pad);
+ continue;
+ }
+
+ if (arcn->name[0] == '/' && !check_Aflag()) {
+ memmove(arcn->name, arcn->name + 1, strlen(arcn->name));
+ }
+ /*
+ * Non standard -Y and -Z flag. When the existing file is
+ * same age or newer skip; ignore this for GNU long links.
+ */
+ if ((Yflag || Zflag) && ((lstat(arcn->name, &sb) == 0))) {
+ if (Yflag && Zflag) {
+ if ((arcn->sb.st_mtime <= sb.st_mtime) &&
+ (arcn->sb.st_ctime <= sb.st_ctime)) {
+ (void)rd_skip(arcn->skip + arcn->pad);
+ continue;
+ }
+ } else if (Yflag) {
+ if (arcn->sb.st_ctime <= sb.st_ctime) {
+ (void)rd_skip(arcn->skip + arcn->pad);
+ continue;
+ }
+ } else if (arcn->sb.st_mtime <= sb.st_mtime) {
+ (void)rd_skip(arcn->skip + arcn->pad);
+ continue;
+ }
+ }
+
+ if (vflag) {
+ if (vflag > 1)
+ ls_list(arcn, now, listf);
+ else {
+ (void)safe_print(arcn->name, listf);
+ vfpart = 1;
+ }
+ }
+
+ /*
+ * if required, chdir around.
+ */
+ if ((arcn->pat != NULL) && (arcn->pat->chdname != NULL) &&
+ !to_stdout)
+ dochdir(arcn->pat->chdname);
+
+ if (secure && path_check(arcn, 0) != 0) {
+ (void)rd_skip(arcn->skip + arcn->pad);
+ continue;
+ }
+
+
+ /*
+ * all ok, extract this member based on type
+ */
+ if ((arcn->type != PAX_REG) && (arcn->type != PAX_CTG)) {
+ /*
+ * process archive members that are not regular files.
+ * throw out padding and any data that might follow the
+ * header (as determined by the format).
+ */
+ if ((arcn->type == PAX_HLK) ||
+ (arcn->type == PAX_HRG))
+ res = lnk_creat(arcn, &write_to_hard_link);
+ else
+ res = node_creat(arcn);
+
+ if (!write_to_hard_link) {
+ (void)rd_skip(arcn->skip + arcn->pad);
+ if (res < 0)
+ purg_lnk(arcn);
+
+ if (vflag && vfpart) {
+ (void)putc('\n', listf);
+ vfpart = 0;
+ }
+ continue;
+ }
+ }
+ if (to_stdout)
+ fd = STDOUT_FILENO;
+ else {
+ /*
+ * We have a file with data here. If we cannot create
+ * it, skip over the data and purge the name from hard
+ * link table.
+ */
+ if ((fd = file_creat(arcn, write_to_hard_link)) < 0) {
+ (void)fflush(listf);
+ (void)rd_skip(arcn->skip + arcn->pad);
+ purg_lnk(arcn);
+ continue;
+ }
+ }
+ /*
+ * extract the file from the archive and skip over padding and
+ * any unprocessed data
+ */
+ res = (*frmt->rd_data)(arcn, fd, &cnt);
+ if (!to_stdout)
+ file_close(arcn, fd);
+ if (vflag && vfpart) {
+ (void)putc('\n', listf);
+ vfpart = 0;
+ }
+ if (!res)
+ (void)rd_skip(cnt + arcn->pad);
+
+ /*
+ * if required, chdir around.
+ */
+ if ((arcn->pat != NULL) && (arcn->pat->chdname != NULL))
+ fdochdir(cwdfd);
+ }
+
+ /*
+ * all done, restore directory modes and times as required; make sure
+ * all patterns supplied by the user were matched; block off signals
+ * to avoid chance for multiple entry into the cleanup code.
+ */
+ (void)(*frmt->end_rd)();
+ (void)sigprocmask(SIG_BLOCK, &s_mask, NULL);
+ ar_close();
+ proc_dir();
+ pat_chk();
+
+ return 0;
+}
+
+/*
+ * wr_archive()
+ * Write an archive. used in both creating a new archive and appends on
+ * previously written archive.
+ */
+
+static int
+wr_archive(ARCHD *arcn, int is_app)
+{
+ int res;
+ int hlk;
+ int wr_one;
+ off_t cnt;
+ int (*wrf)(ARCHD *);
+ int fd = -1;
+ time_t now;
+
+ /*
+ * if this format supports hard link storage, start up the database
+ * that detects them.
+ */
+ if (((hlk = frmt->hlk) == 1) && (lnk_start() < 0))
+ return 1;
+
+ /*
+ * start up the file traversal code and format specific write
+ */
+ if ((ftree_start() < 0) || ((*frmt->st_wr)() < 0))
+ return 1;
+ wrf = frmt->wr;
+
+ now = time(NULL);
+
+ /*
+ * When we are doing interactive rename, we store the mapping of names
+ * so we can fix up hard links files later in the archive.
+ */
+ if (iflag && (name_start() < 0))
+ return 1;
+
+ /*
+ * if this is not append, and there are no files, we do no write a trailer
+ */
+ wr_one = is_app;
+
+ /*
+ * while there are files to archive, process them one at at time
+ */
+ while (next_file(arcn) == 0) {
+ /*
+ * check if this file meets user specified options match.
+ */
+ if (sel_chk(arcn) != 0)
+ continue;
+ /*
+ * Here we handle the exclusion -X gnu style patterns which
+ * are implemented like a pattern list. We don't modify the
+ * name as this will be done below again, and we don't want
+ * to double modify it.
+ */
+ if ((res = mod_name(arcn, 0)) < 0)
+ break;
+ if (res == 1)
+ continue;
+ fd = -1;
+ if (uflag) {
+ /*
+ * only archive if this file is newer than a file with
+ * the same name that is already stored on the archive
+ */
+ if ((res = chk_ftime(arcn)) < 0)
+ break;
+ if (res > 0)
+ continue;
+ }
+
+ /*
+ * this file is considered selected now. see if this is a hard
+ * link to a file already stored
+ */
+ ftree_sel(arcn);
+ if (hlk && (chk_lnk(arcn) < 0))
+ break;
+
+ if ((arcn->type == PAX_REG) || (arcn->type == PAX_HRG) ||
+ (arcn->type == PAX_CTG)) {
+ /*
+ * we will have to read this file. by opening it now we
+ * can avoid writing a header to the archive for a file
+ * we were later unable to read (we also purge it from
+ * the link table).
+ */
+ if ((fd = open(arcn->org_name, O_RDONLY, 0)) < 0) {
+ syswarn(1, errno, "Unable to open %s to read",
+ arcn->org_name);
+ purg_lnk(arcn);
+ continue;
+ }
+ }
+
+ /*
+ * Now modify the name as requested by the user
+ */
+ if ((res = mod_name(arcn, RENM)) < 0) {
+ /*
+ * name modification says to skip this file, close the
+ * file and purge link table entry
+ */
+ rdfile_close(arcn, &fd);
+ purg_lnk(arcn);
+ break;
+ }
+
+ if (arcn->name[0] == '/' && !check_Aflag()) {
+ memmove(arcn->name, arcn->name + 1, strlen(arcn->name));
+ }
+
+ if ((res > 0) || (docrc && (set_crc(arcn, fd) < 0))) {
+ /*
+ * unable to obtain the crc we need, close the file,
+ * purge link table entry
+ */
+ rdfile_close(arcn, &fd);
+ purg_lnk(arcn);
+ continue;
+ }
+
+ if (vflag) {
+ if (vflag > 1)
+ ls_list(arcn, now, listf);
+ else {
+ (void)safe_print(arcn->name, listf);
+ vfpart = 1;
+ }
+ }
+ ++flcnt;
+
+ /*
+ * looks safe to store the file, have the format specific
+ * routine write routine store the file header on the archive
+ */
+ if ((res = (*wrf)(arcn)) < 0) {
+ rdfile_close(arcn, &fd);
+ break;
+ }
+ wr_one = 1;
+ if (res > 0) {
+ /*
+ * format write says no file data needs to be stored
+ * so we are done messing with this file
+ */
+ if (vflag && vfpart) {
+ (void)putc('\n', listf);
+ vfpart = 0;
+ }
+ rdfile_close(arcn, &fd);
+ continue;
+ }
+
+ /*
+ * Add file data to the archive, quit on write error. if we
+ * cannot write the entire file contents to the archive we
+ * must pad the archive to replace the missing file data
+ * (otherwise during an extract the file header for the file
+ * which FOLLOWS this one will not be where we expect it to
+ * be).
+ */
+ res = (*frmt->wr_data)(arcn, fd, &cnt);
+ rdfile_close(arcn, &fd);
+ if (vflag && vfpart) {
+ (void)putc('\n', listf);
+ vfpart = 0;
+ }
+ if (res < 0)
+ break;
+
+ /*
+ * pad as required, cnt is number of bytes not written
+ */
+ if (((cnt > 0) && (wr_skip(cnt) < 0)) ||
+ ((arcn->pad > 0) && (wr_skip(arcn->pad) < 0)))
+ break;
+ }
+
+ /*
+ * tell format to write trailer; pad to block boundary; reset directory
+ * mode/access times, and check if all patterns supplied by the user
+ * were matched. block off signals to avoid chance for multiple entry
+ * into the cleanup code
+ */
+ if (wr_one) {
+ (*frmt->end_wr)();
+ wr_fin();
+ }
+ (void)sigprocmask(SIG_BLOCK, &s_mask, NULL);
+ ar_close();
+ if (tflag)
+ proc_dir();
+ ftree_chk();
+
+ return 0;
+}
+
+/*
+ * append()
+ * Add file to previously written archive. Archive format specified by the
+ * user must agree with archive. The archive is read first to collect
+ * modification times (if -u) and locate the archive trailer. The archive
+ * is positioned in front of the record with the trailer and wr_archive()
+ * is called to add the new members.
+ * PAX IMPLEMENTATION DETAIL NOTE:
+ * -u is implemented by adding the new members to the end of the archive.
+ * Care is taken so that these do not end up as links to the older
+ * version of the same file already stored in the archive. It is expected
+ * when extraction occurs these newer versions will over-write the older
+ * ones stored "earlier" in the archive (this may be a bad assumption as
+ * it depends on the implementation of the program doing the extraction).
+ * It is really difficult to splice in members without either re-writing
+ * the entire archive (from the point were the old version was), or having
+ * assistance of the format specification in terms of a special update
+ * header that invalidates a previous archive record. The posix spec left
+ * the method used to implement -u unspecified. This pax is able to
+ * over write existing files that it creates.
+ */
+
+int
+append(void)
+{
+ ARCHD *arcn;
+ int res;
+ FSUB *orgfrmt;
+ int udev;
+ off_t tlen;
+
+ arcn = &archd;
+ orgfrmt = frmt;
+
+ /*
+ * Do not allow an append operation if the actual archive is of a
+ * different format than the user specified format.
+ */
+ if (get_arc() < 0)
+ return 1;
+ if ((orgfrmt != NULL) && (orgfrmt != frmt)) {
+ tty_warn(1, "Cannot mix current archive format %s with %s",
+ frmt->name, orgfrmt->name);
+ return 1;
+ }
+
+ /*
+ * pass the format any options and start up format
+ */
+ if (((*frmt->options)() < 0) || ((*frmt->st_rd)() < 0))
+ return 1;
+
+ /*
+ * if we only are adding members that are newer, we need to save the
+ * mod times for all files we see.
+ */
+ if (uflag && (ftime_start() < 0))
+ return 1;
+
+ /*
+ * some archive formats encode hard links by recording the device and
+ * file serial number (inode) but copy the file anyway (multiple times)
+ * to the archive. When we append, we run the risk that newly added
+ * files may have the same device and inode numbers as those recorded
+ * on the archive but during a previous run. If this happens, when the
+ * archive is extracted we get INCORRECT hard links. We avoid this by
+ * remapping the device numbers so that newly added files will never
+ * use the same device number as one found on the archive. remapping
+ * allows new members to safely have links among themselves. remapping
+ * also avoids problems with file inode (serial number) truncations
+ * when the inode number is larger than storage space in the archive
+ * header. See the remap routines for more details.
+ */
+ if ((udev = frmt->udev) && (dev_start() < 0))
+ return 1;
+
+ /*
+ * reading the archive may take a long time. If verbose tell the user
+ */
+ if (vflag || Vflag) {
+ (void)fprintf(listf,
+ "%s: Reading archive to position at the end...", argv0);
+ vfpart = 1;
+ }
+
+ /*
+ * step through the archive until the format says it is done
+ */
+ while (next_head(arcn) == 0) {
+ /*
+ * check if this file meets user specified options.
+ */
+ if (sel_chk(arcn) != 0) {
+ if (rd_skip(arcn->skip + arcn->pad) == 1)
+ break;
+ continue;
+ }
+
+ if (uflag) {
+ /*
+ * see if this is the newest version of this file has
+ * already been seen, if so skip.
+ */
+ if ((res = chk_ftime(arcn)) < 0)
+ break;
+ if (res > 0) {
+ if (rd_skip(arcn->skip + arcn->pad) == 1)
+ break;
+ continue;
+ }
+ }
+
+ /*
+ * Store this device number. Device numbers seen during the
+ * read phase of append will cause newly appended files with a
+ * device number seen in the old part of the archive to be
+ * remapped to an unused device number.
+ */
+ if ((udev && (add_dev(arcn) < 0)) ||
+ (rd_skip(arcn->skip + arcn->pad) == 1))
+ break;
+ }
+
+ /*
+ * done, finish up read and get the number of bytes to back up so we
+ * can add new members. The format might have used the hard link table,
+ * purge it.
+ */
+ tlen = (*frmt->end_rd)();
+ lnk_end();
+
+ /*
+ * try to position for write, if this fails quit. if any error occurs,
+ * we will refuse to write
+ */
+ if (appnd_start(tlen) < 0)
+ return 1;
+
+ /*
+ * tell the user we are done reading.
+ */
+ if ((vflag || Vflag) && vfpart) {
+ (void)safe_print("done.\n", listf);
+ vfpart = 0;
+ }
+
+ /*
+ * go to the writing phase to add the new members
+ */
+ res = wr_archive(arcn, 1);
+ if (res == 1) {
+ /*
+ * wr_archive failed in some way, but before any files were
+ * added. These are the only steps needed to cleanup (and
+ * not truncate the archive).
+ */
+ wr_fin();
+ (void)sigprocmask(SIG_BLOCK, &s_mask, NULL);
+ ar_close();
+ }
+ return res;
+}
+
+/*
+ * archive()
+ * write a new archive
+ */
+
+int
+archive(void)
+{
+
+ /*
+ * if we only are adding members that are newer, we need to save the
+ * mod times for all files; set up for writing; pass the format any
+ * options write the archive
+ */
+ if ((uflag && (ftime_start() < 0)) || (wr_start() < 0))
+ return 1;
+ if ((*frmt->options)() < 0)
+ return 1;
+
+ return wr_archive(&archd, 0);
+}
+
+/*
+ * copy()
+ * copy files from one part of the file system to another. this does not
+ * use any archive storage. The EFFECT OF THE COPY IS THE SAME as if an
+ * archive was written and then extracted in the destination directory
+ * (except the files are forced to be under the destination directory).
+ */
+
+int
+copy(void)
+{
+ ARCHD *arcn;
+ int res;
+ int fddest;
+ char *dest_pt;
+ size_t dlen;
+ size_t drem;
+ int fdsrc = -1;
+ struct stat sb;
+ char dirbuf[PAXPATHLEN+1];
+
+ arcn = &archd;
+ /*
+ * set up the destination dir path and make sure it is a directory. We
+ * make sure we have a trailing / on the destination
+ */
+ dlen = strlcpy(dirbuf, dirptr, sizeof(dirbuf));
+ if (dlen >= sizeof(dirbuf) ||
+ (dlen == sizeof(dirbuf) - 1 && dirbuf[dlen - 1] != '/')) {
+ tty_warn(1, "directory name is too long %s", dirptr);
+ return 1;
+ }
+ dest_pt = dirbuf + dlen;
+ if (*(dest_pt-1) != '/') {
+ *dest_pt++ = '/';
+ ++dlen;
+ }
+ *dest_pt = '\0';
+ drem = PAXPATHLEN - dlen;
+
+ if (stat(dirptr, &sb) < 0) {
+ syswarn(1, errno, "Cannot access destination directory %s",
+ dirptr);
+ return 1;
+ }
+ if (!S_ISDIR(sb.st_mode)) {
+ tty_warn(1, "Destination is not a directory %s", dirptr);
+ return 1;
+ }
+
+ /*
+ * start up the hard link table; file traversal routines and the
+ * modification time and access mode database
+ */
+ if ((lnk_start() < 0) || (ftree_start() < 0) || (dir_start() < 0))
+ return 1;
+
+ /*
+ * When we are doing interactive rename, we store the mapping of names
+ * so we can fix up hard links files later in the archive.
+ */
+ if (iflag && (name_start() < 0))
+ return 1;
+
+ /*
+ * set up to cp file trees
+ */
+ cp_start();
+
+ /*
+ * while there are files to archive, process them
+ */
+ while (next_file(arcn) == 0) {
+ fdsrc = -1;
+
+ /*
+ * check if this file meets user specified options
+ */
+ if (sel_chk(arcn) != 0)
+ continue;
+
+ /*
+ * if there is already a file in the destination directory with
+ * the same name and it is newer, skip the one stored on the
+ * archive.
+ * NOTE: this test is done BEFORE name modifications as
+ * specified by pax. this can be confusing to the user who
+ * might expect the test to be done on an existing file AFTER
+ * the name mod. In honesty the pax spec is probably flawed in
+ * this respect
+ */
+ if (uflag || Dflag) {
+ /*
+ * create the destination name
+ */
+ if (strlcpy(dest_pt, arcn->name + (*arcn->name == '/'),
+ drem + 1) > drem) {
+ tty_warn(1, "Destination pathname too long %s",
+ arcn->name);
+ continue;
+ }
+
+ /*
+ * if existing file is same age or newer skip
+ */
+ res = lstat(dirbuf, &sb);
+ *dest_pt = '\0';
+
+ if (res == 0) {
+ if (uflag && Dflag) {
+ if ((arcn->sb.st_mtime<=sb.st_mtime) &&
+ (arcn->sb.st_ctime<=sb.st_ctime))
+ continue;
+ } else if (Dflag) {
+ if (arcn->sb.st_ctime <= sb.st_ctime)
+ continue;
+ } else if (arcn->sb.st_mtime <= sb.st_mtime)
+ continue;
+ }
+ }
+
+ /*
+ * this file is considered selected. See if this is a hard link
+ * to a previous file; modify the name as requested by the
+ * user; set the final destination.
+ */
+ ftree_sel(arcn);
+ if ((chk_lnk(arcn) < 0) || ((res = mod_name(arcn, RENM)) < 0))
+ break;
+ if ((res > 0) || (set_dest(arcn, dirbuf, dlen) < 0)) {
+ /*
+ * skip file, purge from link table
+ */
+ purg_lnk(arcn);
+ continue;
+ }
+
+ /*
+ * Non standard -Y and -Z flag. When the exisiting file is
+ * same age or newer skip
+ */
+ if ((Yflag || Zflag) && ((lstat(arcn->name, &sb) == 0))) {
+ if (Yflag && Zflag) {
+ if ((arcn->sb.st_mtime <= sb.st_mtime) &&
+ (arcn->sb.st_ctime <= sb.st_ctime))
+ continue;
+ } else if (Yflag) {
+ if (arcn->sb.st_ctime <= sb.st_ctime)
+ continue;
+ } else if (arcn->sb.st_mtime <= sb.st_mtime)
+ continue;
+ }
+
+ if (vflag) {
+ (void)safe_print(arcn->name, listf);
+ vfpart = 1;
+ }
+ ++flcnt;
+
+ /*
+ * try to create a hard link to the src file if requested
+ * but make sure we are not trying to overwrite ourselves.
+ */
+ if (lflag)
+ res = cross_lnk(arcn);
+ else
+ res = chk_same(arcn);
+ if (res <= 0) {
+ if (vflag && vfpart) {
+ (void)putc('\n', listf);
+ vfpart = 0;
+ }
+ continue;
+ }
+
+ /*
+ * have to create a new file
+ */
+ if ((arcn->type != PAX_REG) && (arcn->type != PAX_CTG)) {
+ /*
+ * create a link or special file
+ */
+ if ((arcn->type == PAX_HLK) ||
+ (arcn->type == PAX_HRG)) {
+ int payload;
+
+ res = lnk_creat(arcn, &payload);
+ } else {
+ res = node_creat(arcn);
+ }
+ if (res < 0)
+ purg_lnk(arcn);
+ if (vflag && vfpart) {
+ (void)putc('\n', listf);
+ vfpart = 0;
+ }
+ continue;
+ }
+
+ /*
+ * have to copy a regular file to the destination directory.
+ * first open source file and then create the destination file
+ */
+ if ((fdsrc = open(arcn->org_name, O_RDONLY, 0)) < 0) {
+ syswarn(1, errno, "Unable to open %s to read",
+ arcn->org_name);
+ purg_lnk(arcn);
+ continue;
+ }
+ if ((fddest = file_creat(arcn, 0)) < 0) {
+ rdfile_close(arcn, &fdsrc);
+ purg_lnk(arcn);
+ continue;
+ }
+
+ /*
+ * copy source file data to the destination file
+ */
+ cp_file(arcn, fdsrc, fddest);
+ file_close(arcn, fddest);
+ rdfile_close(arcn, &fdsrc);
+
+ if (vflag && vfpart) {
+ (void)putc('\n', listf);
+ vfpart = 0;
+ }
+ }
+
+ /*
+ * restore directory modes and times as required; make sure all
+ * patterns were selected block off signals to avoid chance for
+ * multiple entry into the cleanup code.
+ */
+ (void)sigprocmask(SIG_BLOCK, &s_mask, NULL);
+ ar_close();
+ proc_dir();
+ ftree_chk();
+
+ return 0;
+}
+
+/*
+ * next_head()
+ * try to find a valid header in the archive. Uses format specific
+ * routines to extract the header and id the trailer. Trailers may be
+ * located within a valid header or in an invalid header (the location
+ * is format specific. The inhead field from the option table tells us
+ * where to look for the trailer).
+ * We keep reading (and resyncing) until we get enough contiguous data
+ * to check for a header. If we cannot find one, we shift by a byte
+ * add a new byte from the archive to the end of the buffer and try again.
+ * If we get a read error, we throw out what we have (as we must have
+ * contiguous data) and start over again.
+ * ASSUMED: headers fit within a BLKMULT header.
+ * Return:
+ * 0 if we got a header, -1 if we are unable to ever find another one
+ * (we reached the end of input, or we reached the limit on retries. see
+ * the specs for rd_wrbuf() for more details)
+ */
+
+static int
+next_head(ARCHD *arcn)
+{
+ int ret;
+ char *hdend;
+ int res;
+ int shftsz;
+ int hsz;
+ int in_resync = 0; /* set when we are in resync mode */
+ int cnt = 0; /* counter for trailer function */
+ int first = 1; /* on 1st read, EOF isn't premature. */
+
+ /*
+ * set up initial conditions, we want a whole frmt->hsz block as we
+ * have no data yet.
+ */
+ res = hsz = frmt->hsz;
+ hdend = hdbuf;
+ shftsz = hsz - 1;
+ for(;;) {
+ /*
+ * keep looping until we get a contiguous FULL buffer
+ * (frmt->hsz is the proper size)
+ */
+ for (;;) {
+ if ((ret = rd_wrbuf(hdend, res)) == res)
+ break;
+
+ /*
+ * If we read 0 bytes (EOF) from an archive when we
+ * expect to find a header, we have stepped upon
+ * an archive without the customary block of zeroes
+ * end marker. It's just stupid to error out on
+ * them, so exit gracefully.
+ */
+ if (first && ret == 0)
+ return -1;
+ first = 0;
+
+ /*
+ * some kind of archive read problem, try to resync the
+ * storage device, better give the user the bad news.
+ */
+ if ((ret == 0) || (rd_sync() < 0)) {
+ tty_warn(1,
+ "Premature end of file on archive read");
+ return -1;
+ }
+ if (!in_resync) {
+ if (act == APPND) {
+ tty_warn(1,
+ "Archive I/O error, cannot continue");
+ return -1;
+ }
+ tty_warn(1,
+ "Archive I/O error. Trying to recover.");
+ ++in_resync;
+ }
+
+ /*
+ * oh well, throw it all out and start over
+ */
+ res = hsz;
+ hdend = hdbuf;
+ }
+
+ /*
+ * ok we have a contiguous buffer of the right size. Call the
+ * format read routine. If this was not a valid header and this
+ * format stores trailers outside of the header, call the
+ * format specific trailer routine to check for a trailer. We
+ * have to watch out that we do not mis-identify file data or
+ * block padding as a header or trailer. Format specific
+ * trailer functions must NOT check for the trailer while we
+ * are running in resync mode. Some trailer functions may tell
+ * us that this block cannot contain a valid header either, so
+ * we then throw out the entire block and start over.
+ */
+ if ((*frmt->rd)(arcn, hdbuf) == 0)
+ break;
+
+ if (!frmt->inhead) {
+ /*
+ * this format has trailers outside of valid headers
+ */
+ if ((ret = (*frmt->trail)(hdbuf,in_resync,&cnt)) == 0){
+ /*
+ * valid trailer found, drain input as required
+ */
+ ar_drain();
+ return -1;
+ }
+
+ if (ret == 1) {
+ /*
+ * we are in resync and we were told to throw
+ * the whole block out because none of the
+ * bytes in this block can be used to form a
+ * valid header
+ */
+ res = hsz;
+ hdend = hdbuf;
+ continue;
+ }
+ }
+
+ /*
+ * Brute force section.
+ * not a valid header. We may be able to find a header yet. So
+ * we shift over by one byte, and set up to read one byte at a
+ * time from the archive and place it at the end of the buffer.
+ * We will keep moving byte at a time until we find a header or
+ * get a read error and have to start over.
+ */
+ if (!in_resync) {
+ if (act == APPND) {
+ tty_warn(1,
+ "Unable to append, archive header flaw");
+ return -1;
+ }
+ tty_warn(1,
+ "Invalid header, starting valid header search.");
+ ++in_resync;
+ }
+ memmove(hdbuf, hdbuf+1, shftsz);
+ res = 1;
+ hdend = hdbuf + shftsz;
+ }
+
+ /*
+ * ok got a valid header, check for trailer if format encodes it in the
+ * the header. NOTE: the parameters are different than trailer routines
+ * which encode trailers outside of the header!
+ */
+ if (frmt->inhead && ((*frmt->subtrail)(arcn) == 0)) {
+ /*
+ * valid trailer found, drain input as required
+ */
+ ar_drain();
+ return -1;
+ }
+
+ ++flcnt;
+ return 0;
+}
+
+/*
+ * get_arc()
+ * Figure out what format an archive is. Handles archive with flaws by
+ * brute force searches for a legal header in any supported format. The
+ * format id routines have to be careful to NOT mis-identify a format.
+ * ASSUMED: headers fit within a BLKMULT header.
+ * Return:
+ * 0 if archive found -1 otherwise
+ */
+
+static int
+get_arc(void)
+{
+ int i;
+ int hdsz = 0;
+ int res;
+ int minhd = BLKMULT;
+ char *hdend;
+ int notice = 0;
+
+ /*
+ * find the smallest header size in all archive formats and then set up
+ * to read the archive.
+ */
+ for (i = 0; ford[i] >= 0; ++i) {
+ if (fsub[ford[i]].hsz < minhd)
+ minhd = fsub[ford[i]].hsz;
+ }
+ if (rd_start() < 0)
+ return -1;
+ res = BLKMULT;
+ hdsz = 0;
+ hdend = hdbuf;
+ for(;;) {
+ for (;;) {
+ /*
+ * fill the buffer with at least the smallest header
+ */
+ i = rd_wrbuf(hdend, res);
+ if (i > 0)
+ hdsz += i;
+ if (hdsz >= minhd)
+ break;
+
+ /*
+ * if we cannot recover from a read error quit
+ */
+ if ((i == 0) || (rd_sync() < 0))
+ goto out;
+
+ /*
+ * when we get an error none of the data we already
+ * have can be used to create a legal header (we just
+ * got an error in the middle), so we throw it all out
+ * and refill the buffer with fresh data.
+ */
+ res = BLKMULT;
+ hdsz = 0;
+ hdend = hdbuf;
+ if (!notice) {
+ if (act == APPND)
+ return -1;
+ tty_warn(1,
+ "Cannot identify format. Searching...");
+ ++notice;
+ }
+ }
+
+ /*
+ * we have at least the size of the smallest header in any
+ * archive format. Look to see if we have a match. The array
+ * ford[] is used to specify the header id order to reduce the
+ * chance of incorrectly id'ing a valid header (some formats
+ * may be subsets of each other and the order would then be
+ * important).
+ */
+ for (i = 0; ford[i] >= 0; ++i) {
+ if ((*fsub[ford[i]].id)(hdbuf, hdsz) < 0)
+ continue;
+ frmt = &(fsub[ford[i]]);
+ /*
+ * yuck, to avoid slow special case code in the extract
+ * routines, just push this header back as if it was
+ * not seen. We have left extra space at start of the
+ * buffer for this purpose. This is a bit ugly, but
+ * adding all the special case code is far worse.
+ */
+ pback(hdbuf, hdsz);
+ return 0;
+ }
+
+ /*
+ * We have a flawed archive, no match. we start searching, but
+ * we never allow additions to flawed archives
+ */
+ if (!notice) {
+ if (act == APPND)
+ return -1;
+ tty_warn(1, "Cannot identify format. Searching...");
+ ++notice;
+ }
+
+ /*
+ * brute force search for a header that we can id.
+ * we shift through byte at a time. this is slow, but we cannot
+ * determine the nature of the flaw in the archive in a
+ * portable manner
+ */
+ if (--hdsz > 0) {
+ memmove(hdbuf, hdbuf+1, hdsz);
+ res = BLKMULT - hdsz;
+ hdend = hdbuf + hdsz;
+ } else {
+ res = BLKMULT;
+ hdend = hdbuf;
+ hdsz = 0;
+ }
+ }
+
+ out:
+ /*
+ * we cannot find a header, bow, apologize and quit
+ */
+ tty_warn(1, "Sorry, unable to determine archive format.");
+ return -1;
+}
diff --git a/bin/pax/buf_subs.c b/bin/pax/buf_subs.c
new file mode 100644
index 0000000..e4b97af
--- /dev/null
+++ b/bin/pax/buf_subs.c
@@ -0,0 +1,1022 @@
+/* $NetBSD: buf_subs.c,v 1.29 2018/03/19 03:11:39 msaitoh Exp $ */
+
+/*-
+ * Copyright (c) 1992 Keith Muller.
+ * Copyright (c) 1992, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Keith Muller of the University of California, San Diego.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#if HAVE_NBTOOL_CONFIG_H
+#include "nbtool_config.h"
+#endif
+
+#include <sys/cdefs.h>
+#if !defined(lint)
+#if 0
+static char sccsid[] = "@(#)buf_subs.c 8.2 (Berkeley) 4/18/94";
+#else
+__RCSID("$NetBSD: buf_subs.c,v 1.29 2018/03/19 03:11:39 msaitoh Exp $");
+#endif
+#endif /* not lint */
+
+#include <sys/types.h>
+#include <sys/time.h>
+#include <sys/stat.h>
+#include <sys/param.h>
+#include <stdio.h>
+#include <ctype.h>
+#include <errno.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <string.h>
+#include "pax.h"
+#include "extern.h"
+
+/*
+ * routines which implement archive and file buffering
+ */
+
+#define MINFBSZ 512 /* default block size for hole detect */
+#define MAXFLT 10 /* default media read error limit */
+
+/*
+ * Need to change bufmem to dynamic allocation when the upper
+ * limit on blocking size is removed (though that will violate pax spec)
+ * MAXBLK define and tests will also need to be updated.
+ */
+static char bufmem[MAXBLK+BLKMULT]; /* i/o buffer + pushback id space */
+static char *buf; /* normal start of i/o buffer */
+static char *bufend; /* end or last char in i/o buffer */
+static char *bufpt; /* read/write point in i/o buffer */
+int blksz = MAXBLK; /* block input/output size in bytes */
+int wrblksz; /* user spec output size in bytes */
+int maxflt = MAXFLT; /* MAX consecutive media errors */
+int rdblksz; /* first read blksize (tapes only) */
+off_t wrlimit; /* # of bytes written per archive vol */
+off_t wrcnt; /* # of bytes written on current vol */
+off_t rdcnt; /* # of bytes read on current vol */
+
+/*
+ * wr_start()
+ * set up the buffering system to operate in a write mode
+ * Return:
+ * 0 if ok, -1 if the user specified write block size violates pax spec
+ */
+
+int
+wr_start(void)
+{
+ buf = &(bufmem[BLKMULT]);
+ /*
+ * Check to make sure the write block size meets pax specs. If the user
+ * does not specify a blocksize, we use the format default blocksize.
+ * We must be picky on writes, so we do not allow the user to create an
+ * archive that might be hard to read elsewhere. If all ok, we then
+ * open the first archive volume
+ */
+ if (!wrblksz)
+ wrblksz = frmt->bsz;
+ if (wrblksz > MAXBLK) {
+ tty_warn(1, "Write block size of %d too large, maximum is: %d",
+ wrblksz, MAXBLK);
+ return -1;
+ }
+ if (wrblksz % BLKMULT) {
+ tty_warn(1, "Write block size of %d is not a %d byte multiple",
+ wrblksz, BLKMULT);
+ return -1;
+ }
+
+ /*
+ * we only allow wrblksz to be used with all archive operations
+ */
+ blksz = rdblksz = wrblksz;
+ if ((ar_open(arcname) < 0) && (ar_next() < 0))
+ return -1;
+ wrcnt = 0;
+ bufend = buf + wrblksz;
+ bufpt = buf;
+ return 0;
+}
+
+/*
+ * rd_start()
+ * set up buffering system to read an archive
+ * Return:
+ * 0 if ok, -1 otherwise
+ */
+
+int
+rd_start(void)
+{
+ /*
+ * leave space for the header pushback (see get_arc()). If we are
+ * going to append and user specified a write block size, check it
+ * right away
+ */
+ buf = &(bufmem[BLKMULT]);
+ if ((act == APPND) && wrblksz) {
+ if (wrblksz > MAXBLK) {
+ tty_warn(1,
+ "Write block size %d too large, maximum is: %d",
+ wrblksz, MAXBLK);
+ return -1;
+ }
+ if (wrblksz % BLKMULT) {
+ tty_warn(1,
+ "Write block size %d is not a %d byte multiple",
+ wrblksz, BLKMULT);
+ return -1;
+ }
+ }
+
+ /*
+ * open the archive
+ */
+ if ((ar_open(arcname) < 0) && (ar_next() < 0))
+ return -1;
+ bufend = buf + rdblksz;
+ bufpt = bufend;
+ rdcnt = 0;
+ return 0;
+}
+
+/*
+ * cp_start()
+ * set up buffer system for copying within the file system
+ */
+
+void
+cp_start(void)
+{
+ buf = &(bufmem[BLKMULT]);
+ rdblksz = blksz = MAXBLK;
+}
+
+/*
+ * appnd_start()
+ * Set up the buffering system to append new members to an archive that
+ * was just read. The last block(s) of an archive may contain a format
+ * specific trailer. To append a new member, this trailer has to be
+ * removed from the archive. The first byte of the trailer is replaced by
+ * the start of the header of the first file added to the archive. The
+ * format specific end read function tells us how many bytes to move
+ * backwards in the archive to be positioned BEFORE the trailer. Two
+ * different positions have to be adjusted, the O.S. file offset (e.g. the
+ * position of the tape head) and the write point within the data we have
+ * stored in the read (soon to become write) buffer. We may have to move
+ * back several records (the number depends on the size of the archive
+ * record and the size of the format trailer) to read up the record where
+ * the first byte of the trailer is recorded. Trailers may span (and
+ * overlap) record boundaries.
+ * We first calculate which record has the first byte of the trailer. We
+ * move the OS file offset back to the start of this record and read it
+ * up. We set the buffer write pointer to be at this byte (the byte where
+ * the trailer starts). We then move the OS file pointer back to the
+ * start of this record so a flush of this buffer will replace the record
+ * in the archive.
+ * A major problem is rewriting this last record. For archives stored
+ * on disk files, this is trivial. However, many devices are really picky
+ * about the conditions under which they will allow a write to occur.
+ * Often devices restrict the conditions where writes can be made,
+ * so it may not be feasable to append archives stored on all types of
+ * devices.
+ * Return:
+ * 0 for success, -1 for failure
+ */
+
+int
+appnd_start(off_t skcnt)
+{
+ int res;
+ off_t cnt;
+
+ if (exit_val != 0) {
+ tty_warn(0, "Cannot append to an archive that may have flaws.");
+ return -1;
+ }
+ /*
+ * if the user did not specify a write blocksize, inherit the size used
+ * in the last archive volume read. (If a is set we still use rdblksz
+ * until next volume, cannot shift sizes within a single volume).
+ */
+ if (!wrblksz)
+ wrblksz = blksz = rdblksz;
+ else
+ blksz = rdblksz;
+
+ /*
+ * make sure that this volume allows appends
+ */
+ if (ar_app_ok() < 0)
+ return -1;
+
+ /*
+ * Calculate bytes to move back and move in front of record where we
+ * need to start writing from. Remember we have to add in any padding
+ * that might be in the buffer after the trailer in the last block. We
+ * travel skcnt + padding ROUNDED UP to blksize.
+ */
+ skcnt += bufend - bufpt;
+ if ((cnt = (skcnt/blksz) * blksz) < skcnt)
+ cnt += blksz;
+ if (ar_rev((off_t)cnt) < 0)
+ goto out;
+
+ /*
+ * We may have gone too far if there is valid data in the block we are
+ * now in front of, read up the block and position the pointer after
+ * the valid data.
+ */
+ if ((cnt -= skcnt) > 0) {
+ /*
+ * watch out for stupid tape drives. ar_rev() will set rdblksz
+ * to be real physical blocksize so we must loop until we get
+ * the old rdblksz (now in blksz). If ar_rev() fouls up the
+ * determination of the physical block size, we will fail.
+ */
+ bufpt = buf;
+ bufend = buf + blksz;
+ while (bufpt < bufend) {
+ if ((res = ar_read(bufpt, rdblksz)) <= 0)
+ goto out;
+ bufpt += res;
+ }
+ if (ar_rev((off_t)(bufpt - buf)) < 0)
+ goto out;
+ bufpt = buf + cnt;
+ bufend = buf + blksz;
+ } else {
+ /*
+ * buffer is empty
+ */
+ bufend = buf + blksz;
+ bufpt = buf;
+ }
+ rdblksz = blksz;
+ rdcnt -= skcnt;
+ wrcnt = 0;
+
+ /*
+ * At this point we are ready to write. If the device requires special
+ * handling to write at a point were previously recorded data resides,
+ * that is handled in ar_set_wr(). From now on we operate under normal
+ * ARCHIVE mode (write) conditions
+ */
+ if (ar_set_wr() < 0)
+ return -1;
+ act = ARCHIVE;
+ return 0;
+
+ out:
+ tty_warn(1, "Unable to rewrite archive trailer, cannot append.");
+ return -1;
+}
+
+/*
+ * rd_sync()
+ * A read error occurred on this archive volume. Resync the buffer and
+ * try to reset the device (if possible) so we can continue to read. Keep
+ * trying to do this until we get a valid read, or we reach the limit on
+ * consecutive read faults (at which point we give up). The user can
+ * adjust the read error limit through a command line option.
+ * Returns:
+ * 0 on success, and -1 on failure
+ */
+
+int
+rd_sync(void)
+{
+ int errcnt = 0;
+ int res;
+
+ /*
+ * if the user says bail out on first fault, we are out of here...
+ */
+ if (maxflt == 0)
+ return -1;
+ if (act == APPND) {
+ tty_warn(1,
+ "Unable to append when there are archive read errors.");
+ return -1;
+ }
+
+ /*
+ * poke at device and try to get past media error
+ */
+ if (ar_rdsync() < 0) {
+ if (ar_next() < 0)
+ return -1;
+ else
+ rdcnt = 0;
+ }
+
+ for (;;) {
+ if ((res = ar_read(buf, blksz)) > 0) {
+ /*
+ * All right! got some data, fill that buffer
+ */
+ bufpt = buf;
+ bufend = buf + res;
+ rdcnt += res;
+ return 0;
+ }
+
+ /*
+ * Oh well, yet another failed read...
+ * if error limit reached, ditch. otherwise poke device to move past
+ * bad media and try again. if media is badly damaged, we ask
+ * the poor (and upset user at this point) for the next archive
+ * volume. remember the goal on reads is to get the most we
+ * can extract out of the archive.
+ */
+ if ((maxflt > 0) && (++errcnt > maxflt))
+ tty_warn(0,
+ "Archive read error limit (%d) reached",maxflt);
+ else if (ar_rdsync() == 0)
+ continue;
+ if (ar_next() < 0)
+ break;
+ rdcnt = 0;
+ errcnt = 0;
+ }
+ return -1;
+}
+
+/*
+ * pback()
+ * push the data used during the archive id phase back into the I/O
+ * buffer. This is required as we cannot be sure that the header does NOT
+ * overlap a block boundary (as in the case we are trying to recover a
+ * flawed archived). This was not designed to be used for any other
+ * purpose. (What software engineering, HA!)
+ * WARNING: do not even THINK of pback greater than BLKMULT, unless the
+ * pback space is increased.
+ */
+
+void
+pback(char *pt, int cnt)
+{
+ bufpt -= cnt;
+ memcpy(bufpt, pt, cnt);
+ return;
+}
+
+/*
+ * rd_skip()
+ * skip forward in the archive during an archive read. Used to get quickly
+ * past file data and padding for files the user did NOT select.
+ * Return:
+ * 0 if ok, -1 failure, and 1 when EOF on the archive volume was detected.
+ */
+
+int
+rd_skip(off_t skcnt)
+{
+ off_t res;
+ off_t cnt;
+ off_t skipped = 0;
+
+ /*
+ * consume what data we have in the buffer. If we have to move forward
+ * whole records, we call the low level skip function to see if we can
+ * move within the archive without doing the expensive reads on data we
+ * do not want.
+ */
+ if (skcnt == 0)
+ return 0;
+ res = MIN((bufend - bufpt), skcnt);
+ bufpt += res;
+ skcnt -= res;
+
+ /*
+ * if skcnt is now 0, then no additional i/o is needed
+ */
+ if (skcnt == 0)
+ return 0;
+
+ /*
+ * We have to read more, calculate complete and partial record reads
+ * based on rdblksz. we skip over "cnt" complete records
+ */
+ res = skcnt%rdblksz;
+ cnt = (skcnt/rdblksz) * rdblksz;
+
+ /*
+ * if the skip fails, we will have to resync. ar_fow will tell us
+ * how much it can skip over. We will have to read the rest.
+ */
+ if (ar_fow(cnt, &skipped) < 0)
+ return -1;
+ res += cnt - skipped;
+ rdcnt += skipped;
+
+ /*
+ * what is left we have to read (which may be the whole thing if
+ * ar_fow() told us the device can only read to skip records);
+ */
+ while (res > 0L) {
+ cnt = bufend - bufpt;
+ /*
+ * if the read fails, we will have to resync
+ */
+ if ((cnt <= 0) && ((cnt = buf_fill()) < 0))
+ return -1;
+ if (cnt == 0)
+ return 1;
+ cnt = MIN(cnt, res);
+ bufpt += cnt;
+ res -= cnt;
+ }
+ return 0;
+}
+
+/*
+ * wr_fin()
+ * flush out any data (and pad if required) the last block. We always pad
+ * with zero (even though we do not have to). Padding with 0 makes it a
+ * lot easier to recover if the archive is damaged. zero paddding SHOULD
+ * BE a requirement....
+ */
+
+void
+wr_fin(void)
+{
+ if (bufpt > buf) {
+ memset(bufpt, 0, bufend - bufpt);
+ bufpt = bufend;
+ (void)buf_flush(blksz);
+ }
+}
+
+/*
+ * wr_rdbuf()
+ * fill the write buffer from data passed to it in a buffer (usually used
+ * by format specific write routines to pass a file header). On failure we
+ * punt. We do not allow the user to continue to write flawed archives.
+ * We assume these headers are not very large (the memory copy we use is
+ * a bit expensive).
+ * Return:
+ * 0 if buffer was filled ok, -1 o.w. (buffer flush failure)
+ */
+
+int
+wr_rdbuf(char *out, int outcnt)
+{
+ int cnt;
+
+ /*
+ * while there is data to copy into the write buffer. when the
+ * write buffer fills, flush it to the archive and continue
+ */
+ while (outcnt > 0) {
+ cnt = bufend - bufpt;
+ if ((cnt <= 0) && ((cnt = buf_flush(blksz)) < 0))
+ return -1;
+ /*
+ * only move what we have space for
+ */
+ cnt = MIN(cnt, outcnt);
+ memcpy(bufpt, out, cnt);
+ bufpt += cnt;
+ out += cnt;
+ outcnt -= cnt;
+ }
+ return 0;
+}
+
+/*
+ * rd_wrbuf()
+ * copy from the read buffer into a supplied buffer a specified number of
+ * bytes. If the read buffer is empty fill it and continue to copy.
+ * usually used to obtain a file header for processing by a format
+ * specific read routine.
+ * Return
+ * number of bytes copied to the buffer, 0 indicates EOF on archive volume,
+ * -1 is a read error
+ */
+
+int
+rd_wrbuf(char *in, int cpcnt)
+{
+ int res;
+ int cnt;
+ int incnt = cpcnt;
+
+ /*
+ * loop until we fill the buffer with the requested number of bytes
+ */
+ while (incnt > 0) {
+ cnt = bufend - bufpt;
+ if ((cnt <= 0) && ((cnt = buf_fill()) <= 0)) {
+ /*
+ * read error, return what we got (or the error if
+ * no data was copied). The caller must know that an
+ * error occurred and has the best knowledge what to
+ * do with it
+ */
+ if ((res = cpcnt - incnt) > 0)
+ return res;
+ return cnt;
+ }
+
+ /*
+ * calculate how much data to copy based on whats left and
+ * state of buffer
+ */
+ cnt = MIN(cnt, incnt);
+ memcpy(in, bufpt, cnt);
+ bufpt += cnt;
+ incnt -= cnt;
+ in += cnt;
+ }
+ return cpcnt;
+}
+
+/*
+ * wr_skip()
+ * skip forward during a write. In other words add padding to the file.
+ * we add zero filled padding as it makes flawed archives much easier to
+ * recover from. the caller tells us how many bytes of padding to add
+ * This routine was not designed to add HUGE amount of padding, just small
+ * amounts (a few 512 byte blocks at most)
+ * Return:
+ * 0 if ok, -1 if there was a buf_flush failure
+ */
+
+int
+wr_skip(off_t skcnt)
+{
+ int cnt;
+
+ /*
+ * loop while there is more padding to add
+ */
+ while (skcnt > 0L) {
+ cnt = bufend - bufpt;
+ if ((cnt <= 0) && ((cnt = buf_flush(blksz)) < 0))
+ return -1;
+ cnt = MIN(cnt, skcnt);
+ memset(bufpt, 0, cnt);
+ bufpt += cnt;
+ skcnt -= cnt;
+ }
+ return 0;
+}
+
+/*
+ * wr_rdfile()
+ * fill write buffer with the contents of a file. We are passed an open
+ * file descriptor to the file an the archive structure that describes the
+ * file we are storing. The variable "left" is modified to contain the
+ * number of bytes of the file we were NOT able to write to the archive.
+ * it is important that we always write EXACTLY the number of bytes that
+ * the format specific write routine told us to. The file can also get
+ * bigger, so reading to the end of file would create an improper archive,
+ * we just detect this case and warn the user. We never create a bad
+ * archive if we can avoid it. Of course trying to archive files that are
+ * active is asking for trouble. It we fail, we pass back how much we
+ * could NOT copy and let the caller deal with it.
+ * Return:
+ * 0 ok, -1 if archive write failure. a short read of the file returns a
+ * 0, but "left" is set to be greater than zero.
+ */
+
+int
+wr_rdfile(ARCHD *arcn, int ifd, off_t *left)
+{
+ int cnt;
+ int res = 0;
+ off_t size = arcn->sb.st_size;
+ struct stat origsb, sb;
+
+ /*
+ * by default, remember the previously obtained stat information
+ * (in arcn->sb) for comparing the mtime after reading.
+ * if Mflag is set, use the actual mtime instead.
+ */
+ origsb = arcn->sb;
+ if (Mflag && (fstat(ifd, &origsb) < 0))
+ syswarn(1, errno, "Failed stat on %s", arcn->org_name);
+
+ /*
+ * while there are more bytes to write
+ */
+ while (size > 0L) {
+ cnt = bufend - bufpt;
+ if ((cnt <= 0) && ((cnt = buf_flush(blksz)) < 0)) {
+ *left = size;
+ return -1;
+ }
+ cnt = MIN(cnt, size);
+ if ((res = read_with_restart(ifd, bufpt, cnt)) <= 0)
+ break;
+ size -= res;
+ bufpt += res;
+ }
+
+ /*
+ * better check the file did not change during this operation
+ * or the file read failed.
+ */
+ if (res < 0)
+ syswarn(1, errno, "Read fault on %s", arcn->org_name);
+ else if (size != 0L)
+ tty_warn(1, "File changed size during read %s", arcn->org_name);
+ else if (fstat(ifd, &sb) < 0)
+ syswarn(1, errno, "Failed stat on %s", arcn->org_name);
+ else if (origsb.st_mtime != sb.st_mtime)
+ tty_warn(1, "File %s was modified during copy to archive",
+ arcn->org_name);
+ *left = size;
+ return 0;
+}
+
+/*
+ * rd_wrfile()
+ * extract the contents of a file from the archive. If we are unable to
+ * extract the entire file (due to failure to write the file) we return
+ * the numbers of bytes we did NOT process. This way the caller knows how
+ * many bytes to skip past to find the next archive header. If the failure
+ * was due to an archive read, we will catch that when we try to skip. If
+ * the format supplies a file data crc value, we calculate the actual crc
+ * so that it can be compared to the value stored in the header
+ * NOTE:
+ * We call a special function to write the file. This function attempts to
+ * restore file holes (blocks of zeros) into the file. When files are
+ * sparse this saves space, and is a LOT faster. For non sparse files
+ * the performance hit is small. As of this writing, no archive supports
+ * information on where the file holes are.
+ * Return:
+ * 0 ok, -1 if archive read failure. if we cannot write the entire file,
+ * we return a 0 but "left" is set to be the amount unwritten
+ */
+
+int
+rd_wrfile(ARCHD *arcn, int ofd, off_t *left)
+{
+ int cnt = 0;
+ off_t size = arcn->sb.st_size;
+ int res = 0;
+ char *fnm = arcn->name;
+ int isem = 1;
+ int rem;
+ int sz = MINFBSZ;
+ struct stat sb;
+ u_long crc = 0L;
+
+ /*
+ * pass the blocksize of the file being written to the write routine,
+ * if the size is zero, use the default MINFBSZ
+ */
+ if (ofd < 0)
+ sz = PAXPATHLEN+1;
+ else if (fstat(ofd, &sb) == 0) {
+ if (sb.st_blksize > 0)
+ sz = (int)sb.st_blksize;
+ } else
+ syswarn(0, errno,
+ "Unable to obtain block size for file %s", fnm);
+ rem = sz;
+ *left = 0L;
+
+ /*
+ * Copy the archive to the file the number of bytes specified. We have
+ * to assume that we want to recover file holes as none of the archive
+ * formats can record the location of file holes.
+ */
+ while (size > 0L) {
+ cnt = bufend - bufpt;
+ /*
+ * if we get a read error, we do not want to skip, as we may
+ * miss a header, so we do not set left, but if we get a write
+ * error, we do want to skip over the unprocessed data.
+ */
+ if ((cnt <= 0) && ((cnt = buf_fill()) <= 0))
+ break;
+ cnt = MIN(cnt, size);
+ if ((res = file_write(ofd,bufpt,cnt,&rem,&isem,sz,fnm)) <= 0) {
+ *left = size;
+ break;
+ }
+
+ if (docrc) {
+ /*
+ * update the actual crc value
+ */
+ cnt = res;
+ while (--cnt >= 0)
+ crc += *bufpt++ & 0xff;
+ } else
+ bufpt += res;
+ size -= res;
+ }
+
+ /*
+ * if the last block has a file hole (all zero), we must make sure this
+ * gets updated in the file. We force the last block of zeros to be
+ * written. just closing with the file offset moved forward may not put
+ * a hole at the end of the file.
+ */
+ if (ofd >= 0 && isem && (arcn->sb.st_size > 0L))
+ file_flush(ofd, fnm, isem);
+
+ /*
+ * if we failed from archive read, we do not want to skip
+ */
+ if ((size > 0L) && (*left == 0L))
+ return -1;
+
+ /*
+ * some formats record a crc on file data. If so, then we compare the
+ * calculated crc to the crc stored in the archive
+ */
+ if (docrc && (size == 0L) && (arcn->crc != crc))
+ tty_warn(1,"Actual crc does not match expected crc %s",
+ arcn->name);
+ return 0;
+}
+
+/*
+ * cp_file()
+ * copy the contents of one file to another. used during -rw phase of pax
+ * just as in rd_wrfile() we use a special write function to write the
+ * destination file so we can properly copy files with holes.
+ */
+
+void
+cp_file(ARCHD *arcn, int fd1, int fd2)
+{
+ int cnt;
+ off_t cpcnt = 0L;
+ int res = 0;
+ char *fnm = arcn->name;
+ int no_hole = 0;
+ int isem = 1;
+ int rem;
+ int sz = MINFBSZ;
+ struct stat sb, origsb;
+
+ /*
+ * check for holes in the source file. If none, we will use regular
+ * write instead of file write.
+ */
+ if (((off_t)(arcn->sb.st_blocks * BLKMULT)) >= arcn->sb.st_size)
+ ++no_hole;
+
+ /*
+ * by default, remember the previously obtained stat information
+ * (in arcn->sb) for comparing the mtime after reading.
+ * if Mflag is set, use the actual mtime instead.
+ */
+ origsb = arcn->sb;
+ if (Mflag && (fstat(fd1, &origsb) < 0))
+ syswarn(1, errno, "Failed stat on %s", arcn->org_name);
+
+ /*
+ * pass the blocksize of the file being written to the write routine,
+ * if the size is zero, use the default MINFBSZ
+ */
+ if (fstat(fd2, &sb) == 0) {
+ if (sb.st_blksize > 0)
+ sz = sb.st_blksize;
+ } else
+ syswarn(0, errno,
+ "Unable to obtain block size for file %s", fnm);
+ rem = sz;
+
+ /*
+ * read the source file and copy to destination file until EOF
+ */
+ for(;;) {
+ if ((cnt = read_with_restart(fd1, buf, blksz)) <= 0)
+ break;
+ if (no_hole)
+ res = xwrite(fd2, buf, cnt);
+ else
+ res = file_write(fd2, buf, cnt, &rem, &isem, sz, fnm);
+ if (res != cnt)
+ break;
+ cpcnt += cnt;
+ }
+
+ /*
+ * check to make sure the copy is valid.
+ */
+ if (res < 0)
+ syswarn(1, errno, "Failed write during copy of %s to %s",
+ arcn->org_name, arcn->name);
+ else if (cpcnt != arcn->sb.st_size)
+ tty_warn(1, "File %s changed size during copy to %s",
+ arcn->org_name, arcn->name);
+ else if (fstat(fd1, &sb) < 0)
+ syswarn(1, errno, "Failed stat of %s", arcn->org_name);
+ else if (origsb.st_mtime != sb.st_mtime)
+ tty_warn(1, "File %s was modified during copy to %s",
+ arcn->org_name, arcn->name);
+
+ /*
+ * if the last block has a file hole (all zero), we must make sure this
+ * gets updated in the file. We force the last block of zeros to be
+ * written. just closing with the file offset moved forward may not put
+ * a hole at the end of the file.
+ */
+ if (!no_hole && isem && (arcn->sb.st_size > 0L))
+ file_flush(fd2, fnm, isem);
+ return;
+}
+
+/*
+ * buf_fill()
+ * fill the read buffer with the next record (or what we can get) from
+ * the archive volume.
+ * Return:
+ * Number of bytes of data in the read buffer, -1 for read error, and
+ * 0 when finished (user specified termination in ar_next()).
+ */
+
+int
+buf_fill(void)
+{
+ int cnt;
+ static int fini = 0;
+
+ if (fini)
+ return 0;
+
+ for(;;) {
+ /*
+ * try to fill the buffer. on error the next archive volume is
+ * opened and we try again.
+ */
+ if ((cnt = ar_read(buf, blksz)) > 0) {
+ bufpt = buf;
+ bufend = buf + cnt;
+ rdcnt += cnt;
+ return cnt;
+ }
+
+ /*
+ * errors require resync, EOF goes to next archive
+ * but in case we have not determined yet the format,
+ * this means that we have a very short file, so we
+ * are done again.
+ */
+ if (cnt < 0)
+ break;
+ if (frmt == NULL || ar_next() < 0) {
+ fini = 1;
+ return 0;
+ }
+ rdcnt = 0;
+ }
+ exit_val = 1;
+ return -1;
+}
+
+/*
+ * buf_flush()
+ * force the write buffer to the archive. We are passed the number of
+ * bytes in the buffer at the point of the flush. When we change archives
+ * the record size might change. (either larger or smaller).
+ * Return:
+ * 0 if all is ok, -1 when a write error occurs.
+ */
+
+int
+buf_flush(int bufcnt)
+{
+ int cnt;
+ int push = 0;
+ int totcnt = 0;
+
+ /*
+ * if we have reached the user specified byte count for each archive
+ * volume, prompt for the next volume. (The non-standard -R flag).
+ * NOTE: If the wrlimit is smaller than wrcnt, we will always write
+ * at least one record. We always round limit UP to next blocksize.
+ */
+ if ((wrlimit > 0) && (wrcnt > wrlimit)) {
+ tty_warn(0,
+ "User specified archive volume byte limit reached.");
+ if (ar_next() < 0) {
+ wrcnt = 0;
+ exit_val = 1;
+ return -1;
+ }
+ wrcnt = 0;
+
+ /*
+ * The new archive volume might have changed the size of the
+ * write blocksize. if so we figure out if we need to write
+ * (one or more times), or if there is now free space left in
+ * the buffer (it is no longer full). bufcnt has the number of
+ * bytes in the buffer, (the blocksize, at the point we were
+ * CALLED). Push has the amount of "extra" data in the buffer
+ * if the block size has shrunk from a volume change.
+ */
+ bufend = buf + blksz;
+ if (blksz > bufcnt)
+ return 0;
+ if (blksz < bufcnt)
+ push = bufcnt - blksz;
+ }
+
+ /*
+ * We have enough data to write at least one archive block
+ */
+ for (;;) {
+ /*
+ * write a block and check if it all went out ok
+ */
+ cnt = ar_write(buf, blksz);
+ if (cnt == blksz) {
+ /*
+ * the write went ok
+ */
+ wrcnt += cnt;
+ totcnt += cnt;
+ if (push > 0) {
+ /* we have extra data to push to the front.
+ * check for more than 1 block of push, and if
+ * so we loop back to write again
+ */
+ memcpy(buf, bufend, push);
+ bufpt = buf + push;
+ if (push >= blksz) {
+ push -= blksz;
+ continue;
+ }
+ } else
+ bufpt = buf;
+ return totcnt;
+ } else if (cnt > 0) {
+ /*
+ * Oh drat we got a partial write!
+ * if format doesnt care about alignment let it go,
+ * we warned the user in ar_write().... but this means
+ * the last record on this volume violates pax spec....
+ */
+ totcnt += cnt;
+ wrcnt += cnt;
+ bufpt = buf + cnt;
+ cnt = bufcnt - cnt;
+ memcpy(buf, bufpt, cnt);
+ bufpt = buf + cnt;
+ if (!frmt->blkalgn || ((cnt % frmt->blkalgn) == 0))
+ return totcnt;
+ break;
+ }
+
+ /*
+ * All done, go to next archive
+ */
+ wrcnt = 0;
+ if (ar_next() < 0)
+ break;
+
+ /*
+ * The new archive volume might also have changed the block
+ * size. if so, figure out if we have too much or too little
+ * data for using the new block size
+ */
+ bufend = buf + blksz;
+ if (blksz > bufcnt)
+ return 0;
+ if (blksz < bufcnt)
+ push = bufcnt - blksz;
+ }
+
+ /*
+ * write failed, stop pax. we must not create a bad archive!
+ */
+ exit_val = 1;
+ return -1;
+}
diff --git a/bin/pax/cpio.1 b/bin/pax/cpio.1
new file mode 100644
index 0000000..45f5854
--- /dev/null
+++ b/bin/pax/cpio.1
@@ -0,0 +1,307 @@
+.\" $NetBSD: cpio.1,v 1.15 2017/07/03 21:33:23 wiz Exp $
+.\"
+.\" Copyright (c) 1997 SigmaSoft, Th. Lockert
+.\" All rights reserved.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\" notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must reproduce the above copyright
+.\" notice, this list of conditions and the following disclaimer in the
+.\" documentation and/or other materials provided with the distribution.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+.\" IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+.\" OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+.\" IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+.\" INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+.\" NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+.\" DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+.\" THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+.\" (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+.\" THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+.\"
+.\" OpenBSD: cpio.1,v 1.14 2000/11/10 17:52:02 aaron Exp
+.\"
+.Dd June 18, 2011
+.Dt CPIO 1
+.Os
+.Sh NAME
+.Nm cpio
+.Nd copy file archives in and out
+.Sh SYNOPSIS
+.Nm cpio
+.Fl o
+.Op Fl AaBcLvZz
+.Op Fl C Ar bytes
+.Op Fl F Ar archive
+.Op Fl H Ar format
+.Op Fl O Ar archive
+.Ar "< name-list"
+.Op Ar "> archive"
+.Nm cpio
+.Fl i
+.Op Fl 6BbcdfmrSstuvZz
+.Op Fl C Ar bytes
+.Op Fl E Ar file
+.Op Fl F Ar archive
+.Op Fl H Ar format
+.Op Fl I Ar archive
+.Op Ar "pattern ..."
+.Op Ar "< archive"
+.Nm cpio
+.Fl p
+.Op Fl adLlmuv
+.Ar destination-directory
+.Ar "< name-list"
+.Sh DESCRIPTION
+The
+.Nm
+command copies files to and from a
+.Nm
+archive.
+If the archive is of the form:
+.Ar [[user@]host:]file
+then the archive will be processed using
+.Xr rmt 8 .
+.Pp
+The options are as follows:
+.Bl -tag -width Ds
+.It Fl o , Fl Fl create
+Create an archive.
+Reads the list of files to store in the
+archive from standard input, and writes the archive on standard
+output.
+.Bl -tag -width Ds
+.It Fl a , Fl Fl reset-access-time
+Reset the access times on files that have been copied to the
+archive.
+.It Fl A , Fl Fl append
+Append to the specified archive.
+.It Fl B
+Set block size of output to 5120 bytes.
+.It Fl c
+Use ASCII format for
+.Nm
+header for portability.
+.It Fl C Ar bytes
+Set the block size of output to
+.Ar bytes .
+.It Fl F Ar archive
+.It Fl O Ar archive
+Use the specified file name as the archive to write to.
+.It Fl H Ar format
+Write the archive in the specified format.
+Recognized formats are:
+.Pp
+.Bl -tag -width sv4cpio -compact
+.It Ar bcpio
+Old binary
+.Nm
+format.
+.It Ar cpio
+Old octal character
+.Nm
+format.
+.It Ar sv4cpio
+SVR4 hex
+.Nm
+format.
+.It Ar tar
+Old tar format.
+.It Ar ustar
+POSIX ustar format.
+.El
+.It Fl L
+Follow symbolic links.
+.It Fl v
+Be verbose about operations.
+List filenames as they are written to the archive.
+.It Fl Fl xz
+Compress/decompress archive using
+.Xr xz 1
+format.
+.It Fl Z
+Compress archive using
+.Xr compress 1
+format.
+.It Fl z
+Compress/decompress archive using
+.Xr gzip 1
+format.
+.El
+.It Fl i , Fl Fl extract
+Restore files from an archive.
+Reads the archive file from
+standard input and extracts files matching the
+.Ar patterns
+that were specified on the command line.
+.Bl -tag -width Ds
+.It Fl b
+Do byte and word swapping after reading in data from the
+archive, for restoring archives created on systems with
+a different byte order.
+.It Fl B
+Set the block size of the archive being read to 5120 bytes.
+.It Fl c
+Expect the archive headers to be in ASCII format.
+.It Fl C Ar bytes
+Read archive written with a block size of
+.Ar bytes .
+.It Fl d , Fl Fl make-directories
+Create any intermediate directories as needed during
+restore.
+.It Fl E Ar file , Fl Fl pattern-file Ar file
+Read list of file name patterns to extract or list from
+.Ar file .
+.It Fl f , Fl Fl nonmatching
+Restore all files except those matching the
+.Ar patterns
+given on the command line.
+.It Fl F Ar archive , Fl Fl file Ar archive
+.It Fl I Ar archive
+Use the specified file as the input for the archive.
+.It Fl H Ar format , Fl Fl format Ar format
+Read an archive of the specified format.
+Recognized formats are:
+.Pp
+.Bl -tag -width sv4cpio -compact
+.It Ar bcpio
+Old binary
+.Nm
+format.
+.It Ar cpio
+Old octal character
+.Nm
+format.
+.It Ar sv4cpio
+SVR4 hex
+.Nm
+format.
+.It Ar tar
+Old tar format.
+.It Ar ustar
+POSIX ustar format.
+.El
+.It Fl m
+Restore modification times on files.
+.It Fl r , Fl Fl rename
+Rename restored files interactively.
+.It Fl s
+Swap bytes after reading data from the archive.
+.It Fl S , Fl Fl swap-halfwords
+Swap words after reading data from the archive.
+.It Fl t , Fl Fl list
+Only list the contents of the archive, no files or
+directories will be created.
+.It Fl u , Fl Fl unconditional
+Overwrite files even when the file in the archive is
+older than the one that will be overwritten.
+.It Fl v , Fl Fl verbose
+Be verbose about operations.
+List filenames as they are copied in from the archive.
+.It Fl z
+Uncompress archive using
+.Xr gzip 1
+format.
+.It Fl Z
+Uncompress archive using
+.Xr compress 1
+format.
+.It Fl 6
+Process old-style
+.Nm
+format archives.
+.El
+.It Fl p , Fl Fl pass-through
+Copy files from one location to another in a single pass.
+The list of files to copy are read from standard input and
+written out to a directory relative to the specified
+.Ar directory
+argument.
+.Bl -tag -width Ds
+.It Fl a
+Reset the access times on files that have been copied.
+.It Fl d
+Create any intermediate directories as needed to write
+the files at the new location.
+.It Fl l , Fl Fl link
+When possible, link files rather than creating an
+extra copy.
+.It Fl L , Fl Fl dereference
+Follow symbolic links.
+.It Fl m , Fl Fl preserve-modification-time
+Restore modification times on files.
+.It Fl u , Fl Fl unconditional
+Overwrite files even when the original file being copied is
+older than the one that will be overwritten.
+.It Fl v , Fl Fl verbose
+Be verbose about operations.
+List filenames as they are copied.
+.It Fl Fl force-local
+Do not interpret filenames that contain a
+.Sq \&:
+as remote files.
+.It Fl Fl insecure
+Normally
+.Nm
+ignores filenames that contain
+.Dq ..
+as a path component.
+With this option, files that contain
+.Dq ..
+can be processed.
+.El
+.El
+.Sh EXIT STATUS
+.Nm
+will exit with one of the following values:
+.Bl -tag -width 2n
+.It 0
+All files were processed successfully.
+.It 1
+An error occurred.
+.El
+.Pp
+Whenever
+.Nm
+cannot create a file or a link when extracting an archive or cannot
+find a file while writing an archive, or cannot preserve the user
+ID, group ID, file mode, or access and modification times when the
+.Fl p
+option is specified, a diagnostic message is written to standard
+error and a non-zero exit value will be returned, but processing
+will continue.
+In the case where
+.Nm
+cannot create a link to a file,
+.Nm
+will not create a second copy of the file.
+.Pp
+If the extraction of a file from an archive is prematurely terminated
+by a signal or error,
+.Nm
+may have only partially extracted the file the user wanted.
+Additionally, the file modes of extracted files and directories may
+have incorrect file bits, and the modification and access times may
+be wrong.
+.Pp
+If the creation of an archive is prematurely terminated by a signal
+or error,
+.Nm
+may have only partially created the archive which may violate the
+specific archive format specification.
+.Sh SEE ALSO
+.Xr pax 1 ,
+.Xr tar 1
+.Sh AUTHORS
+.An Keith Muller
+at the University of California, San Diego.
+.Sh BUGS
+The
+.Fl s
+and
+.Fl S
+options are currently not implemented.
diff --git a/bin/pax/cpio.c b/bin/pax/cpio.c
new file mode 100644
index 0000000..1a38ba2
--- /dev/null
+++ b/bin/pax/cpio.c
@@ -0,0 +1,1134 @@
+/* $NetBSD: cpio.c,v 1.22 2012/08/09 08:09:21 christos Exp $ */
+
+/*-
+ * Copyright (c) 1992 Keith Muller.
+ * Copyright (c) 1992, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Keith Muller of the University of California, San Diego.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#if HAVE_NBTOOL_CONFIG_H
+#include "nbtool_config.h"
+#endif
+
+#include <sys/cdefs.h>
+#if !defined(lint)
+#if 0
+static char sccsid[] = "@(#)cpio.c 8.1 (Berkeley) 5/31/93";
+#else
+__RCSID("$NetBSD: cpio.c,v 1.22 2012/08/09 08:09:21 christos Exp $");
+#endif
+#endif /* not lint */
+
+#include <sys/types.h>
+#include <sys/time.h>
+#include <sys/stat.h>
+#include <sys/param.h>
+#include <string.h>
+#include <ctype.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include "pax.h"
+#include "cpio.h"
+#include "extern.h"
+
+static int rd_nm(ARCHD *, int);
+static int rd_ln_nm(ARCHD *);
+static int com_rd(ARCHD *);
+
+/*
+ * Routines which support the different cpio versions
+ */
+
+int cpio_swp_head; /* binary cpio header byte swap */
+
+/*
+ * Routines common to all versions of cpio
+ */
+
+/*
+ * cpio_strd()
+ * Fire up the hard link detection code
+ * Return:
+ * 0 if ok -1 otherwise (the return values of lnk_start())
+ */
+
+int
+cpio_strd(void)
+{
+ return lnk_start();
+}
+
+/*
+ * cpio_subtrail()
+ * Called to determine if a header block is a valid trailer. We are
+ * passed the block, the in_sync flag (which tells us we are in resync
+ * mode; looking for a valid header), and cnt (which starts at zero)
+ * which is used to count the number of empty blocks we have seen so far.
+ * Return:
+ * 0 if a valid trailer, -1 if not a valid trailer,
+ */
+
+int
+cpio_subtrail(ARCHD *arcn)
+{
+ /*
+ * look for trailer id in file we are about to process
+ */
+ if ((strcmp(arcn->name, TRAILER) == 0) && (arcn->sb.st_size == 0))
+ return 0;
+ return -1;
+}
+
+/*
+ * com_rd()
+ * operations common to all cpio read functions.
+ * Return:
+ * 0
+ */
+
+static int
+com_rd(ARCHD *arcn)
+{
+ arcn->skip = 0;
+ arcn->pat = NULL;
+ arcn->org_name = arcn->name;
+ switch(arcn->sb.st_mode & C_IFMT) {
+ case C_ISFIFO:
+ arcn->type = PAX_FIF;
+ break;
+ case C_ISDIR:
+ arcn->type = PAX_DIR;
+ break;
+ case C_ISBLK:
+ arcn->type = PAX_BLK;
+ break;
+ case C_ISCHR:
+ arcn->type = PAX_CHR;
+ break;
+ case C_ISLNK:
+ arcn->type = PAX_SLK;
+ break;
+ case C_ISOCK:
+ arcn->type = PAX_SCK;
+ break;
+ case C_ISCTG:
+ case C_ISREG:
+ default:
+ /*
+ * we have file data, set up skip (pad is set in the format
+ * specific sections)
+ */
+ arcn->sb.st_mode = (arcn->sb.st_mode & 0xfff) | C_ISREG;
+ arcn->type = PAX_REG;
+ arcn->skip = arcn->sb.st_size;
+ break;
+ }
+ if (chk_lnk(arcn) < 0)
+ return -1;
+ return 0;
+}
+
+/*
+ * cpio_end_wr()
+ * write the special file with the name trailer in the proper format
+ * Return:
+ * result of the write of the trailer from the cpio specific write func
+ */
+
+int
+cpio_endwr(void)
+{
+ ARCHD last;
+
+ /*
+ * create a trailer request and call the proper format write function
+ */
+ memset(&last, 0, sizeof(last));
+ last.nlen = sizeof(TRAILER) - 1;
+ last.type = PAX_REG;
+ last.sb.st_nlink = 1;
+ (void)strcpy(last.name, TRAILER);
+ return (*frmt->wr)(&last);
+}
+
+/*
+ * rd_nam()
+ * read in the file name which follows the cpio header
+ * Return:
+ * 0 if ok, -1 otherwise
+ */
+
+static int
+rd_nm(ARCHD *arcn, int nsz)
+{
+ /*
+ * do not even try bogus values
+ */
+ if ((nsz <= 0) || (nsz > (int)sizeof(arcn->name))) {
+ tty_warn(1, "Cpio file name length %d is out of range", nsz);
+ return -1;
+ }
+
+ /*
+ * read the name and make sure it is not empty and is \0 terminated
+ */
+ if ((rd_wrbuf(arcn->name,nsz) != nsz) || (arcn->name[nsz-1] != '\0') ||
+ (arcn->name[0] == '\0')) {
+ tty_warn(1, "Cpio file name in header is corrupted");
+ return -1;
+ }
+ return 0;
+}
+
+/*
+ * rd_ln_nm()
+ * read in the link name for a file with links. The link name is stored
+ * like file data (and is NOT \0 terminated!)
+ * Return:
+ * 0 if ok, -1 otherwise
+ */
+
+static int
+rd_ln_nm(ARCHD *arcn)
+{
+ /*
+ * check the length specified for bogus values
+ */
+ if ((arcn->sb.st_size == 0) ||
+ (arcn->sb.st_size >= (off_t)sizeof(arcn->ln_name))) {
+ tty_warn(1, "Cpio link name length is invalid: " OFFT_F,
+ (OFFT_T) arcn->sb.st_size);
+ return -1;
+ }
+
+ /*
+ * read in the link name and \0 terminate it
+ */
+ if (rd_wrbuf(arcn->ln_name, (int)arcn->sb.st_size) !=
+ (int)arcn->sb.st_size) {
+ tty_warn(1, "Cpio link name read error");
+ return -1;
+ }
+ arcn->ln_nlen = arcn->sb.st_size;
+ arcn->ln_name[arcn->ln_nlen] = '\0';
+
+ /*
+ * watch out for those empty link names
+ */
+ if (arcn->ln_name[0] == '\0') {
+ tty_warn(1, "Cpio link name is corrupt");
+ return -1;
+ }
+ return 0;
+}
+
+/*
+ * Routines common to the extended byte oriented cpio format
+ */
+
+/*
+ * cpio_id()
+ * determine if a block given to us is a valid extended byte oriented
+ * cpio header
+ * Return:
+ * 0 if a valid header, -1 otherwise
+ */
+
+int
+cpio_id(char *blk, int size)
+{
+ if ((size < (int)sizeof(HD_CPIO)) ||
+ (strncmp(blk, AMAGIC, sizeof(AMAGIC) - 1) != 0))
+ return -1;
+ return 0;
+}
+
+/*
+ * cpio_rd()
+ * determine if a buffer is a byte oriented extended cpio archive entry.
+ * convert and store the values in the ARCHD parameter.
+ * Return:
+ * 0 if a valid header, -1 otherwise.
+ */
+
+int
+cpio_rd(ARCHD *arcn, char *buf)
+{
+ int nsz;
+ HD_CPIO *hd;
+
+ /*
+ * check that this is a valid header, if not return -1
+ */
+ if (cpio_id(buf, sizeof(HD_CPIO)) < 0)
+ return -1;
+ hd = (HD_CPIO *)buf;
+
+ /*
+ * byte oriented cpio (posix) does not have padding! extract the octal
+ * ascii fields from the header
+ */
+ arcn->pad = 0L;
+ arcn->sb.st_dev = (dev_t)asc_u32(hd->c_dev, sizeof(hd->c_dev), OCT);
+ arcn->sb.st_ino = (ino_t)asc_u32(hd->c_ino, sizeof(hd->c_ino), OCT);
+ arcn->sb.st_mode = (mode_t)asc_u32(hd->c_mode, sizeof(hd->c_mode), OCT);
+ arcn->sb.st_uid = (uid_t)asc_u32(hd->c_uid, sizeof(hd->c_uid), OCT);
+ arcn->sb.st_gid = (gid_t)asc_u32(hd->c_gid, sizeof(hd->c_gid), OCT);
+ arcn->sb.st_nlink = (nlink_t)asc_u32(hd->c_nlink, sizeof(hd->c_nlink),
+ OCT);
+ arcn->sb.st_rdev = (dev_t)asc_u32(hd->c_rdev, sizeof(hd->c_rdev), OCT);
+ arcn->sb.st_mtime = (time_t)(int32_t)asc_u32(hd->c_mtime, sizeof(hd->c_mtime),
+ OCT);
+ arcn->sb.st_ctime = arcn->sb.st_atime = arcn->sb.st_mtime;
+ arcn->sb.st_size = (off_t)ASC_OFFT(hd->c_filesize,
+ sizeof(hd->c_filesize), OCT);
+
+ /*
+ * check name size and if valid, read in the name of this entry (name
+ * follows header in the archive)
+ */
+ if ((nsz = (int)asc_u32(hd->c_namesize,sizeof(hd->c_namesize),OCT)) < 2)
+ return -1;
+ arcn->nlen = nsz - 1;
+ if (rd_nm(arcn, nsz) < 0)
+ return -1;
+
+ if (((arcn->sb.st_mode&C_IFMT) != C_ISLNK)||(arcn->sb.st_size == 0)) {
+ /*
+ * no link name to read for this file
+ */
+ arcn->ln_nlen = 0;
+ arcn->ln_name[0] = '\0';
+ return com_rd(arcn);
+ }
+
+ /*
+ * check link name size and read in the link name. Link names are
+ * stored like file data.
+ */
+ if (rd_ln_nm(arcn) < 0)
+ return -1;
+
+ /*
+ * we have a valid header (with a link)
+ */
+ return com_rd(arcn);
+}
+
+/*
+ * cpio_endrd()
+ * no cleanup needed here, just return size of the trailer (for append)
+ * Return:
+ * size of trailer header in this format
+ */
+
+off_t
+cpio_endrd(void)
+{
+ return (off_t)(sizeof(HD_CPIO) + sizeof(TRAILER));
+}
+
+/*
+ * cpio_stwr()
+ * start up the device mapping table
+ * Return:
+ * 0 if ok, -1 otherwise (what dev_start() returns)
+ */
+
+int
+cpio_stwr(void)
+{
+ return dev_start();
+}
+
+/*
+ * cpio_wr()
+ * copy the data in the ARCHD to buffer in extended byte oriented cpio
+ * format.
+ * Return
+ * 0 if file has data to be written after the header, 1 if file has NO
+ * data to write after the header, -1 if archive write failed
+ */
+
+int
+cpio_wr(ARCHD *arcn)
+{
+ HD_CPIO *hd;
+ int nsz;
+ char hdblk[sizeof(HD_CPIO)];
+
+ /*
+ * check and repair truncated device and inode fields in the header
+ */
+ if (map_dev(arcn, (u_long)CPIO_MASK, (u_long)CPIO_MASK) < 0)
+ return -1;
+
+ arcn->pad = 0L;
+ nsz = arcn->nlen + 1;
+ hd = (HD_CPIO *)hdblk;
+ if ((arcn->type != PAX_BLK) && (arcn->type != PAX_CHR))
+ arcn->sb.st_rdev = 0;
+
+ switch(arcn->type) {
+ case PAX_CTG:
+ case PAX_REG:
+ case PAX_HRG:
+ /*
+ * set data size for file data
+ */
+ if (OFFT_ASC(arcn->sb.st_size, hd->c_filesize,
+ sizeof(hd->c_filesize), OCT)) {
+ tty_warn(1,"File is too large for cpio format %s",
+ arcn->org_name);
+ return 1;
+ }
+ break;
+ case PAX_SLK:
+ /*
+ * set data size to hold link name
+ */
+ if (u32_asc((uintmax_t)arcn->ln_nlen, hd->c_filesize,
+ sizeof(hd->c_filesize), OCT))
+ goto out;
+ break;
+ default:
+ /*
+ * all other file types have no file data
+ */
+ if (u32_asc((uintmax_t)0, hd->c_filesize, sizeof(hd->c_filesize),
+ OCT))
+ goto out;
+ break;
+ }
+
+ /*
+ * copy the values to the header using octal ascii
+ */
+ if (u32_asc((uintmax_t)MAGIC, hd->c_magic, sizeof(hd->c_magic), OCT) ||
+ u32_asc((uintmax_t)arcn->sb.st_dev, hd->c_dev, sizeof(hd->c_dev),
+ OCT) ||
+ u32_asc((uintmax_t)arcn->sb.st_ino, hd->c_ino, sizeof(hd->c_ino),
+ OCT) ||
+ u32_asc((uintmax_t)arcn->sb.st_mode, hd->c_mode, sizeof(hd->c_mode),
+ OCT) ||
+ u32_asc((uintmax_t)arcn->sb.st_uid, hd->c_uid, sizeof(hd->c_uid),
+ OCT) ||
+ u32_asc((uintmax_t)arcn->sb.st_gid, hd->c_gid, sizeof(hd->c_gid),
+ OCT) ||
+ u32_asc((uintmax_t)arcn->sb.st_nlink, hd->c_nlink, sizeof(hd->c_nlink),
+ OCT) ||
+ u32_asc((uintmax_t)arcn->sb.st_rdev, hd->c_rdev, sizeof(hd->c_rdev),
+ OCT) ||
+ u32_asc((uintmax_t)arcn->sb.st_mtime,hd->c_mtime,sizeof(hd->c_mtime),
+ OCT) ||
+ u32_asc((uintmax_t)nsz, hd->c_namesize, sizeof(hd->c_namesize), OCT))
+ goto out;
+
+ /*
+ * write the file name to the archive
+ */
+ if ((wr_rdbuf(hdblk, (int)sizeof(HD_CPIO)) < 0) ||
+ (wr_rdbuf(arcn->name, nsz) < 0)) {
+ tty_warn(1, "Unable to write cpio header for %s",
+ arcn->org_name);
+ return -1;
+ }
+
+ /*
+ * if this file has data, we are done. The caller will write the file
+ * data, if we are link tell caller we are done, go to next file
+ */
+ if ((arcn->type == PAX_CTG) || (arcn->type == PAX_REG))
+ return 0;
+ if (arcn->type != PAX_SLK)
+ return 1;
+
+ /*
+ * write the link name to the archive, tell the caller to go to the
+ * next file as we are done.
+ */
+ if (wr_rdbuf(arcn->ln_name, arcn->ln_nlen) < 0) {
+ tty_warn(1,"Unable to write cpio link name for %s",
+ arcn->org_name);
+ return -1;
+ }
+ return 1;
+
+ out:
+ /*
+ * header field is out of range
+ */
+ tty_warn(1, "Cpio header field is too small to store file %s",
+ arcn->org_name);
+ return 1;
+}
+
+/*
+ * Routines common to the system VR4 version of cpio (with/without file CRC)
+ */
+
+/*
+ * vcpio_id()
+ * determine if a block given to us is a valid system VR4 cpio header
+ * WITHOUT crc. WATCH it the magic cookies are in OCTAL, the header
+ * uses HEX
+ * Return:
+ * 0 if a valid header, -1 otherwise
+ */
+
+int
+vcpio_id(char *blk, int size)
+{
+ if ((size < (int)sizeof(HD_VCPIO)) ||
+ (strncmp(blk, AVMAGIC, sizeof(AVMAGIC) - 1) != 0))
+ return -1;
+ return 0;
+}
+
+/*
+ * crc_id()
+ * determine if a block given to us is a valid system VR4 cpio header
+ * WITH crc. WATCH it the magic cookies are in OCTAL the header uses HEX
+ * Return:
+ * 0 if a valid header, -1 otherwise
+ */
+
+int
+crc_id(char *blk, int size)
+{
+ if ((size < (int)sizeof(HD_VCPIO)) ||
+ (strncmp(blk, AVCMAGIC, sizeof(AVCMAGIC) - 1) != 0))
+ return -1;
+ return 0;
+}
+
+/*
+ * crc_strd()
+ * set file data CRC calculations. Fire up the hard link detection code
+ * Return:
+ * 0 if ok -1 otherwise (the return values of lnk_start())
+ */
+
+int
+crc_strd(void)
+{
+ docrc = 1;
+ return lnk_start();
+}
+
+/*
+ * vcpio_rd()
+ * determine if a buffer is a system VR4 archive entry. (with/without CRC)
+ * convert and store the values in the ARCHD parameter.
+ * Return:
+ * 0 if a valid header, -1 otherwise.
+ */
+
+int
+vcpio_rd(ARCHD *arcn, char *buf)
+{
+ HD_VCPIO *hd;
+ dev_t devminor;
+ dev_t devmajor;
+ int nsz;
+
+ /*
+ * during the id phase it was determined if we were using CRC, use the
+ * proper id routine.
+ */
+ if (docrc) {
+ if (crc_id(buf, sizeof(HD_VCPIO)) < 0)
+ return -1;
+ } else {
+ if (vcpio_id(buf, sizeof(HD_VCPIO)) < 0)
+ return -1;
+ }
+
+ hd = (HD_VCPIO *)buf;
+ arcn->pad = 0L;
+
+ /*
+ * extract the hex ascii fields from the header
+ */
+ arcn->sb.st_ino = (ino_t)asc_u32(hd->c_ino, sizeof(hd->c_ino), HEX);
+ arcn->sb.st_mode = (mode_t)asc_u32(hd->c_mode, sizeof(hd->c_mode), HEX);
+ arcn->sb.st_uid = (uid_t)asc_u32(hd->c_uid, sizeof(hd->c_uid), HEX);
+ arcn->sb.st_gid = (gid_t)asc_u32(hd->c_gid, sizeof(hd->c_gid), HEX);
+ arcn->sb.st_mtime = (time_t)(int32_t)asc_u32(hd->c_mtime,sizeof(hd->c_mtime),HEX);
+ arcn->sb.st_ctime = arcn->sb.st_atime = arcn->sb.st_mtime;
+ arcn->sb.st_size = (off_t)ASC_OFFT(hd->c_filesize,
+ sizeof(hd->c_filesize), HEX);
+ arcn->sb.st_nlink = (nlink_t)asc_u32(hd->c_nlink, sizeof(hd->c_nlink),
+ HEX);
+ devmajor = (dev_t)asc_u32(hd->c_maj, sizeof(hd->c_maj), HEX);
+ devminor = (dev_t)asc_u32(hd->c_min, sizeof(hd->c_min), HEX);
+ arcn->sb.st_dev = TODEV(devmajor, devminor);
+ devmajor = (dev_t)asc_u32(hd->c_rmaj, sizeof(hd->c_maj), HEX);
+ devminor = (dev_t)asc_u32(hd->c_rmin, sizeof(hd->c_min), HEX);
+ arcn->sb.st_rdev = TODEV(devmajor, devminor);
+ arcn->crc = asc_u32(hd->c_chksum, sizeof(hd->c_chksum), HEX);
+
+ /*
+ * check the length of the file name, if ok read it in, return -1 if
+ * bogus
+ */
+ if ((nsz = (int)asc_u32(hd->c_namesize,sizeof(hd->c_namesize),HEX)) < 2)
+ return -1;
+ arcn->nlen = nsz - 1;
+ if (rd_nm(arcn, nsz) < 0)
+ return -1;
+
+ /*
+ * skip padding. header + filename is aligned to 4 byte boundaries
+ */
+ if (rd_skip((off_t)(VCPIO_PAD(sizeof(HD_VCPIO) + nsz))) < 0)
+ return -1;
+
+ /*
+ * if not a link (or a file with no data), calculate pad size (for
+ * padding which follows the file data), clear the link name and return
+ */
+ if (((arcn->sb.st_mode&C_IFMT) != C_ISLNK)||(arcn->sb.st_size == 0)) {
+ /*
+ * we have a valid header (not a link)
+ */
+ arcn->ln_nlen = 0;
+ arcn->ln_name[0] = '\0';
+ arcn->pad = VCPIO_PAD(arcn->sb.st_size);
+ return com_rd(arcn);
+ }
+
+ /*
+ * read in the link name and skip over the padding
+ */
+ if ((rd_ln_nm(arcn) < 0) ||
+ (rd_skip((off_t)(VCPIO_PAD(arcn->sb.st_size))) < 0))
+ return -1;
+
+ /*
+ * we have a valid header (with a link)
+ */
+ return com_rd(arcn);
+}
+
+/*
+ * vcpio_endrd()
+ * no cleanup needed here, just return size of the trailer (for append)
+ * Return:
+ * size of trailer header in this format
+ */
+
+off_t
+vcpio_endrd(void)
+{
+ return (off_t)(sizeof(HD_VCPIO) + sizeof(TRAILER) +
+ (VCPIO_PAD(sizeof(HD_VCPIO) + sizeof(TRAILER))));
+}
+
+/*
+ * crc_stwr()
+ * start up the device mapping table, enable crc file calculation
+ * Return:
+ * 0 if ok, -1 otherwise (what dev_start() returns)
+ */
+
+int
+crc_stwr(void)
+{
+ docrc = 1;
+ return dev_start();
+}
+
+/*
+ * vcpio_wr()
+ * copy the data in the ARCHD to buffer in system VR4 cpio
+ * (with/without crc) format.
+ * Return
+ * 0 if file has data to be written after the header, 1 if file has
+ * NO data to write after the header, -1 if archive write failed
+ */
+
+int
+vcpio_wr(ARCHD *arcn)
+{
+ HD_VCPIO *hd;
+ unsigned int nsz;
+ char hdblk[sizeof(HD_VCPIO)];
+
+ /*
+ * check and repair truncated device and inode fields in the cpio
+ * header
+ */
+ if (map_dev(arcn, (u_long)VCPIO_MASK, (u_long)VCPIO_MASK) < 0)
+ return -1;
+ nsz = arcn->nlen + 1;
+ hd = (HD_VCPIO *)hdblk;
+ if ((arcn->type != PAX_BLK) && (arcn->type != PAX_CHR))
+ arcn->sb.st_rdev = 0;
+
+ /*
+ * add the proper magic value depending whether we were asked for
+ * file data crc's, and the crc if needed.
+ */
+ if (docrc) {
+ if (u32_asc((uintmax_t)VCMAGIC, hd->c_magic, sizeof(hd->c_magic),
+ OCT) ||
+ u32_asc((uintmax_t)arcn->crc,hd->c_chksum,sizeof(hd->c_chksum),
+ HEX))
+ goto out;
+ } else {
+ if (u32_asc((uintmax_t)VMAGIC, hd->c_magic, sizeof(hd->c_magic),
+ OCT) ||
+ u32_asc((uintmax_t)0, hd->c_chksum, sizeof(hd->c_chksum),HEX))
+ goto out;
+ }
+
+ switch(arcn->type) {
+ case PAX_CTG:
+ case PAX_REG:
+ case PAX_HRG:
+ /*
+ * caller will copy file data to the archive. tell him how
+ * much to pad.
+ */
+ arcn->pad = VCPIO_PAD(arcn->sb.st_size);
+ if (OFFT_ASC(arcn->sb.st_size, hd->c_filesize,
+ sizeof(hd->c_filesize), HEX)) {
+ tty_warn(1,"File is too large for sv4cpio format %s",
+ arcn->org_name);
+ return 1;
+ }
+ break;
+ case PAX_SLK:
+ /*
+ * no file data for the caller to process, the file data has
+ * the size of the link
+ */
+ arcn->pad = 0L;
+ if (u32_asc((uintmax_t)arcn->ln_nlen, hd->c_filesize,
+ sizeof(hd->c_filesize), HEX))
+ goto out;
+ break;
+ default:
+ /*
+ * no file data for the caller to process
+ */
+ arcn->pad = 0L;
+ if (u32_asc((uintmax_t)0, hd->c_filesize, sizeof(hd->c_filesize),
+ HEX))
+ goto out;
+ break;
+ }
+
+ /*
+ * set the other fields in the header
+ */
+ if (u32_asc((uintmax_t)arcn->sb.st_ino, hd->c_ino, sizeof(hd->c_ino),
+ HEX) ||
+ u32_asc((uintmax_t)arcn->sb.st_mode, hd->c_mode, sizeof(hd->c_mode),
+ HEX) ||
+ u32_asc((uintmax_t)arcn->sb.st_uid, hd->c_uid, sizeof(hd->c_uid),
+ HEX) ||
+ u32_asc((uintmax_t)arcn->sb.st_gid, hd->c_gid, sizeof(hd->c_gid),
+ HEX) ||
+ u32_asc((uintmax_t)arcn->sb.st_mtime, hd->c_mtime, sizeof(hd->c_mtime),
+ HEX) ||
+ u32_asc((uintmax_t)arcn->sb.st_nlink, hd->c_nlink, sizeof(hd->c_nlink),
+ HEX) ||
+ u32_asc((uintmax_t)MAJOR(arcn->sb.st_dev),hd->c_maj, sizeof(hd->c_maj),
+ HEX) ||
+ u32_asc((uintmax_t)MINOR(arcn->sb.st_dev),hd->c_min, sizeof(hd->c_min),
+ HEX) ||
+ u32_asc((uintmax_t)MAJOR(arcn->sb.st_rdev),hd->c_rmaj,sizeof(hd->c_maj),
+ HEX) ||
+ u32_asc((uintmax_t)MINOR(arcn->sb.st_rdev),hd->c_rmin,sizeof(hd->c_min),
+ HEX) ||
+ u32_asc((uintmax_t)nsz, hd->c_namesize, sizeof(hd->c_namesize), HEX))
+ goto out;
+
+ /*
+ * write the header, the file name and padding as required.
+ */
+ if ((wr_rdbuf(hdblk, (int)sizeof(HD_VCPIO)) < 0) ||
+ (wr_rdbuf(arcn->name, (int)nsz) < 0) ||
+ (wr_skip((off_t)(VCPIO_PAD(sizeof(HD_VCPIO) + nsz))) < 0)) {
+ tty_warn(1,"Could not write sv4cpio header for %s",
+ arcn->org_name);
+ return -1;
+ }
+
+ /*
+ * if we have file data, tell the caller we are done, copy the file
+ */
+ if ((arcn->type == PAX_CTG) || (arcn->type == PAX_REG) ||
+ (arcn->type == PAX_HRG))
+ return 0;
+
+ /*
+ * if we are not a link, tell the caller we are done, go to next file
+ */
+ if (arcn->type != PAX_SLK)
+ return 1;
+
+ /*
+ * write the link name, tell the caller we are done.
+ */
+ if ((wr_rdbuf(arcn->ln_name, arcn->ln_nlen) < 0) ||
+ (wr_skip((off_t)(VCPIO_PAD(arcn->ln_nlen))) < 0)) {
+ tty_warn(1,"Could not write sv4cpio link name for %s",
+ arcn->org_name);
+ return -1;
+ }
+ return 1;
+
+ out:
+ /*
+ * header field is out of range
+ */
+ tty_warn(1,"Sv4cpio header field is too small for file %s",
+ arcn->org_name);
+ return 1;
+}
+
+/*
+ * Routines common to the old binary header cpio
+ */
+
+/*
+ * bcpio_id()
+ * determine if a block given to us is a old binary cpio header
+ * (with/without header byte swapping)
+ * Return:
+ * 0 if a valid header, -1 otherwise
+ */
+
+int
+bcpio_id(char *blk, int size)
+{
+ if (size < (int)sizeof(HD_BCPIO))
+ return -1;
+
+ /*
+ * check both normal and byte swapped magic cookies
+ */
+ if (((u_short)SHRT_EXT(blk)) == MAGIC)
+ return 0;
+ if (((u_short)RSHRT_EXT(blk)) == MAGIC) {
+ if (!cpio_swp_head)
+ ++cpio_swp_head;
+ return 0;
+ }
+ return -1;
+}
+
+/*
+ * bcpio_rd()
+ * determine if a buffer is a old binary archive entry. (it may have byte
+ * swapped header) convert and store the values in the ARCHD parameter.
+ * This is a very old header format and should not really be used.
+ * Return:
+ * 0 if a valid header, -1 otherwise.
+ */
+
+int
+bcpio_rd(ARCHD *arcn, char *buf)
+{
+ HD_BCPIO *hd;
+ int nsz;
+
+ /*
+ * check the header
+ */
+ if (bcpio_id(buf, sizeof(HD_BCPIO)) < 0)
+ return -1;
+
+ arcn->pad = 0L;
+ hd = (HD_BCPIO *)buf;
+ if (cpio_swp_head) {
+ /*
+ * header has swapped bytes on 16 bit boundaries
+ */
+ arcn->sb.st_dev = (dev_t)(RSHRT_EXT(hd->h_dev));
+ arcn->sb.st_ino = (ino_t)(RSHRT_EXT(hd->h_ino));
+ arcn->sb.st_mode = (mode_t)(RSHRT_EXT(hd->h_mode));
+ arcn->sb.st_uid = (uid_t)(RSHRT_EXT(hd->h_uid));
+ arcn->sb.st_gid = (gid_t)(RSHRT_EXT(hd->h_gid));
+ arcn->sb.st_nlink = (nlink_t)(RSHRT_EXT(hd->h_nlink));
+ arcn->sb.st_rdev = (dev_t)(RSHRT_EXT(hd->h_rdev));
+ arcn->sb.st_mtime = (time_t)(RSHRT_EXT(hd->h_mtime_1));
+ arcn->sb.st_mtime = (arcn->sb.st_mtime << 16) |
+ ((time_t)(RSHRT_EXT(hd->h_mtime_2)));
+ arcn->sb.st_size = (off_t)(RSHRT_EXT(hd->h_filesize_1));
+ arcn->sb.st_size = (arcn->sb.st_size << 16) |
+ ((off_t)(RSHRT_EXT(hd->h_filesize_2)));
+ nsz = (int)(RSHRT_EXT(hd->h_namesize));
+ } else {
+ arcn->sb.st_dev = (dev_t)(SHRT_EXT(hd->h_dev));
+ arcn->sb.st_ino = (ino_t)(SHRT_EXT(hd->h_ino));
+ arcn->sb.st_mode = (mode_t)(SHRT_EXT(hd->h_mode));
+ arcn->sb.st_uid = (uid_t)(SHRT_EXT(hd->h_uid));
+ arcn->sb.st_gid = (gid_t)(SHRT_EXT(hd->h_gid));
+ arcn->sb.st_nlink = (nlink_t)(SHRT_EXT(hd->h_nlink));
+ arcn->sb.st_rdev = (dev_t)(SHRT_EXT(hd->h_rdev));
+ arcn->sb.st_mtime = (time_t)(SHRT_EXT(hd->h_mtime_1));
+ arcn->sb.st_mtime = (arcn->sb.st_mtime << 16) |
+ ((time_t)(SHRT_EXT(hd->h_mtime_2)));
+ arcn->sb.st_size = (off_t)(SHRT_EXT(hd->h_filesize_1));
+ arcn->sb.st_size = (arcn->sb.st_size << 16) |
+ ((off_t)(SHRT_EXT(hd->h_filesize_2)));
+ nsz = (int)(SHRT_EXT(hd->h_namesize));
+ }
+ arcn->sb.st_ctime = arcn->sb.st_atime = arcn->sb.st_mtime;
+
+ /*
+ * check the file name size, if bogus give up. otherwise read the file
+ * name
+ */
+ if (nsz < 2)
+ return -1;
+ arcn->nlen = nsz - 1;
+ if (rd_nm(arcn, nsz) < 0)
+ return -1;
+
+ /*
+ * header + file name are aligned to 2 byte boundaries, skip if needed
+ */
+ if (rd_skip((off_t)(BCPIO_PAD(sizeof(HD_BCPIO) + nsz))) < 0)
+ return -1;
+
+ /*
+ * if not a link (or a file with no data), calculate pad size (for
+ * padding which follows the file data), clear the link name and return
+ */
+ if (((arcn->sb.st_mode & C_IFMT) != C_ISLNK)||(arcn->sb.st_size == 0)){
+ /*
+ * we have a valid header (not a link)
+ */
+ arcn->ln_nlen = 0;
+ arcn->ln_name[0] = '\0';
+ arcn->pad = BCPIO_PAD(arcn->sb.st_size);
+ return com_rd(arcn);
+ }
+
+ if ((rd_ln_nm(arcn) < 0) ||
+ (rd_skip((off_t)(BCPIO_PAD(arcn->sb.st_size))) < 0))
+ return -1;
+
+ /*
+ * we have a valid header (with a link)
+ */
+ return com_rd(arcn);
+}
+
+/*
+ * bcpio_endrd()
+ * no cleanup needed here, just return size of the trailer (for append)
+ * Return:
+ * size of trailer header in this format
+ */
+
+off_t
+bcpio_endrd(void)
+{
+ return (off_t)(sizeof(HD_BCPIO) + sizeof(TRAILER) +
+ (BCPIO_PAD(sizeof(HD_BCPIO) + sizeof(TRAILER))));
+}
+
+/*
+ * bcpio_wr()
+ * copy the data in the ARCHD to buffer in old binary cpio format
+ * There is a real chance of field overflow with this critter. So we
+ * always check the conversion is ok. nobody in their right mind
+ * should write an archive in this format...
+ * Return
+ * 0 if file has data to be written after the header, 1 if file has NO
+ * data to write after the header, -1 if archive write failed
+ */
+
+int
+bcpio_wr(ARCHD *arcn)
+{
+ HD_BCPIO *hd;
+ int nsz;
+ char hdblk[sizeof(HD_BCPIO)];
+ off_t t_offt;
+ int t_int;
+ time_t t_timet;
+
+ /*
+ * check and repair truncated device and inode fields in the cpio
+ * header
+ */
+ if (map_dev(arcn, (u_long)BCPIO_MASK, (u_long)BCPIO_MASK) < 0)
+ return -1;
+
+ if ((arcn->type != PAX_BLK) && (arcn->type != PAX_CHR))
+ arcn->sb.st_rdev = 0;
+ hd = (HD_BCPIO *)hdblk;
+
+ switch(arcn->type) {
+ case PAX_CTG:
+ case PAX_REG:
+ case PAX_HRG:
+ /*
+ * caller will copy file data to the archive. tell him how
+ * much to pad.
+ */
+ arcn->pad = BCPIO_PAD(arcn->sb.st_size);
+ hd->h_filesize_1[0] = CHR_WR_0(arcn->sb.st_size);
+ hd->h_filesize_1[1] = CHR_WR_1(arcn->sb.st_size);
+ hd->h_filesize_2[0] = CHR_WR_2(arcn->sb.st_size);
+ hd->h_filesize_2[1] = CHR_WR_3(arcn->sb.st_size);
+ t_offt = (off_t)(SHRT_EXT(hd->h_filesize_1));
+ t_offt = (t_offt<<16) | ((off_t)(SHRT_EXT(hd->h_filesize_2)));
+ if (arcn->sb.st_size != t_offt) {
+ tty_warn(1,"File is too large for bcpio format %s",
+ arcn->org_name);
+ return 1;
+ }
+ break;
+ case PAX_SLK:
+ /*
+ * no file data for the caller to process, the file data has
+ * the size of the link
+ */
+ arcn->pad = 0L;
+ hd->h_filesize_1[0] = CHR_WR_0(arcn->ln_nlen);
+ hd->h_filesize_1[1] = CHR_WR_1(arcn->ln_nlen);
+ hd->h_filesize_2[0] = CHR_WR_2(arcn->ln_nlen);
+ hd->h_filesize_2[1] = CHR_WR_3(arcn->ln_nlen);
+ t_int = (int)(SHRT_EXT(hd->h_filesize_1));
+ t_int = (t_int << 16) | ((int)(SHRT_EXT(hd->h_filesize_2)));
+ if (arcn->ln_nlen != t_int)
+ goto out;
+ break;
+ default:
+ /*
+ * no file data for the caller to process
+ */
+ arcn->pad = 0L;
+ hd->h_filesize_1[0] = (char)0;
+ hd->h_filesize_1[1] = (char)0;
+ hd->h_filesize_2[0] = (char)0;
+ hd->h_filesize_2[1] = (char)0;
+ break;
+ }
+
+ /*
+ * build up the rest of the fields
+ */
+ hd->h_magic[0] = CHR_WR_2(MAGIC);
+ hd->h_magic[1] = CHR_WR_3(MAGIC);
+ hd->h_dev[0] = CHR_WR_2(arcn->sb.st_dev);
+ hd->h_dev[1] = CHR_WR_3(arcn->sb.st_dev);
+ if (arcn->sb.st_dev != (dev_t)(SHRT_EXT(hd->h_dev)))
+ goto out;
+ hd->h_ino[0] = CHR_WR_2(arcn->sb.st_ino);
+ hd->h_ino[1] = CHR_WR_3(arcn->sb.st_ino);
+ if (arcn->sb.st_ino != (ino_t)(SHRT_EXT(hd->h_ino)))
+ goto out;
+ hd->h_mode[0] = CHR_WR_2(arcn->sb.st_mode);
+ hd->h_mode[1] = CHR_WR_3(arcn->sb.st_mode);
+ if (arcn->sb.st_mode != (mode_t)(SHRT_EXT(hd->h_mode)))
+ goto out;
+ hd->h_uid[0] = CHR_WR_2(arcn->sb.st_uid);
+ hd->h_uid[1] = CHR_WR_3(arcn->sb.st_uid);
+ if (arcn->sb.st_uid != (uid_t)(SHRT_EXT(hd->h_uid)))
+ goto out;
+ hd->h_gid[0] = CHR_WR_2(arcn->sb.st_gid);
+ hd->h_gid[1] = CHR_WR_3(arcn->sb.st_gid);
+ if (arcn->sb.st_gid != (gid_t)(SHRT_EXT(hd->h_gid)))
+ goto out;
+ hd->h_nlink[0] = CHR_WR_2(arcn->sb.st_nlink);
+ hd->h_nlink[1] = CHR_WR_3(arcn->sb.st_nlink);
+ if (arcn->sb.st_nlink != (nlink_t)(SHRT_EXT(hd->h_nlink)))
+ goto out;
+ hd->h_rdev[0] = CHR_WR_2(arcn->sb.st_rdev);
+ hd->h_rdev[1] = CHR_WR_3(arcn->sb.st_rdev);
+ if (arcn->sb.st_rdev != (dev_t)(SHRT_EXT(hd->h_rdev)))
+ goto out;
+ hd->h_mtime_1[0] = CHR_WR_0(arcn->sb.st_mtime);
+ hd->h_mtime_1[1] = CHR_WR_1(arcn->sb.st_mtime);
+ hd->h_mtime_2[0] = CHR_WR_2(arcn->sb.st_mtime);
+ hd->h_mtime_2[1] = CHR_WR_3(arcn->sb.st_mtime);
+ t_timet = (time_t)(SHRT_EXT(hd->h_mtime_1));
+ t_timet = (t_timet << 16) | ((time_t)(SHRT_EXT(hd->h_mtime_2)));
+ if (arcn->sb.st_mtime != t_timet)
+ goto out;
+ nsz = arcn->nlen + 1;
+ hd->h_namesize[0] = CHR_WR_2(nsz);
+ hd->h_namesize[1] = CHR_WR_3(nsz);
+ if (nsz != (int)(SHRT_EXT(hd->h_namesize)))
+ goto out;
+
+ /*
+ * write the header, the file name and padding as required.
+ */
+ if ((wr_rdbuf(hdblk, (int)sizeof(HD_BCPIO)) < 0) ||
+ (wr_rdbuf(arcn->name, nsz) < 0) ||
+ (wr_skip((off_t)(BCPIO_PAD(sizeof(HD_BCPIO) + nsz))) < 0)) {
+ tty_warn(1, "Could not write bcpio header for %s",
+ arcn->org_name);
+ return -1;
+ }
+
+ /*
+ * if we have file data, tell the caller we are done
+ */
+ if ((arcn->type == PAX_CTG) || (arcn->type == PAX_REG) ||
+ (arcn->type == PAX_HRG))
+ return 0;
+
+ /*
+ * if we are not a link, tell the caller we are done, go to next file
+ */
+ if (arcn->type != PAX_SLK)
+ return 1;
+
+ /*
+ * write the link name, tell the caller we are done.
+ */
+ if ((wr_rdbuf(arcn->ln_name, arcn->ln_nlen) < 0) ||
+ (wr_skip((off_t)(BCPIO_PAD(arcn->ln_nlen))) < 0)) {
+ tty_warn(1,"Could not write bcpio link name for %s",
+ arcn->org_name);
+ return -1;
+ }
+ return 1;
+
+ out:
+ /*
+ * header field is out of range
+ */
+ tty_warn(1,"Bcpio header field is too small for file %s",
+ arcn->org_name);
+ return 1;
+}
diff --git a/bin/pax/cpio.h b/bin/pax/cpio.h
new file mode 100644
index 0000000..bbf40ed
--- /dev/null
+++ b/bin/pax/cpio.h
@@ -0,0 +1,149 @@
+/* $NetBSD: cpio.h,v 1.6 2003/10/13 07:41:22 agc Exp $ */
+
+/*-
+ * Copyright (c) 1992 Keith Muller.
+ * Copyright (c) 1992, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Keith Muller of the University of California, San Diego.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * @(#)cpio.h 8.1 (Berkeley) 5/31/93
+ */
+
+/*
+ * Defines common to all versions of cpio
+ */
+#define TRAILER "TRAILER!!!" /* name in last archive record */
+
+/*
+ * Header encoding of the different file types
+ */
+#define C_ISDIR 040000 /* Directory */
+#define C_ISFIFO 010000 /* FIFO */
+#define C_ISREG 0100000 /* Regular file */
+#define C_ISBLK 060000 /* Block special file */
+#define C_ISCHR 020000 /* Character special file */
+#define C_ISCTG 0110000 /* Reserved for contiguous files */
+#define C_ISLNK 0120000 /* Reserved for symbolic links */
+#define C_ISOCK 0140000 /* Reserved for sockets */
+#define C_IFMT 0170000 /* type of file */
+
+/*
+ * Data Interchange Format - Extended cpio header format - POSIX 1003.1-1990
+ */
+typedef struct {
+ char c_magic[6]; /* magic cookie */
+ char c_dev[6]; /* device number */
+ char c_ino[6]; /* inode number */
+ char c_mode[6]; /* file type/access */
+ char c_uid[6]; /* owners uid */
+ char c_gid[6]; /* owners gid */
+ char c_nlink[6]; /* # of links at archive creation */
+ char c_rdev[6]; /* block/char major/minor # */
+ char c_mtime[11]; /* modification time */
+ char c_namesize[6]; /* length of pathname */
+ char c_filesize[11]; /* length of file in bytes */
+} HD_CPIO;
+
+#define MAGIC 070707 /* transportable archive id */
+
+#ifdef _PAX_
+#define AMAGIC "070707" /* ascii equivalent string of MAGIC */
+#define CPIO_MASK 0x3ffff /* bits valid in the dev/ino fields */
+ /* used for dev/inode remaps */
+#endif /* _PAX_ */
+
+/*
+ * Binary cpio header structure
+ *
+ * CAUTION! CAUTION! CAUTION!
+ * Each field really represents a 16 bit short (NOT ASCII). Described as
+ * an array of chars in an attempt to improve portability!!
+ */
+typedef struct {
+ u_char h_magic[2];
+ u_char h_dev[2];
+ u_char h_ino[2];
+ u_char h_mode[2];
+ u_char h_uid[2];
+ u_char h_gid[2];
+ u_char h_nlink[2];
+ u_char h_rdev[2];
+ u_char h_mtime_1[2];
+ u_char h_mtime_2[2];
+ u_char h_namesize[2];
+ u_char h_filesize_1[2];
+ u_char h_filesize_2[2];
+} HD_BCPIO;
+
+#ifdef _PAX_
+/*
+ * extraction and creation macros for binary cpio
+ */
+#define SHRT_EXT(ch) ((((unsigned)(ch)[0])<<8) | (((unsigned)(ch)[1])&0xff))
+#define RSHRT_EXT(ch) ((((unsigned)(ch)[1])<<8) | (((unsigned)(ch)[0])&0xff))
+#define CHR_WR_0(val) ((char)(((val) >> 24) & 0xff))
+#define CHR_WR_1(val) ((char)(((val) >> 16) & 0xff))
+#define CHR_WR_2(val) ((char)(((val) >> 8) & 0xff))
+#define CHR_WR_3(val) ((char)((val) & 0xff))
+
+/*
+ * binary cpio masks and pads
+ */
+#define BCPIO_PAD(x) ((2 - ((x) & 1)) & 1) /* pad to next 2 byte word */
+#define BCPIO_MASK 0xffff /* mask for dev/ino fields */
+#endif /* _PAX_ */
+
+/*
+ * System VR4 cpio header structure (with/without file data crc)
+ */
+typedef struct {
+ char c_magic[6]; /* magic cookie */
+ char c_ino[8]; /* inode number */
+ char c_mode[8]; /* file type/access */
+ char c_uid[8]; /* owners uid */
+ char c_gid[8]; /* owners gid */
+ char c_nlink[8]; /* # of links at archive creation */
+ char c_mtime[8]; /* modification time */
+ char c_filesize[8]; /* length of file in bytes */
+ char c_maj[8]; /* block/char major # */
+ char c_min[8]; /* block/char minor # */
+ char c_rmaj[8]; /* special file major # */
+ char c_rmin[8]; /* special file minor # */
+ char c_namesize[8]; /* length of pathname */
+ char c_chksum[8]; /* 0 OR CRC of bytes of FILE data */
+} HD_VCPIO;
+
+#define VMAGIC 070701 /* sVr4 new portable archive id */
+#define VCMAGIC 070702 /* sVr4 new portable archive id CRC */
+#ifdef _PAX_
+#define AVMAGIC "070701" /* ascii string of above */
+#define AVCMAGIC "070702" /* ascii string of above */
+#define VCPIO_PAD(x) ((4 - ((x) & 3)) & 3) /* pad to next 4 byte word */
+#define VCPIO_MASK 0xffffffff /* mask for dev/ino fields */
+#endif /* _PAX_ */
diff --git a/bin/pax/dumptar.c b/bin/pax/dumptar.c
new file mode 100644
index 0000000..5538702
--- /dev/null
+++ b/bin/pax/dumptar.c
@@ -0,0 +1,131 @@
+/* $NetBSD: dumptar.c,v 1.3 2016/05/30 17:34:35 dholland Exp $ */
+
+/*-
+ * Copyright (c) 2004 The NetBSD Foundation, Inc.
+ * All rights reserved.
+ *
+ * This code is derived from software contributed to The NetBSD Foundation
+ * by Christos Zoulas.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
+ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <err.h>
+#include <assert.h>
+#include <sys/stat.h>
+#include <sys/mman.h>
+
+#include "tar.h"
+
+#define ussum(a) 1
+
+/*
+ * Ensure null termination.
+ */
+static char *
+buf(const char *p, size_t s)
+{
+ static char buf[1024];
+
+ assert(s < sizeof(buf));
+ memcpy(buf, p, s);
+ buf[s] = '\0';
+ return buf;
+}
+
+static int
+intarg(const char *p, size_t s)
+{
+ char *ep, *b = buf(p, s);
+ int r = (int)strtol(b, &ep, 8);
+ return r;
+}
+
+static int
+usdump(void *p)
+{
+ HD_USTAR *t = p;
+ int size = intarg(t->size, sizeof(t->size));
+ size = ((size + 511) / 512) * 512 + 512;
+
+ (void)fprintf(stdout, "*****\n");
+#define PR(a) \
+ (void)fprintf(stdout, #a "=%s\n", buf(t->a, sizeof(t->a)));
+#define IPR(a) \
+ (void)fprintf(stdout, #a "=%d\n", intarg(t->a, sizeof(t->a)));
+#define OPR(a) \
+ (void)fprintf(stdout, #a "=%o\n", intarg(t->a, sizeof(t->a)));
+ PR(name);
+ OPR(mode);
+ IPR(uid);
+ IPR(gid);
+ IPR(size);
+ OPR(mtime);
+ OPR(chksum);
+ (void)fprintf(stdout, "typeflag=%c\n", t->typeflag);
+ PR(linkname);
+ PR(magic);
+ PR(version);
+ PR(uname);
+ PR(gname);
+ OPR(devmajor);
+ OPR(devminor);
+ PR(prefix);
+ return size;
+}
+
+int
+main(int argc, char *argv[])
+{
+ int fd;
+ struct stat st;
+ char *p, *ep;
+
+ if (argc != 2) {
+ (void)fprintf(stderr, "Usage: %s <filename>\n", getprogname());
+ return 1;
+ }
+
+ if ((fd = open(argv[1], O_RDONLY)) == -1)
+ err(1, "Cannot open `%s'", argv[1]);
+
+ if (fstat(fd, &st) == -1)
+ err(1, "Cannot fstat `%s'", argv[1]);
+
+ if ((p = mmap(NULL, (size_t)st.st_size, PROT_READ,
+ MAP_FILE|MAP_PRIVATE, fd, (off_t)0)) == MAP_FAILED)
+ err(1, "Cannot mmap `%s'", argv[1]);
+ (void)close(fd);
+
+ ep = (char *)p + (size_t)st.st_size;
+
+ for (; p < ep + sizeof(HD_USTAR);) {
+ if (ussum(p))
+ p += usdump(p);
+ }
+ return 0;
+}
diff --git a/bin/pax/extern.h b/bin/pax/extern.h
new file mode 100644
index 0000000..298600c
--- /dev/null
+++ b/bin/pax/extern.h
@@ -0,0 +1,326 @@
+/* $NetBSD: extern.h,v 1.59 2012/08/09 08:09:21 christos Exp $ */
+
+/*-
+ * Copyright (c) 1992 Keith Muller.
+ * Copyright (c) 1992, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Keith Muller of the University of California, San Diego.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * @(#)extern.h 8.2 (Berkeley) 4/18/94
+ */
+
+/*
+ * External references from each source file
+ */
+
+#include <sys/cdefs.h>
+#include <err.h>
+
+/*
+ * ar_io.c
+ */
+extern const char *arcname;
+extern int curdirfd;
+extern const char *gzip_program;
+extern time_t starttime;
+extern int force_one_volume;
+extern char *chdname;
+extern int forcelocal;
+extern int secure;
+
+int ar_open(const char *);
+void ar_close(void);
+void ar_drain(void);
+int ar_set_wr(void);
+int ar_app_ok(void);
+#ifdef SYS_NO_RESTART
+int read_with_restart(int, void *, int);
+int write_with_restart(int, void *, int);
+#else
+#define read_with_restart read
+#define write_with_restart write
+#endif
+int xread(int, void *, int);
+int xwrite(int, void *, int);
+int ar_read(char *, int);
+int ar_write(char *, int);
+int ar_rdsync(void);
+int ar_fow(off_t, off_t *);
+int ar_rev(off_t );
+int ar_next(void);
+void ar_summary(int);
+int ar_dochdir(const char *);
+
+/*
+ * ar_subs.c
+ */
+extern u_long flcnt;
+extern ARCHD archd;
+int updatepath(void);
+int dochdir(const char *);
+int fdochdir(int);
+int domkdir(const char *, mode_t);
+int list(void);
+int extract(void);
+int append(void);
+int archive(void);
+int copy(void);
+
+/*
+ * buf_subs.c
+ */
+extern int blksz;
+extern int wrblksz;
+extern int maxflt;
+extern int rdblksz;
+extern off_t wrlimit;
+extern off_t rdcnt;
+extern off_t wrcnt;
+int wr_start(void);
+int rd_start(void);
+void cp_start(void);
+int appnd_start(off_t);
+int rd_sync(void);
+void pback(char *, int);
+int rd_skip(off_t);
+void wr_fin(void);
+int wr_rdbuf(char *, int);
+int rd_wrbuf(char *, int);
+int wr_skip(off_t);
+int wr_rdfile(ARCHD *, int, off_t *);
+int rd_wrfile(ARCHD *, int, off_t *);
+void cp_file(ARCHD *, int, int);
+int buf_fill(void);
+int buf_flush(int);
+
+/*
+ * cpio.c
+ */
+extern int cpio_swp_head;
+int cpio_strd(void);
+int cpio_subtrail(ARCHD *);
+int cpio_endwr(void);
+int cpio_id(char *, int);
+int cpio_rd(ARCHD *, char *);
+off_t cpio_endrd(void);
+int cpio_stwr(void);
+int cpio_wr(ARCHD *);
+int vcpio_id(char *, int);
+int crc_id(char *, int);
+int crc_strd(void);
+int vcpio_rd(ARCHD *, char *);
+off_t vcpio_endrd(void);
+int crc_stwr(void);
+int vcpio_wr(ARCHD *);
+int bcpio_id(char *, int);
+int bcpio_rd(ARCHD *, char *);
+off_t bcpio_endrd(void);
+int bcpio_wr(ARCHD *);
+
+/*
+ * file_subs.c
+ */
+extern char *gnu_name_string, *gnu_link_string;
+extern size_t gnu_name_length, gnu_link_length;
+extern char *xtmp_name;
+int file_creat(ARCHD *, int);
+void file_close(ARCHD *, int);
+int lnk_creat(ARCHD *, int *);
+int cross_lnk(ARCHD *);
+int chk_same(ARCHD *);
+int node_creat(ARCHD *);
+int unlnk_exist(char *, int);
+int chk_path(char *, uid_t, gid_t);
+void set_ftime(char *fnm, time_t mtime, time_t atime, int frc, int slk);
+int set_ids(char *, uid_t, gid_t);
+void set_pmode(char *, mode_t);
+void set_chflags(char *fnm, u_int32_t flags);
+int file_write(int, char *, int, int *, int *, int, char *);
+void file_flush(int, char *, int);
+void rdfile_close(ARCHD *, int *);
+int set_crc(ARCHD *, int);
+
+/*
+ * ftree.c
+ */
+int ftree_start(void);
+int ftree_add(char *, int);
+void ftree_sel(ARCHD *);
+void ftree_chk(void);
+int next_file(ARCHD *);
+
+/*
+ * gen_subs.c
+ */
+void ls_list(ARCHD *, time_t, FILE *);
+void ls_tty(ARCHD *);
+void safe_print(const char *, FILE *);
+uint32_t asc_u32(char *, int, int);
+int u32_asc(uintmax_t, char *, int, int);
+uintmax_t asc_umax(char *, int, int);
+int umax_asc(uintmax_t, char *, int, int);
+int check_Aflag(void);
+
+/*
+ * getoldopt.c
+ */
+struct option;
+int getoldopt(int, char **, const char *, struct option *, int *);
+
+/*
+ * options.c
+ */
+extern FSUB fsub[];
+extern int ford[];
+extern int sep;
+extern int havechd;
+void options(int, char **);
+OPLIST * opt_next(void);
+int bad_opt(void);
+int mkpath(char *);
+char *chdname;
+#if !HAVE_NBTOOL_CONFIG_H
+int do_chroot;
+#endif
+
+/*
+ * pat_rep.c
+ */
+int rep_add(char *);
+int pat_add(char *, char *, int);
+void pat_chk(void);
+int pat_sel(ARCHD *);
+int pat_match(ARCHD *);
+int mod_name(ARCHD *, int);
+int set_dest(ARCHD *, char *, int);
+
+/*
+ * pax.c
+ */
+extern int act;
+extern FSUB *frmt;
+extern int Aflag;
+extern int cflag;
+extern int cwdfd;
+extern int dflag;
+extern int iflag;
+extern int kflag;
+extern int lflag;
+extern int nflag;
+extern int tflag;
+extern int uflag;
+extern int vflag;
+extern int Dflag;
+extern int Hflag;
+extern int Lflag;
+extern int Mflag;
+extern int Vflag;
+extern int Xflag;
+extern int Yflag;
+extern int Zflag;
+extern int vfpart;
+extern int patime;
+extern int pmtime;
+extern int nodirs;
+extern int pfflags;
+extern int pmode;
+extern int pids;
+extern int rmleadslash;
+extern int exit_val;
+extern int docrc;
+extern int to_stdout;
+extern char *dirptr;
+extern char *ltmfrmt;
+extern const char *argv0;
+extern FILE *listf;
+extern char *tempfile;
+extern char *tempbase;
+
+/*
+ * sel_subs.c
+ */
+int sel_chk(ARCHD *);
+int grp_add(char *);
+int usr_add(char *);
+int trng_add(char *);
+
+/*
+ * tables.c
+ */
+int lnk_start(void);
+int chk_lnk(ARCHD *);
+void purg_lnk(ARCHD *);
+void lnk_end(void);
+int ftime_start(void);
+int chk_ftime(ARCHD *);
+int name_start(void);
+int add_name(char *, int, char *);
+void sub_name(char *, int *, size_t);
+int dev_start(void);
+int add_dev(ARCHD *);
+int map_dev(ARCHD *, u_long, u_long);
+int atdir_start(void);
+void atdir_end(void);
+void add_atdir(char *, dev_t, ino_t, time_t, time_t);
+int get_atdir(dev_t, ino_t, time_t *, time_t *);
+int dir_start(void);
+void add_dir(char *, int, struct stat *, int);
+void proc_dir(void);
+u_int st_hash(char *, int, int);
+
+/*
+ * tar.c
+ */
+extern int is_gnutar;
+int tar_endwr(void);
+off_t tar_endrd(void);
+int tar_trail(char *, int, int *);
+int tar_id(char *, int);
+int tar_opt(void);
+int tar_rd(ARCHD *, char *);
+int tar_wr(ARCHD *);
+int ustar_strd(void);
+int ustar_stwr(void);
+int ustar_id(char *, int);
+int ustar_rd(ARCHD *, char *);
+int ustar_wr(ARCHD *);
+int tar_gnutar_X_compat(const char *);
+int tar_gnutar_minus_minus_exclude(const char *);
+
+/*
+ * tty_subs.c
+ */
+int tty_init(void);
+void tty_prnt(const char *, ...)
+ __attribute__((format (printf, 1, 2)));
+int tty_read(char *, int);
+void tty_warn(int, const char *, ...)
+ __attribute__((format (printf, 2, 3)));
+void syswarn(int, int, const char *, ...)
+ __attribute__((format (printf, 3, 4)));
diff --git a/bin/pax/file_subs.c b/bin/pax/file_subs.c
new file mode 100644
index 0000000..cd421d0
--- /dev/null
+++ b/bin/pax/file_subs.c
@@ -0,0 +1,1156 @@
+/* $NetBSD: file_subs.c,v 1.63 2013/07/29 17:46:36 christos Exp $ */
+
+/*-
+ * Copyright (c) 1992 Keith Muller.
+ * Copyright (c) 1992, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Keith Muller of the University of California, San Diego.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#if HAVE_NBTOOL_CONFIG_H
+#include "nbtool_config.h"
+#endif
+
+#include <sys/cdefs.h>
+#if !defined(lint)
+#if 0
+static char sccsid[] = "@(#)file_subs.c 8.1 (Berkeley) 5/31/93";
+#else
+__RCSID("$NetBSD: file_subs.c,v 1.63 2013/07/29 17:46:36 christos Exp $");
+#endif
+#endif /* not lint */
+
+#include <sys/types.h>
+#include <sys/time.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <sys/param.h>
+#include <fcntl.h>
+#include <string.h>
+#include <stdio.h>
+#include <ctype.h>
+#include <errno.h>
+#include <sys/uio.h>
+#include <stdlib.h>
+#include "pax.h"
+#include "extern.h"
+#include "options.h"
+
+char *xtmp_name;
+
+static int
+mk_link(char *,struct stat *,char *, int);
+
+static int warn_broken;
+
+/*
+ * routines that deal with file operations such as: creating, removing;
+ * and setting access modes, uid/gid and times of files
+ */
+#define SET_BITS (S_ISUID | S_ISGID)
+#define FILE_BITS (S_IRWXU | S_IRWXG | S_IRWXO)
+#define A_BITS (FILE_BITS | SET_BITS | S_ISVTX)
+
+/*
+ * The S_ISVTX (sticky bit) can be set by non-superuser on directories
+ * but not other kinds of files.
+ */
+#define FILEBITS(dir) ((dir) ? (FILE_BITS | S_ISVTX) : FILE_BITS)
+#define SETBITS(dir) ((dir) ? SET_BITS : (SET_BITS | S_ISVTX))
+
+static mode_t
+apply_umask(mode_t mode)
+{
+ static mode_t cached_umask;
+ static int cached_umask_valid;
+
+ if (!cached_umask_valid) {
+ cached_umask = umask(0);
+ umask(cached_umask);
+ cached_umask_valid = 1;
+ }
+
+ return mode & ~cached_umask;
+}
+
+/*
+ * file_creat()
+ * Create and open a file.
+ * Return:
+ * file descriptor or -1 for failure
+ */
+
+int
+file_creat(ARCHD *arcn, int write_to_hardlink)
+{
+ int fd = -1;
+ int oerrno;
+
+ /*
+ * Some horribly busted tar implementations, have directory nodes
+ * that end in a /, but they mark as files. Compensate for that
+ * by not creating a directory node at this point, but a file node,
+ * and not creating the temp file.
+ */
+ if (arcn->nlen != 0 && arcn->name[arcn->nlen - 1] == '/') {
+ if (!warn_broken) {
+ tty_warn(0, "Archive was created with a broken tar;"
+ " file `%s' is a directory, but marked as plain.",
+ arcn->name);
+ warn_broken = 1;
+ }
+ return -1;
+ }
+
+ /*
+ * In "cpio" archives it's usually the last record of a set of
+ * hardlinks which includes the contents of the file. We cannot
+ * use a tempory file in that case because we couldn't link it
+ * with the existing other hardlinks after restoring the contents
+ * to it. And it's also useless to create the hardlink under a
+ * temporary name because the other hardlinks would have partial
+ * contents while restoring.
+ */
+ if (write_to_hardlink)
+ return (open(arcn->name, O_TRUNC | O_EXCL | O_RDWR, 0));
+
+ /*
+ * Create a temporary file name so that the file doesn't have partial
+ * contents while restoring.
+ */
+ arcn->tmp_name = malloc(arcn->nlen + 8);
+ if (arcn->tmp_name == NULL) {
+ syswarn(1, errno, "Cannot malloc %d bytes", arcn->nlen + 8);
+ return -1;
+ }
+ if (xtmp_name != NULL)
+ abort();
+ xtmp_name = arcn->tmp_name;
+
+ for (;;) {
+ /*
+ * try to create the temporary file we use to restore the
+ * contents info. if this fails, keep checking all the nodes
+ * in the path until chk_path() finds that it cannot fix
+ * anything further. if that happens we just give up.
+ */
+ (void)snprintf(arcn->tmp_name, arcn->nlen + 8, "%s.XXXXXX",
+ arcn->name);
+ fd = mkstemp(arcn->tmp_name);
+ if (fd >= 0)
+ break;
+ oerrno = errno;
+ if (nodirs || chk_path(arcn->name,arcn->sb.st_uid,arcn->sb.st_gid) < 0) {
+ (void)fflush(listf);
+ syswarn(1, oerrno, "Cannot create %s", arcn->tmp_name);
+ xtmp_name = NULL;
+ free(arcn->tmp_name);
+ arcn->tmp_name = NULL;
+ return -1;
+ }
+ }
+ return fd;
+}
+
+/*
+ * file_close()
+ * Close file descriptor to a file just created by pax. Sets modes,
+ * ownership and times as required.
+ * Return:
+ * 0 for success, -1 for failure
+ */
+
+void
+file_close(ARCHD *arcn, int fd)
+{
+ char *tmp_name;
+ int res;
+
+ if (fd < 0)
+ return;
+
+ tmp_name = (arcn->tmp_name != NULL) ? arcn->tmp_name : arcn->name;
+
+ if (close(fd) < 0)
+ syswarn(0, errno, "Cannot close file descriptor on %s",
+ tmp_name);
+
+ /*
+ * set owner/groups first as this may strip off mode bits we want
+ * then set file permission modes. Then set file access and
+ * modification times.
+ */
+ if (pids)
+ res = set_ids(tmp_name, arcn->sb.st_uid, arcn->sb.st_gid);
+ else
+ res = 0;
+
+ /*
+ * IMPORTANT SECURITY NOTE:
+ * if not preserving mode or we cannot set uid/gid, then PROHIBIT
+ * set uid/gid bits but restore the file modes (since mkstemp doesn't).
+ */
+ if (!pmode || res)
+ arcn->sb.st_mode &= ~SETBITS(0);
+ if (pmode)
+ set_pmode(tmp_name, arcn->sb.st_mode);
+ else
+ set_pmode(tmp_name,
+ apply_umask((arcn->sb.st_mode & FILEBITS(0))));
+ if (patime || pmtime)
+ set_ftime(tmp_name, arcn->sb.st_mtime,
+ arcn->sb.st_atime, 0, 0);
+
+ /* Did we write directly to the target file? */
+ if (arcn->tmp_name == NULL)
+ return;
+
+ /*
+ * Finally, now the temp file is fully instantiated rename it to
+ * the desired file name.
+ */
+ if (rename(tmp_name, arcn->name) < 0) {
+ syswarn(0, errno, "Cannot rename %s to %s",
+ tmp_name, arcn->name);
+ (void)unlink(tmp_name);
+ }
+
+#if HAVE_STRUCT_STAT_ST_FLAGS
+ if (pfflags && arcn->type != PAX_SLK)
+ set_chflags(arcn->name, arcn->sb.st_flags);
+#endif
+
+ free(arcn->tmp_name);
+ arcn->tmp_name = NULL;
+ xtmp_name = NULL;
+}
+
+/*
+ * lnk_creat()
+ * Create a hard link to arcn->ln_name from arcn->name. arcn->ln_name
+ * must exist;
+ * Return:
+ * 0 if ok, -1 otherwise
+ */
+
+int
+lnk_creat(ARCHD *arcn, int *payload)
+{
+ struct stat sb;
+
+ /*
+ * Check if this hardlink carries the "payload". In "cpio" archives
+ * it's usually the last record of a set of hardlinks which includes
+ * the contents of the file.
+ *
+ */
+ *payload = S_ISREG(arcn->sb.st_mode) &&
+ (arcn->sb.st_size > 0) && (arcn->sb.st_size <= arcn->skip);
+
+ /*
+ * We may be running as root, so we have to be sure that link target
+ * is not a directory, so we lstat and check. XXX: This is still racy.
+ */
+ if (lstat(arcn->ln_name, &sb) != -1 && S_ISDIR(sb.st_mode)) {
+ tty_warn(1, "A hard link to the directory %s is not allowed",
+ arcn->ln_name);
+ return -1;
+ }
+
+ return mk_link(arcn->ln_name, &sb, arcn->name, 0);
+}
+
+/*
+ * cross_lnk()
+ * Create a hard link to arcn->org_name from arcn->name. Only used in copy
+ * with the -l flag. No warning or error if this does not succeed (we will
+ * then just create the file)
+ * Return:
+ * 1 if copy() should try to create this file node
+ * 0 if cross_lnk() ok, -1 for fatal flaw (like linking to self).
+ */
+
+int
+cross_lnk(ARCHD *arcn)
+{
+ /*
+ * try to make a link to original file (-l flag in copy mode). make
+ * sure we do not try to link to directories in case we are running as
+ * root (and it might succeed).
+ */
+ if (arcn->type == PAX_DIR)
+ return 1;
+ return mk_link(arcn->org_name, &(arcn->sb), arcn->name, 1);
+}
+
+/*
+ * chk_same()
+ * In copy mode if we are not trying to make hard links between the src
+ * and destinations, make sure we are not going to overwrite ourselves by
+ * accident. This slows things down a little, but we have to protect all
+ * those people who make typing errors.
+ * Return:
+ * 1 the target does not exist, go ahead and copy
+ * 0 skip it file exists (-k) or may be the same as source file
+ */
+
+int
+chk_same(ARCHD *arcn)
+{
+ struct stat sb;
+
+ /*
+ * if file does not exist, return. if file exists and -k, skip it
+ * quietly
+ */
+ if (lstat(arcn->name, &sb) < 0)
+ return 1;
+ if (kflag)
+ return 0;
+
+ /*
+ * better make sure the user does not have src == dest by mistake
+ */
+ if ((arcn->sb.st_dev == sb.st_dev) && (arcn->sb.st_ino == sb.st_ino)) {
+ tty_warn(1, "Unable to copy %s, file would overwrite itself",
+ arcn->name);
+ return 0;
+ }
+ return 1;
+}
+
+/*
+ * mk_link()
+ * try to make a hard link between two files. if ign set, we do not
+ * complain.
+ * Return:
+ * 0 if successful (or we are done with this file but no error, such as
+ * finding the from file exists and the user has set -k).
+ * 1 when ign was set to indicates we could not make the link but we
+ * should try to copy/extract the file as that might work (and is an
+ * allowed option). -1 an error occurred.
+ */
+
+static int
+mk_link(char *to, struct stat *to_sb, char *from, int ign)
+{
+ struct stat sb;
+ int oerrno;
+
+ /*
+ * if from file exists, it has to be unlinked to make the link. If the
+ * file exists and -k is set, skip it quietly
+ */
+ if (lstat(from, &sb) == 0) {
+ if (kflag)
+ return 0;
+
+ /*
+ * make sure it is not the same file, protect the user
+ */
+ if ((to_sb->st_dev==sb.st_dev)&&(to_sb->st_ino == sb.st_ino)) {
+ tty_warn(1, "Cannot link file %s to itself", to);
+ return -1;
+ }
+
+ /*
+ * try to get rid of the file, based on the type
+ */
+ if (S_ISDIR(sb.st_mode) && strcmp(from, ".") != 0) {
+ if (rmdir(from) < 0) {
+ syswarn(1, errno, "Cannot remove %s", from);
+ return -1;
+ }
+ } else if (unlink(from) < 0) {
+ if (!ign) {
+ syswarn(1, errno, "Cannot remove %s", from);
+ return -1;
+ }
+ return 1;
+ }
+ }
+
+ /*
+ * from file is gone (or did not exist), try to make the hard link.
+ * if it fails, check the path and try it again (if chk_path() says to
+ * try again)
+ */
+ for (;;) {
+ if (link(to, from) == 0)
+ break;
+ oerrno = errno;
+ if (chk_path(from, to_sb->st_uid, to_sb->st_gid) == 0)
+ continue;
+ if (!ign) {
+ syswarn(1, oerrno, "Cannot link to %s from %s", to,
+ from);
+ return -1;
+ }
+ return 1;
+ }
+
+ /*
+ * all right the link was made
+ */
+ return 0;
+}
+
+/*
+ * node_creat()
+ * create an entry in the file system (other than a file or hard link).
+ * If successful, sets uid/gid modes and times as required.
+ * Return:
+ * 0 if ok, -1 otherwise
+ */
+
+int
+node_creat(ARCHD *arcn)
+{
+ int res;
+ int ign = 0;
+ int oerrno;
+ int pass = 0;
+ mode_t file_mode;
+ struct stat sb;
+ char target[MAXPATHLEN];
+ char *nm = arcn->name;
+ int len;
+
+ /*
+ * create node based on type, if that fails try to unlink the node and
+ * try again. finally check the path and try again. As noted in the
+ * file and link creation routines, this method seems to exhibit the
+ * best performance in general use workloads.
+ */
+ file_mode = arcn->sb.st_mode & FILEBITS(arcn->type == PAX_DIR);
+
+ for (;;) {
+ switch (arcn->type) {
+ case PAX_DIR:
+ /*
+ * If -h (or -L) was given in tar-mode, follow the
+ * potential symlink chain before trying to create the
+ * directory.
+ */
+ if (strcmp(NM_TAR, argv0) == 0 && Lflag) {
+ while (lstat(nm, &sb) == 0 &&
+ S_ISLNK(sb.st_mode)) {
+ len = readlink(nm, target,
+ sizeof target - 1);
+ if (len == -1) {
+ syswarn(0, errno,
+ "cannot follow symlink %s "
+ "in chain for %s",
+ nm, arcn->name);
+ res = -1;
+ goto badlink;
+ }
+ target[len] = '\0';
+ nm = target;
+ }
+ }
+ res = domkdir(nm, file_mode);
+badlink:
+ if (ign)
+ res = 0;
+ break;
+ case PAX_CHR:
+ file_mode |= S_IFCHR;
+ res = mknod(nm, file_mode, arcn->sb.st_rdev);
+ break;
+ case PAX_BLK:
+ file_mode |= S_IFBLK;
+ res = mknod(nm, file_mode, arcn->sb.st_rdev);
+ break;
+ case PAX_FIF:
+ res = mkfifo(nm, file_mode);
+ break;
+ case PAX_SCK:
+ /*
+ * Skip sockets, operation has no meaning under BSD
+ */
+ tty_warn(0,
+ "%s skipped. Sockets cannot be copied or extracted",
+ nm);
+ return (-1);
+ case PAX_SLK:
+ res = symlink(arcn->ln_name, nm);
+ break;
+ case PAX_CTG:
+ case PAX_HLK:
+ case PAX_HRG:
+ case PAX_REG:
+ default:
+ /*
+ * we should never get here
+ */
+ tty_warn(0, "%s has an unknown file type, skipping",
+ nm);
+ return (-1);
+ }
+
+ /*
+ * if we were able to create the node break out of the loop,
+ * otherwise try to unlink the node and try again. if that
+ * fails check the full path and try a final time.
+ */
+ if (res == 0)
+ break;
+
+ /*
+ * we failed to make the node
+ */
+ oerrno = errno;
+ switch (pass++) {
+ case 0:
+ if ((ign = unlnk_exist(nm, arcn->type)) < 0)
+ return (-1);
+ continue;
+
+ case 1:
+ if (nodirs ||
+ chk_path(nm, arcn->sb.st_uid,
+ arcn->sb.st_gid) < 0) {
+ syswarn(1, oerrno, "Cannot create %s", nm);
+ return (-1);
+ }
+ continue;
+ }
+
+ /*
+ * it must be a file that exists but we can't create or
+ * remove, but we must avoid the infinite loop.
+ */
+ break;
+ }
+
+ /*
+ * we were able to create the node. set uid/gid, modes and times
+ */
+ if (pids)
+ res = set_ids(nm, arcn->sb.st_uid, arcn->sb.st_gid);
+ else
+ res = 0;
+
+ /*
+ * IMPORTANT SECURITY NOTE:
+ * if not preserving mode or we cannot set uid/gid, then PROHIBIT any
+ * set uid/gid bits
+ */
+ if (!pmode || res)
+ arcn->sb.st_mode &= ~SETBITS(arcn->type == PAX_DIR);
+ if (pmode)
+ set_pmode(arcn->name, arcn->sb.st_mode);
+
+ if (arcn->type == PAX_DIR && strcmp(NM_CPIO, argv0) != 0) {
+ /*
+ * Dirs must be processed again at end of extract to set times
+ * and modes to agree with those stored in the archive. However
+ * to allow extract to continue, we may have to also set owner
+ * rights. This allows nodes in the archive that are children
+ * of this directory to be extracted without failure. Both time
+ * and modes will be fixed after the entire archive is read and
+ * before pax exits.
+ */
+ if (access(nm, R_OK | W_OK | X_OK) < 0) {
+ if (lstat(nm, &sb) < 0) {
+ syswarn(0, errno,"Cannot access %s (stat)",
+ arcn->name);
+ set_pmode(nm,file_mode | S_IRWXU);
+ } else {
+ /*
+ * We have to add rights to the dir, so we make
+ * sure to restore the mode. The mode must be
+ * restored AS CREATED and not as stored if
+ * pmode is not set.
+ */
+ set_pmode(nm, ((sb.st_mode &
+ FILEBITS(arcn->type == PAX_DIR)) |
+ S_IRWXU));
+ if (!pmode)
+ arcn->sb.st_mode = sb.st_mode;
+ }
+
+ /*
+ * we have to force the mode to what was set here,
+ * since we changed it from the default as created.
+ */
+ add_dir(nm, arcn->nlen, &(arcn->sb), 1);
+ } else if (pmode || patime || pmtime)
+ add_dir(nm, arcn->nlen, &(arcn->sb), 0);
+ }
+
+ if (patime || pmtime)
+ set_ftime(arcn->name, arcn->sb.st_mtime,
+ arcn->sb.st_atime, 0, (arcn->type == PAX_SLK) ? 1 : 0);
+
+#if HAVE_STRUCT_STAT_ST_FLAGS
+ if (pfflags && arcn->type != PAX_SLK)
+ set_chflags(arcn->name, arcn->sb.st_flags);
+#endif
+ return 0;
+}
+
+/*
+ * unlnk_exist()
+ * Remove node from file system with the specified name. We pass the type
+ * of the node that is going to replace it. When we try to create a
+ * directory and find that it already exists, we allow processing to
+ * continue as proper modes etc will always be set for it later on.
+ * Return:
+ * 0 is ok to proceed, no file with the specified name exists
+ * -1 we were unable to remove the node, or we should not remove it (-k)
+ * 1 we found a directory and we were going to create a directory.
+ */
+
+int
+unlnk_exist(char *name, int type)
+{
+ struct stat sb;
+
+ /*
+ * the file does not exist, or -k we are done
+ */
+ if (lstat(name, &sb) < 0)
+ return 0;
+ if (kflag)
+ return -1;
+
+ if (S_ISDIR(sb.st_mode)) {
+ /*
+ * try to remove a directory, if it fails and we were going to
+ * create a directory anyway, tell the caller (return a 1).
+ *
+ * don't try to remove the directory if the name is "."
+ * otherwise later file/directory creation fails.
+ */
+ if (strcmp(name, ".") == 0)
+ return 1;
+ if (rmdir(name) < 0) {
+ if (type == PAX_DIR)
+ return 1;
+ syswarn(1, errno, "Cannot remove directory %s", name);
+ return -1;
+ }
+ return 0;
+ }
+
+ /*
+ * try to get rid of all non-directory type nodes
+ */
+ if (unlink(name) < 0) {
+ (void)fflush(listf);
+ syswarn(1, errno, "Cannot unlink %s", name);
+ return -1;
+ }
+ return 0;
+}
+
+/*
+ * chk_path()
+ * We were trying to create some kind of node in the file system and it
+ * failed. chk_path() makes sure the path up to the node exists and is
+ * writable. When we have to create a directory that is missing along the
+ * path somewhere, the directory we create will be set to the same
+ * uid/gid as the file has (when uid and gid are being preserved).
+ * NOTE: this routine is a real performance loss. It is only used as a
+ * last resort when trying to create entries in the file system.
+ * Return:
+ * -1 when it could find nothing it is allowed to fix.
+ * 0 otherwise
+ */
+
+int
+chk_path(char *name, uid_t st_uid, gid_t st_gid)
+{
+ char *spt = name;
+ struct stat sb;
+ int retval = -1;
+
+ /*
+ * watch out for paths with nodes stored directly in / (e.g. /bozo)
+ */
+ if (*spt == '/')
+ ++spt;
+
+ for(;;) {
+ /*
+ * work forward from the first / and check each part of
+ * the path
+ */
+ spt = strchr(spt, '/');
+ if (spt == NULL)
+ break;
+ *spt = '\0';
+
+ /*
+ * if it exists we assume it is a directory, it is not within
+ * the spec (at least it seems to read that way) to alter the
+ * file system for nodes NOT EXPLICITLY stored on the archive.
+ * If that assumption is changed, you would test the node here
+ * and figure out how to get rid of it (probably like some
+ * recursive unlink()) or fix up the directory permissions if
+ * required (do an access()).
+ */
+ if (lstat(name, &sb) == 0) {
+ *(spt++) = '/';
+ continue;
+ }
+
+ /*
+ * the path fails at this point, see if we can create the
+ * needed directory and continue on
+ */
+ if (domkdir(name, S_IRWXU | S_IRWXG | S_IRWXO) == -1) {
+ *spt = '/';
+ retval = -1;
+ break;
+ }
+
+ /*
+ * we were able to create the directory. We will tell the
+ * caller that we found something to fix, and it is ok to try
+ * and create the node again.
+ */
+ retval = 0;
+ if (pids)
+ (void)set_ids(name, st_uid, st_gid);
+
+ /*
+ * make sure the user doesn't have some strange umask that
+ * causes this newly created directory to be unusable. We fix
+ * the modes and restore them back to the creation default at
+ * the end of pax
+ */
+ if ((access(name, R_OK | W_OK | X_OK) < 0) &&
+ (lstat(name, &sb) == 0)) {
+ set_pmode(name, ((sb.st_mode & FILEBITS(0)) |
+ S_IRWXU));
+ add_dir(name, spt - name, &sb, 1);
+ }
+ *(spt++) = '/';
+ continue;
+ }
+ /*
+ * We perform one final check here, because if someone else
+ * created the directory in parallel with us, we might return
+ * the wrong error code, even if the directory exists now.
+ */
+ if (retval == -1 && stat(name, &sb) == 0 && S_ISDIR(sb.st_mode))
+ retval = 0;
+ return retval;
+}
+
+/*
+ * set_ftime()
+ * Set the access time and modification time for a named file. If frc
+ * is non-zero we force these times to be set even if the user did not
+ * request access and/or modification time preservation (this is also
+ * used by -t to reset access times).
+ * When ign is zero, only those times the user has asked for are set, the
+ * other ones are left alone. We do not assume the un-documented feature
+ * of many utimes() implementations that consider a 0 time value as a do
+ * not set request.
+ *
+ * Unfortunately, there are systems where lutimes() is present but does
+ * not work on some filesystem types, which cannot be detected at
+ * compile time. This requires passing down symlink knowledge into
+ * this function to obtain correct operation. Linux with XFS is one
+ * example of such a system.
+ */
+
+void
+set_ftime(char *fnm, time_t mtime, time_t atime, int frc, int slk)
+{
+ struct timeval tv[2];
+ struct stat sb;
+
+ tv[0].tv_sec = atime;
+ tv[0].tv_usec = 0;
+ tv[1].tv_sec = mtime;
+ tv[1].tv_usec = 0;
+ if (!frc && (!patime || !pmtime)) {
+ /*
+ * if we are not forcing, only set those times the user wants
+ * set. We get the current values of the times if we need them.
+ */
+ if (lstat(fnm, &sb) == 0) {
+#if BSD4_4 && !HAVE_NBTOOL_CONFIG_H
+ if (!patime)
+ TIMESPEC_TO_TIMEVAL(&tv[0], &sb.st_atimespec);
+ if (!pmtime)
+ TIMESPEC_TO_TIMEVAL(&tv[1], &sb.st_mtimespec);
+#else
+ if (!patime)
+ tv[0].tv_sec = sb.st_atime;
+ if (!pmtime)
+ tv[1].tv_sec = sb.st_mtime;
+#endif
+ } else
+ syswarn(0, errno, "Cannot obtain file stats %s", fnm);
+ }
+
+ /*
+ * set the times
+ */
+#if HAVE_LUTIMES
+ if (lutimes(fnm, tv) == 0)
+ return;
+ if (errno != ENOSYS) /* XXX linux: lutimes is per-FS */
+ goto bad;
+#endif
+ if (slk)
+ return;
+ if (utimes(fnm, tv) == -1)
+ goto bad;
+ return;
+bad:
+ syswarn(1, errno, "Access/modification time set failed on: %s", fnm);
+}
+
+/*
+ * set_ids()
+ * set the uid and gid of a file system node
+ * Return:
+ * 0 when set, -1 on failure
+ */
+
+int
+set_ids(char *fnm, uid_t uid, gid_t gid)
+{
+ if (geteuid() == 0)
+ if (lchown(fnm, uid, gid)) {
+ (void)fflush(listf);
+ syswarn(1, errno, "Cannot set file uid/gid of %s",
+ fnm);
+ return -1;
+ }
+ return 0;
+}
+
+/*
+ * set_pmode()
+ * Set file access mode
+ */
+
+void
+set_pmode(char *fnm, mode_t mode)
+{
+ mode &= A_BITS;
+ if (lchmod(fnm, mode)) {
+ (void)fflush(listf);
+ syswarn(1, errno, "Cannot set permissions on %s", fnm);
+ }
+ return;
+}
+
+/*
+ * set_chflags()
+ * Set 4.4BSD file flags
+ */
+void
+set_chflags(char *fnm, u_int32_t flags)
+{
+
+#if 0
+ if (chflags(fnm, flags) < 0 && errno != EOPNOTSUPP)
+ syswarn(1, errno, "Cannot set file flags on %s", fnm);
+#endif
+ return;
+}
+
+/*
+ * file_write()
+ * Write/copy a file (during copy or archive extract). This routine knows
+ * how to copy files with lseek holes in it. (Which are read as file
+ * blocks containing all 0's but do not have any file blocks associated
+ * with the data). Typical examples of these are files created by dbm
+ * variants (.pag files). While the file size of these files are huge, the
+ * actual storage is quite small (the files are sparse). The problem is
+ * the holes read as all zeros so are probably stored on the archive that
+ * way (there is no way to determine if the file block is really a hole,
+ * we only know that a file block of all zero's can be a hole).
+ * At this writing, no major archive format knows how to archive files
+ * with holes. However, on extraction (or during copy, -rw) we have to
+ * deal with these files. Without detecting the holes, the files can
+ * consume a lot of file space if just written to disk. This replacement
+ * for write when passed the basic allocation size of a file system block,
+ * uses lseek whenever it detects the input data is all 0 within that
+ * file block. In more detail, the strategy is as follows:
+ * While the input is all zero keep doing an lseek. Keep track of when we
+ * pass over file block boundaries. Only write when we hit a non zero
+ * input. once we have written a file block, we continue to write it to
+ * the end (we stop looking at the input). When we reach the start of the
+ * next file block, start checking for zero blocks again. Working on file
+ * block boundaries significantly reduces the overhead when copying files
+ * that are NOT very sparse. This overhead (when compared to a write) is
+ * almost below the measurement resolution on many systems. Without it,
+ * files with holes cannot be safely copied. It does has a side effect as
+ * it can put holes into files that did not have them before, but that is
+ * not a problem since the file contents are unchanged (in fact it saves
+ * file space). (Except on paging files for diskless clients. But since we
+ * cannot determine one of those file from here, we ignore them). If this
+ * ever ends up on a system where CTG files are supported and the holes
+ * are not desired, just do a conditional test in those routines that
+ * call file_write() and have it call write() instead. BEFORE CLOSING THE
+ * FILE, make sure to call file_flush() when the last write finishes with
+ * an empty block. A lot of file systems will not create an lseek hole at
+ * the end. In this case we drop a single 0 at the end to force the
+ * trailing 0's in the file.
+ * ---Parameters---
+ * rem: how many bytes left in this file system block
+ * isempt: have we written to the file block yet (is it empty)
+ * sz: basic file block allocation size
+ * cnt: number of bytes on this write
+ * str: buffer to write
+ * Return:
+ * number of bytes written, -1 on write (or lseek) error.
+ */
+
+int
+file_write(int fd, char *str, int cnt, int *rem, int *isempt, int sz,
+ char *name)
+{
+ char *pt;
+ char *end;
+ int wcnt;
+ char *st = str;
+ char **strp;
+ size_t *lenp;
+
+ /*
+ * while we have data to process
+ */
+ while (cnt) {
+ if (!*rem) {
+ /*
+ * We are now at the start of file system block again
+ * (or what we think one is...). start looking for
+ * empty blocks again
+ */
+ *isempt = 1;
+ *rem = sz;
+ }
+
+ /*
+ * only examine up to the end of the current file block or
+ * remaining characters to write, whatever is smaller
+ */
+ wcnt = MIN(cnt, *rem);
+ cnt -= wcnt;
+ *rem -= wcnt;
+ if (*isempt) {
+ /*
+ * have not written to this block yet, so we keep
+ * looking for zero's
+ */
+ pt = st;
+ end = st + wcnt;
+
+ /*
+ * look for a zero filled buffer
+ */
+ while ((pt < end) && (*pt == '\0'))
+ ++pt;
+
+ if (pt == end) {
+ /*
+ * skip, buf is empty so far
+ */
+ if (fd > -1 &&
+ lseek(fd, (off_t)wcnt, SEEK_CUR) < 0) {
+ syswarn(1, errno, "File seek on %s",
+ name);
+ return -1;
+ }
+ st = pt;
+ continue;
+ }
+ /*
+ * drat, the buf is not zero filled
+ */
+ *isempt = 0;
+ }
+
+ /*
+ * have non-zero data in this file system block, have to write
+ */
+ switch (fd) {
+ case -PAX_GLF:
+ strp = &gnu_name_string;
+ lenp = &gnu_name_length;
+ break;
+ case -PAX_GLL:
+ strp = &gnu_link_string;
+ lenp = &gnu_link_length;
+ break;
+ default:
+ strp = NULL;
+ lenp = NULL;
+ break;
+ }
+ if (strp) {
+ char *nstr = *strp ? realloc(*strp, *lenp + wcnt + 1) :
+ malloc(wcnt + 1);
+ if (nstr == NULL) {
+ tty_warn(1, "Out of memory");
+ return -1;
+ }
+ (void)strlcpy(&nstr[*lenp], st, wcnt + 1);
+ *strp = nstr;
+ *lenp += wcnt;
+ } else if (xwrite(fd, st, wcnt) != wcnt) {
+ syswarn(1, errno, "Failed write to file %s", name);
+ return -1;
+ }
+ st += wcnt;
+ }
+ return st - str;
+}
+
+/*
+ * file_flush()
+ * when the last file block in a file is zero, many file systems will not
+ * let us create a hole at the end. To get the last block with zeros, we
+ * write the last BYTE with a zero (back up one byte and write a zero).
+ */
+
+void
+file_flush(int fd, char *fname, int isempt)
+{
+ static char blnk[] = "\0";
+
+ /*
+ * silly test, but make sure we are only called when the last block is
+ * filled with all zeros.
+ */
+ if (!isempt)
+ return;
+
+ /*
+ * move back one byte and write a zero
+ */
+ if (lseek(fd, (off_t)-1, SEEK_CUR) < 0) {
+ syswarn(1, errno, "Failed seek on file %s", fname);
+ return;
+ }
+
+ if (write_with_restart(fd, blnk, 1) < 0)
+ syswarn(1, errno, "Failed write to file %s", fname);
+ return;
+}
+
+/*
+ * rdfile_close()
+ * close a file we have been reading (to copy or archive). If we have to
+ * reset access time (tflag) do so (the times are stored in arcn).
+ */
+
+void
+rdfile_close(ARCHD *arcn, int *fd)
+{
+ /*
+ * make sure the file is open
+ */
+ if (*fd < 0)
+ return;
+
+ (void)close(*fd);
+ *fd = -1;
+ if (!tflag)
+ return;
+
+ /*
+ * user wants last access time reset
+ */
+ set_ftime(arcn->org_name, arcn->sb.st_mtime, arcn->sb.st_atime, 1, 0);
+ return;
+}
+
+/*
+ * set_crc()
+ * read a file to calculate its crc. This is a real drag. Archive formats
+ * that have this, end up reading the file twice (we have to write the
+ * header WITH the crc before writing the file contents. Oh well...
+ * Return:
+ * 0 if was able to calculate the crc, -1 otherwise
+ */
+
+int
+set_crc(ARCHD *arcn, int fd)
+{
+ int i;
+ int res;
+ off_t cpcnt = 0L;
+ u_long size;
+ unsigned long crc = 0L;
+ char tbuf[FILEBLK];
+ struct stat sb;
+
+ if (fd < 0) {
+ /*
+ * hmm, no fd, should never happen. well no crc then.
+ */
+ arcn->crc = 0L;
+ return 0;
+ }
+
+ if ((size = (u_long)arcn->sb.st_blksize) > (u_long)sizeof(tbuf))
+ size = (u_long)sizeof(tbuf);
+
+ /*
+ * read all the bytes we think that there are in the file. If the user
+ * is trying to archive an active file, forget this file.
+ */
+ for(;;) {
+ if ((res = read(fd, tbuf, size)) <= 0)
+ break;
+ cpcnt += res;
+ for (i = 0; i < res; ++i)
+ crc += (tbuf[i] & 0xff);
+ }
+
+ /*
+ * safety check. we want to avoid archiving files that are active as
+ * they can create inconsistent archive copies.
+ */
+ if (cpcnt != arcn->sb.st_size)
+ tty_warn(1, "File changed size %s", arcn->org_name);
+ else if (fstat(fd, &sb) < 0)
+ syswarn(1, errno, "Failed stat on %s", arcn->org_name);
+ else if (arcn->sb.st_mtime != sb.st_mtime)
+ tty_warn(1, "File %s was modified during read", arcn->org_name);
+ else if (lseek(fd, (off_t)0L, SEEK_SET) < 0)
+ syswarn(1, errno, "File rewind failed on: %s", arcn->org_name);
+ else {
+ arcn->crc = crc;
+ return 0;
+ }
+ return -1;
+}
diff --git a/bin/pax/ftree.c b/bin/pax/ftree.c
new file mode 100644
index 0000000..0d45429
--- /dev/null
+++ b/bin/pax/ftree.c
@@ -0,0 +1,741 @@
+/* $NetBSD: ftree.c,v 1.42 2012/09/27 00:44:59 christos Exp $ */
+
+/*-
+ * Copyright (c) 1992 Keith Muller.
+ * Copyright (c) 1992, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Keith Muller of the University of California, San Diego.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+/*-
+ * Copyright (c) 2001 The NetBSD Foundation, Inc.
+ * All rights reserved.
+ *
+ * This code is derived from software contributed to The NetBSD Foundation
+ * by Luke Mewburn of Wasabi Systems.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
+ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#if HAVE_NBTOOL_CONFIG_H
+#include "nbtool_config.h"
+#endif
+
+#include <sys/cdefs.h>
+#if !defined(lint)
+#if 0
+static char sccsid[] = "@(#)ftree.c 8.2 (Berkeley) 4/18/94";
+#else
+__RCSID("$NetBSD: ftree.c,v 1.42 2012/09/27 00:44:59 christos Exp $");
+#endif
+#endif /* not lint */
+
+#include <sys/types.h>
+#include <sys/time.h>
+#include <sys/stat.h>
+#include <sys/param.h>
+#include <ctype.h>
+#include <errno.h>
+#include <fts.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include "pax.h"
+#include "ftree.h"
+#include "extern.h"
+#include "options.h"
+#ifndef SMALL
+#include "mtree.h"
+#endif /* SMALL */
+
+/*
+ * routines to interface with the fts library function.
+ *
+ * file args supplied to pax are stored on a single linked list (of type FTREE)
+ * and given to fts to be processed one at a time. pax "selects" files from
+ * the expansion of each arg into the corresponding file tree (if the arg is a
+ * directory, otherwise the node itself is just passed to pax). The selection
+ * is modified by the -n and -u flags. The user is informed when a specific
+ * file arg does not generate any selected files. -n keeps expanding the file
+ * tree arg until one of its files is selected, then skips to the next file
+ * arg. when the user does not supply the file trees as command line args to
+ * pax, they are read from stdin
+ */
+
+static FTS *ftsp = NULL; /* current FTS handle */
+static int ftsopts; /* options to be used on fts_open */
+static char *farray[2]; /* array for passing each arg to fts */
+static FTREE *fthead = NULL; /* head of linked list of file args */
+static FTREE *fttail = NULL; /* tail of linked list of file args */
+static FTREE *ftcur = NULL; /* current file arg being processed */
+static FTSENT *ftent = NULL; /* current file tree entry */
+static int ftree_skip; /* when set skip to next file arg */
+#ifndef SMALL
+static NODE *ftnode = NULL; /* mtree(8) specfile; used by -M */
+#endif /* SMALL */
+
+static int ftree_arg(void);
+
+#define FTS_ERRNO(x) (x)->fts_errno
+
+/*
+ * ftree_start()
+ * initialize the options passed to fts_open() during this run of pax
+ * options are based on the selection of pax options by the user
+ * fts_start() also calls fts_arg() to open the first valid file arg. We
+ * also attempt to reset directory access times when -t (tflag) is set.
+ * Return:
+ * 0 if there is at least one valid file arg to process, -1 otherwise
+ */
+
+int
+ftree_start(void)
+{
+
+#ifndef SMALL
+ /*
+ * if -M is given, the list of filenames on stdin is actually
+ * an mtree(8) specfile, so parse the specfile into a NODE *
+ * tree at ftnode, for use by next_file()
+ */
+ if (Mflag) {
+ if (fthead != NULL) {
+ tty_warn(1,
+ "The -M flag is only supported when reading file list from stdin");
+ return -1;
+ }
+ ftnode = spec(stdin);
+ if (ftnode != NULL &&
+ (ftnode->type != F_DIR || strcmp(ftnode->name, ".") != 0)) {
+ tty_warn(1,
+ "First node of specfile is not `.' directory");
+ return -1;
+ }
+ return 0;
+ }
+#endif /* SMALL */
+
+ /*
+ * set up the operation mode of fts, open the first file arg. We must
+ * use FTS_NOCHDIR, as the user may have to open multiple archives and
+ * if fts did a chdir off into the boondocks, we may create an archive
+ * volume in an place where the user did not expect to.
+ */
+ ftsopts = FTS_NOCHDIR;
+
+ /*
+ * optional user flags that effect file traversal
+ * -H command line symlink follow only (half follow)
+ * -L follow sylinks (logical)
+ * -P do not follow sylinks (physical). This is the default.
+ * -X do not cross over mount points
+ * -t preserve access times on files read.
+ * -n select only the first member of a file tree when a match is found
+ * -d do not extract subtrees rooted at a directory arg.
+ */
+ if (Lflag)
+ ftsopts |= FTS_LOGICAL;
+ else
+ ftsopts |= FTS_PHYSICAL;
+ if (Hflag)
+ ftsopts |= FTS_COMFOLLOW;
+ if (Xflag)
+ ftsopts |= FTS_XDEV;
+
+ if ((fthead == NULL) && ((farray[0] = malloc(PAXPATHLEN+2)) == NULL)) {
+ tty_warn(1, "Unable to allocate memory for file name buffer");
+ return -1;
+ }
+
+ if (ftree_arg() < 0)
+ return -1;
+ if (tflag && (atdir_start() < 0))
+ return -1;
+ return 0;
+}
+
+/*
+ * ftree_add()
+ * add the arg to the linked list of files to process. Each will be
+ * processed by fts one at a time
+ * Return:
+ * 0 if added to the linked list, -1 if failed
+ */
+
+int
+ftree_add(char *str, int isdir)
+{
+ FTREE *ft;
+ int len;
+
+ /*
+ * simple check for bad args
+ */
+ if ((str == NULL) || (*str == '\0')) {
+ tty_warn(0, "Invalid file name argument");
+ return -1;
+ }
+
+ /*
+ * allocate FTREE node and add to the end of the linked list (args are
+ * processed in the same order they were passed to pax). Get rid of any
+ * trailing / the user may pass us. (watch out for / by itself).
+ */
+ if ((ft = (FTREE *)malloc(sizeof(FTREE))) == NULL) {
+ tty_warn(0, "Unable to allocate memory for filename");
+ return -1;
+ }
+
+ if (((len = strlen(str) - 1) > 0) && (str[len] == '/'))
+ str[len] = '\0';
+ ft->fname = str;
+ ft->refcnt = -isdir;
+ ft->fow = NULL;
+ if (fthead == NULL) {
+ fttail = fthead = ft;
+ return 0;
+ }
+ fttail->fow = ft;
+ fttail = ft;
+ return 0;
+}
+
+/*
+ * ftree_sel()
+ * this entry has been selected by pax. bump up reference count and handle
+ * -n and -d processing.
+ */
+
+void
+ftree_sel(ARCHD *arcn)
+{
+ /*
+ * set reference bit for this pattern. This linked list is only used
+ * when file trees are supplied pax as args. The list is not used when
+ * the trees are read from stdin.
+ */
+ if (ftcur != NULL)
+ ftcur->refcnt = 1;
+
+ /*
+ * if -n we are done with this arg, force a skip to the next arg when
+ * pax asks for the next file in next_file().
+ * if -M we don't use fts(3), so the rest of this function is moot.
+ * if -d we tell fts only to match the directory (if the arg is a dir)
+ * and not the entire file tree rooted at that point.
+ */
+ if (nflag)
+ ftree_skip = 1;
+
+ if (Mflag || !dflag || (arcn->type != PAX_DIR))
+ return;
+
+ if (ftent != NULL)
+ (void)fts_set(ftsp, ftent, FTS_SKIP);
+}
+
+/*
+ * ftree_chk()
+ * called at end on pax execution. Prints all those file args that did not
+ * have a selected member (reference count still 0)
+ */
+
+void
+ftree_chk(void)
+{
+ FTREE *ft;
+ int wban = 0;
+
+ /*
+ * make sure all dir access times were reset.
+ */
+ if (tflag)
+ atdir_end();
+
+ /*
+ * walk down list and check reference count. Print out those members
+ * that never had a match
+ */
+ for (ft = fthead; ft != NULL; ft = ft->fow) {
+ if (ft->refcnt != 0)
+ continue;
+ if (wban == 0) {
+ tty_warn(1,
+ "WARNING! These file names were not selected:");
+ ++wban;
+ }
+ (void)fprintf(stderr, "%s\n", ft->fname);
+ }
+}
+
+/*
+ * ftree_arg()
+ * Get the next file arg for fts to process. Can be from either the linked
+ * list or read from stdin when the user did not them as args to pax. Each
+ * arg is processed until the first successful fts_open().
+ * Return:
+ * 0 when the next arg is ready to go, -1 if out of file args (or EOF on
+ * stdin).
+ */
+
+static int
+ftree_arg(void)
+{
+ /*
+ * close off the current file tree
+ */
+ if (ftsp != NULL) {
+ (void)fts_close(ftsp);
+ ftsp = NULL;
+ ftent = NULL;
+ }
+
+ /*
+ * keep looping until we get a valid file tree to process. Stop when we
+ * reach the end of the list (or get an eof on stdin)
+ */
+ for(;;) {
+ if (fthead == NULL) {
+ int i, c = EOF;
+ /*
+ * the user didn't supply any args, get the file trees
+ * to process from stdin;
+ */
+ for (i = 0; i < PAXPATHLEN + 2;) {
+ c = getchar();
+ if (c == EOF)
+ break;
+ else if (c == sep) {
+ if (i != 0)
+ break;
+ } else
+ farray[0][i++] = c;
+ }
+ if (i == 0)
+ return -1;
+ farray[0][i] = '\0';
+ } else {
+ /*
+ * the user supplied the file args as arguments to pax
+ */
+ if (ftcur == NULL)
+ ftcur = fthead;
+ else if ((ftcur = ftcur->fow) == NULL)
+ return -1;
+
+ if (ftcur->refcnt < 0) {
+ /*
+ * chdir entry.
+ * Change directory and retry loop.
+ */
+ if (ar_dochdir(ftcur->fname))
+ return (-1);
+ continue;
+ }
+ farray[0] = ftcur->fname;
+ }
+
+ /*
+ * watch it, fts wants the file arg stored in a array of char
+ * ptrs, with the last one a null. we use a two element array
+ * and set farray[0] to point at the buffer with the file name
+ * in it. We cannot pass all the file args to fts at one shot
+ * as we need to keep a handle on which file arg generates what
+ * files (the -n and -d flags need this). If the open is
+ * successful, return a 0.
+ */
+ if ((ftsp = fts_open(farray, ftsopts, NULL)) != NULL)
+ break;
+ }
+ return 0;
+}
+
+/*
+ * next_file()
+ * supplies the next file to process in the supplied archd structure.
+ * Return:
+ * 0 when contents of arcn have been set with the next file, -1 when done.
+ */
+
+int
+next_file(ARCHD *arcn)
+{
+#ifndef SMALL
+ static char curdir[PAXPATHLEN+2], curpath[PAXPATHLEN+2];
+ static int curdirlen;
+
+ struct stat statbuf;
+ FTSENT Mftent;
+#endif /* SMALL */
+ int cnt;
+ time_t atime, mtime;
+ char *curlink;
+#define MFTENT_DUMMY_DEV UINT_MAX
+
+ curlink = NULL;
+#ifndef SMALL
+ /*
+ * if parsing an mtree(8) specfile, build up `dummy' ftsent
+ * from specfile info, and jump below to complete setup of arcn.
+ */
+ if (Mflag) {
+ int skipoptional;
+
+ next_ftnode:
+ skipoptional = 0;
+ if (ftnode == NULL) /* tree is empty */
+ return (-1);
+
+ /* get current name */
+ if (snprintf(curpath, sizeof(curpath), "%s%s%s",
+ curdir, curdirlen ? "/" : "", ftnode->name)
+ >= (int)sizeof(curpath)) {
+ tty_warn(1, "line %lu: %s: %s", (u_long)ftnode->lineno,
+ curdir, strerror(ENAMETOOLONG));
+ return (-1);
+ }
+ ftnode->flags |= F_VISIT; /* mark node visited */
+
+ /* construct dummy FTSENT */
+ Mftent.fts_path = curpath;
+ Mftent.fts_statp = &statbuf;
+ Mftent.fts_pointer = ftnode;
+ ftent = &Mftent;
+ /* look for existing file */
+ if (lstat(Mftent.fts_path, &statbuf) == -1) {
+ if (ftnode->flags & F_OPT)
+ skipoptional = 1;
+
+ /* missing: fake up stat info */
+ memset(&statbuf, 0, sizeof(statbuf));
+ statbuf.st_dev = MFTENT_DUMMY_DEV;
+ statbuf.st_ino = ftnode->lineno;
+ statbuf.st_size = 0;
+#define NODETEST(t, m) \
+ if (!(t)) { \
+ tty_warn(1, "line %lu: %s: %s not specified", \
+ (u_long)ftnode->lineno, \
+ ftent->fts_path, m); \
+ return -1; \
+ }
+ statbuf.st_mode = nodetoino(ftnode->type);
+ NODETEST(ftnode->flags & F_TYPE, "type");
+ NODETEST(ftnode->flags & F_MODE, "mode");
+ if (!(ftnode->flags & F_TIME))
+ statbuf.st_mtime = starttime;
+ NODETEST(ftnode->flags & (F_GID | F_GNAME), "group");
+ NODETEST(ftnode->flags & (F_UID | F_UNAME), "user");
+ if (ftnode->type == F_BLOCK || ftnode->type == F_CHAR)
+ NODETEST(ftnode->flags & F_DEV,
+ "device number");
+ if (ftnode->type == F_LINK)
+ NODETEST(ftnode->flags & F_SLINK, "symlink");
+ /* don't require F_FLAGS or F_SIZE */
+#undef NODETEST
+ } else {
+ if (ftnode->flags & F_TYPE && nodetoino(ftnode->type)
+ != (statbuf.st_mode & S_IFMT)) {
+ tty_warn(1,
+ "line %lu: %s: type mismatch: specfile %s, tree %s",
+ (u_long)ftnode->lineno, ftent->fts_path,
+ inotype(nodetoino(ftnode->type)),
+ inotype(statbuf.st_mode));
+ return -1;
+ }
+ if (ftnode->type == F_DIR && (ftnode->flags & F_OPT))
+ skipoptional = 1;
+ }
+ /*
+ * override settings with those from specfile
+ */
+ if (ftnode->flags & F_MODE) {
+ statbuf.st_mode &= ~ALLPERMS;
+ statbuf.st_mode |= (ftnode->st_mode & ALLPERMS);
+ }
+ if (ftnode->flags & (F_GID | F_GNAME))
+ statbuf.st_gid = ftnode->st_gid;
+ if (ftnode->flags & (F_UID | F_UNAME))
+ statbuf.st_uid = ftnode->st_uid;
+#if HAVE_STRUCT_STAT_ST_FLAGS
+ if (ftnode->flags & F_FLAGS)
+ statbuf.st_flags = ftnode->st_flags;
+#endif
+ if (ftnode->flags & F_TIME)
+#if BSD4_4 && !HAVE_NBTOOL_CONFIG_H
+ statbuf.st_mtimespec = ftnode->st_mtimespec;
+#else
+ statbuf.st_mtime = ftnode->st_mtimespec.tv_sec;
+#endif
+ if (ftnode->flags & F_DEV)
+ statbuf.st_rdev = ftnode->st_rdev;
+ if (ftnode->flags & F_SLINK)
+ curlink = ftnode->slink;
+ /* ignore F_SIZE */
+
+ /*
+ * find next node
+ */
+ if (ftnode->type == F_DIR && ftnode->child != NULL) {
+ /* directory with unseen child */
+ ftnode = ftnode->child;
+ curdirlen = strlcpy(curdir, curpath, sizeof(curdir));
+ } else do {
+ if (ftnode->next != NULL) {
+ /* next node at current level */
+ ftnode = ftnode->next;
+ } else { /* move back to parent */
+ /* reset time only on first cd.. */
+ if (Mftent.fts_pointer == ftnode && tflag &&
+ (get_atdir(MFTENT_DUMMY_DEV, ftnode->lineno,
+ &mtime, &atime) == 0)) {
+ set_ftime(ftent->fts_path,
+ mtime, atime, 1, 0);
+ }
+ ftnode = ftnode->parent;
+ if (ftnode->parent == ftnode)
+ ftnode = NULL;
+ else {
+ curdirlen -= strlen(ftnode->name) + 1;
+ curdir[curdirlen] = '\0';
+ }
+ }
+ } while (ftnode != NULL && ftnode->flags & F_VISIT);
+ if (skipoptional) /* skip optional entries */
+ goto next_ftnode;
+ goto got_ftent;
+ }
+#endif /* SMALL */
+
+ /*
+ * ftree_sel() might have set the ftree_skip flag if the user has the
+ * -n option and a file was selected from this file arg tree. (-n says
+ * only one member is matched for each pattern) ftree_skip being 1
+ * forces us to go to the next arg now.
+ */
+ if (ftree_skip) {
+ /*
+ * clear and go to next arg
+ */
+ ftree_skip = 0;
+ if (ftree_arg() < 0)
+ return -1;
+ }
+
+ if (ftsp == NULL)
+ return -1;
+ /*
+ * loop until we get a valid file to process
+ */
+ for(;;) {
+ if ((ftent = fts_read(ftsp)) == NULL) {
+ /*
+ * out of files in this tree, go to next arg, if none
+ * we are done
+ */
+ if (ftree_arg() < 0)
+ return -1;
+ continue;
+ }
+
+ /*
+ * handle each type of fts_read() flag
+ */
+ switch(ftent->fts_info) {
+ case FTS_D:
+ case FTS_DEFAULT:
+ case FTS_F:
+ case FTS_SL:
+ case FTS_SLNONE:
+ /*
+ * these are all ok
+ */
+ break;
+ case FTS_DP:
+ /*
+ * already saw this directory. If the user wants file
+ * access times reset, we use this to restore the
+ * access time for this directory since this is the
+ * last time we will see it in this file subtree
+ * remember to force the time (this is -t on a read
+ * directory, not a created directory).
+ */
+ if (!tflag || (get_atdir(
+ ftent->fts_statp->st_dev, ftent->fts_statp->st_ino,
+ &mtime, &atime) < 0))
+ continue;
+ set_ftime(ftent->fts_path, mtime, atime, 1, 0);
+ continue;
+ case FTS_DC:
+ /*
+ * fts claims a file system cycle
+ */
+ tty_warn(1,"File system cycle found at %s",
+ ftent->fts_path);
+ continue;
+ case FTS_DNR:
+ syswarn(1, FTS_ERRNO(ftent),
+ "Unable to read directory %s", ftent->fts_path);
+ continue;
+ case FTS_ERR:
+ syswarn(1, FTS_ERRNO(ftent),
+ "File system traversal error");
+ continue;
+ case FTS_NS:
+ case FTS_NSOK:
+ syswarn(1, FTS_ERRNO(ftent),
+ "Unable to access %s", ftent->fts_path);
+ continue;
+ }
+
+#ifndef SMALL
+ got_ftent:
+#endif /* SMALL */
+ /*
+ * ok got a file tree node to process. copy info into arcn
+ * structure (initialize as required)
+ */
+ arcn->skip = 0;
+ arcn->pad = 0;
+ arcn->ln_nlen = 0;
+ arcn->ln_name[0] = '\0';
+ arcn->sb = *(ftent->fts_statp);
+
+ /*
+ * file type based set up and copy into the arcn struct
+ * SIDE NOTE:
+ * we try to reset the access time on all files and directories
+ * we may read when the -t flag is specified. files are reset
+ * when we close them after copying. we reset the directories
+ * when we are done with their file tree (we also clean up at
+ * end in case we cut short a file tree traversal). However
+ * there is no way to reset access times on symlinks.
+ */
+ switch(S_IFMT & arcn->sb.st_mode) {
+ case S_IFDIR:
+ arcn->type = PAX_DIR;
+ if (!tflag)
+ break;
+ add_atdir(ftent->fts_path, arcn->sb.st_dev,
+ arcn->sb.st_ino, arcn->sb.st_mtime,
+ arcn->sb.st_atime);
+ break;
+ case S_IFCHR:
+ arcn->type = PAX_CHR;
+ break;
+ case S_IFBLK:
+ arcn->type = PAX_BLK;
+ break;
+ case S_IFREG:
+ /*
+ * only regular files with have data to store on the
+ * archive. all others will store a zero length skip.
+ * the skip field is used by pax for actual data it has
+ * to read (or skip over).
+ */
+ arcn->type = PAX_REG;
+ arcn->skip = arcn->sb.st_size;
+ break;
+ case S_IFLNK:
+ arcn->type = PAX_SLK;
+ if (curlink != NULL) {
+ cnt = strlcpy(arcn->ln_name, curlink,
+ sizeof(arcn->ln_name));
+ /*
+ * have to read the symlink path from the file
+ */
+ } else if ((cnt =
+ readlink(ftent->fts_path, arcn->ln_name,
+ sizeof(arcn->ln_name) - 1)) < 0) {
+ syswarn(1, errno, "Unable to read symlink %s",
+ ftent->fts_path);
+ continue;
+ }
+ /*
+ * set link name length, watch out readlink does not
+ * always null terminate the link path
+ */
+ arcn->ln_name[cnt] = '\0';
+ arcn->ln_nlen = cnt;
+ break;
+#ifdef S_IFSOCK
+ case S_IFSOCK:
+ /*
+ * under BSD storing a socket is senseless but we will
+ * let the format specific write function make the
+ * decision of what to do with it.
+ */
+ arcn->type = PAX_SCK;
+ break;
+#endif
+ case S_IFIFO:
+ arcn->type = PAX_FIF;
+ break;
+ }
+ break;
+ }
+
+ /*
+ * copy file name, set file name length
+ */
+ arcn->nlen = strlcpy(arcn->name, ftent->fts_path, sizeof(arcn->name));
+ arcn->org_name = arcn->fts_name;
+ strlcpy(arcn->fts_name, ftent->fts_path, sizeof arcn->fts_name);
+ if (strcmp(NM_CPIO, argv0) == 0) {
+ /*
+ * cpio does *not* descend directories listed in the
+ * arguments, unlike pax/tar, so needs special handling
+ * here. failure to do so results in massive amounts
+ * of duplicated files in the output. We kill fts after
+ * the first name is extracted, what a waste.
+ */
+ ftcur->refcnt = 1;
+ (void)ftree_arg();
+ }
+ return 0;
+}
diff --git a/bin/pax/ftree.h b/bin/pax/ftree.h
new file mode 100644
index 0000000..490cf51
--- /dev/null
+++ b/bin/pax/ftree.h
@@ -0,0 +1,48 @@
+/* $NetBSD: ftree.h,v 1.5 2003/10/13 07:41:22 agc Exp $ */
+
+/*-
+ * Copyright (c) 1992 Keith Muller.
+ * Copyright (c) 1992, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Keith Muller of the University of California, San Diego.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * @(#)ftree.h 8.1 (Berkeley) 5/31/93
+ */
+
+/*
+ * Data structure used by the ftree.c routines to store the file args to be
+ * handed to fts(). It keeps a reference count of which args generated a
+ * "selected" member
+ */
+
+typedef struct ftree {
+ char *fname; /* file tree name */
+ int refcnt; /* has tree had a selected file? */
+ struct ftree *fow; /* pointer to next entry on list */
+} FTREE;
diff --git a/bin/pax/gen_subs.c b/bin/pax/gen_subs.c
new file mode 100644
index 0000000..9228c69
--- /dev/null
+++ b/bin/pax/gen_subs.c
@@ -0,0 +1,437 @@
+/* $NetBSD: gen_subs.c,v 1.37 2018/11/30 00:53:11 christos Exp $ */
+
+/*-
+ * Copyright (c) 1992 Keith Muller.
+ * Copyright (c) 1992, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Keith Muller of the University of California, San Diego.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#if HAVE_NBTOOL_CONFIG_H
+#include "nbtool_config.h"
+#endif
+
+#include <sys/cdefs.h>
+#if !defined(lint)
+#if 0
+static char sccsid[] = "@(#)gen_subs.c 8.1 (Berkeley) 5/31/93";
+#else
+__RCSID("$NetBSD: gen_subs.c,v 1.37 2018/11/30 00:53:11 christos Exp $");
+#endif
+#endif /* not lint */
+
+#include <sys/types.h>
+#include <sys/time.h>
+#include <sys/stat.h>
+#include <sys/param.h>
+
+#include <ctype.h>
+#include <grp.h>
+#include <pwd.h>
+#include <vis.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <tzfile.h>
+#include <unistd.h>
+
+#include "pax.h"
+#include "extern.h"
+
+/*
+ * a collection of general purpose subroutines used by pax
+ */
+
+/*
+ * constants used by ls_list() when printing out archive members
+ */
+#define MODELEN 20
+#define DATELEN 64
+#define SIXMONTHS ((DAYSPERNYEAR / 2) * SECSPERDAY)
+#define CURFRMT "%b %e %H:%M"
+#define OLDFRMT "%b %e %Y"
+#ifndef UT_NAMESIZE
+#define UT_NAMESIZE 8
+#endif
+#define UT_GRPSIZE 6
+
+/*
+ * convert time to string
+ */
+static void
+formattime(char *buf, size_t buflen, time_t when)
+{
+ int error;
+ struct tm tm;
+ (void)localtime_r(&when, &tm);
+
+ if (when + SIXMONTHS <= time(NULL))
+ error = strftime(buf, buflen, OLDFRMT, &tm);
+ else
+ error = strftime(buf, buflen, CURFRMT, &tm);
+
+ if (error == 0)
+ buf[0] = '\0';
+}
+
+/*
+ * ls_list()
+ * list the members of an archive in ls format
+ */
+
+void
+ls_list(ARCHD *arcn, time_t now, FILE *fp)
+{
+ struct stat *sbp;
+ char f_mode[MODELEN];
+ char f_date[DATELEN];
+ const char *user, *group;
+
+ /*
+ * if not verbose, just print the file name
+ */
+ if (!vflag) {
+ (void)fprintf(fp, "%s\n", arcn->name);
+ (void)fflush(fp);
+ return;
+ }
+
+ /*
+ * user wants long mode
+ */
+ sbp = &(arcn->sb);
+ strmode(sbp->st_mode, f_mode);
+
+ /*
+ * time format based on age compared to the time pax was started.
+ */
+ formattime(f_date, sizeof(f_date), arcn->sb.st_mtime);
+ /*
+ * print file mode, link count, uid, gid and time
+ */
+ user = user_from_uid(sbp->st_uid, 0);
+ group = group_from_gid(sbp->st_gid, 0);
+ (void)fprintf(fp, "%s%2lu %-*s %-*s ", f_mode,
+ (unsigned long)sbp->st_nlink,
+ UT_NAMESIZE, user ? user : "", UT_GRPSIZE, group ? group : "");
+
+ /*
+ * print device id's for devices, or sizes for other nodes
+ */
+ if ((arcn->type == PAX_CHR) || (arcn->type == PAX_BLK))
+ (void)fprintf(fp, "%4lu,%4lu ", (long) MAJOR(sbp->st_rdev),
+ (long) MINOR(sbp->st_rdev));
+ else {
+ (void)fprintf(fp, OFFT_FP("9") " ", (OFFT_T)sbp->st_size);
+ }
+
+ /*
+ * print name and link info for hard and soft links
+ */
+ (void)fprintf(fp, "%s %s", f_date, arcn->name);
+ if ((arcn->type == PAX_HLK) || (arcn->type == PAX_HRG))
+ (void)fprintf(fp, " == %s\n", arcn->ln_name);
+ else if (arcn->type == PAX_SLK)
+ (void)fprintf(fp, " -> %s\n", arcn->ln_name);
+ else
+ (void)fputc('\n', fp);
+ (void)fflush(fp);
+}
+
+/*
+ * tty_ls()
+ * print a short summary of file to tty.
+ */
+
+void
+ls_tty(ARCHD *arcn)
+{
+ char f_date[DATELEN];
+ char f_mode[MODELEN];
+
+ formattime(f_date, sizeof(f_date), arcn->sb.st_mtime);
+ strmode(arcn->sb.st_mode, f_mode);
+ tty_prnt("%s%s %s\n", f_mode, f_date, arcn->name);
+ return;
+}
+
+void
+safe_print(const char *str, FILE *fp)
+{
+ char visbuf[5];
+ const char *cp;
+
+ /*
+ * if printing to a tty, use vis(3) to print special characters.
+ */
+ if (isatty(fileno(fp))) {
+ for (cp = str; *cp; cp++) {
+ (void)vis(visbuf, cp[0], VIS_CSTYLE, cp[1]);
+ (void)fputs(visbuf, fp);
+ }
+ } else {
+ (void)fputs(str, fp);
+ }
+}
+
+/*
+ * asc_u32()
+ * convert hex/octal character string into a uint32_t. We do not have to
+ * check for overflow! (the headers in all supported formats are not large
+ * enough to create an overflow).
+ * NOTE: strings passed to us are NOT TERMINATED.
+ * Return:
+ * uint32_t value
+ */
+
+uint32_t
+asc_u32(char *str, int len, int base)
+{
+ char *stop;
+ uint32_t tval = 0;
+
+ stop = str + len;
+
+ /*
+ * skip over leading blanks and zeros
+ */
+ while ((str < stop) && ((*str == ' ') || (*str == '0')))
+ ++str;
+
+ /*
+ * for each valid digit, shift running value (tval) over to next digit
+ * and add next digit
+ */
+ if (base == HEX) {
+ while (str < stop) {
+ if ((*str >= '0') && (*str <= '9'))
+ tval = (tval << 4) + (*str++ - '0');
+ else if ((*str >= 'A') && (*str <= 'F'))
+ tval = (tval << 4) + 10 + (*str++ - 'A');
+ else if ((*str >= 'a') && (*str <= 'f'))
+ tval = (tval << 4) + 10 + (*str++ - 'a');
+ else
+ break;
+ }
+ } else {
+ while ((str < stop) && (*str >= '0') && (*str <= '7'))
+ tval = (tval << 3) + (*str++ - '0');
+ }
+ return tval;
+}
+
+/*
+ * u32_asc()
+ * convert an uintmax_t into an hex/oct ascii string. pads with LEADING
+ * ascii 0's to fill string completely
+ * NOTE: the string created is NOT TERMINATED.
+ */
+
+int
+u32_asc(uintmax_t val, char *str, int len, int base)
+{
+ char *pt;
+ uint32_t digit;
+ uintmax_t p;
+
+ p = val & TOP_HALF;
+ if (p && p != TOP_HALF)
+ return -1;
+
+ val &= BOTTOM_HALF;
+
+ /*
+ * WARNING str is not '\0' terminated by this routine
+ */
+ pt = str + len - 1;
+
+ /*
+ * do a tailwise conversion (start at right most end of string to place
+ * least significant digit). Keep shifting until conversion value goes
+ * to zero (all digits were converted)
+ */
+ if (base == HEX) {
+ while (pt >= str) {
+ if ((digit = (val & 0xf)) < 10)
+ *pt-- = '0' + (char)digit;
+ else
+ *pt-- = 'a' + (char)(digit - 10);
+ if ((val = (val >> 4)) == (u_long)0)
+ break;
+ }
+ } else {
+ while (pt >= str) {
+ *pt-- = '0' + (char)(val & 0x7);
+ if ((val = (val >> 3)) == 0)
+ break;
+ }
+ }
+
+ /*
+ * pad with leading ascii ZEROS. We return -1 if we ran out of space.
+ */
+ while (pt >= str)
+ *pt-- = '0';
+ if (val != 0)
+ return -1;
+ return 0;
+}
+
+/*
+ * asc_umax()
+ * convert hex/octal/base-256 value into a uintmax.
+ * NOTE: strings passed to us are NOT TERMINATED.
+ * Return:
+ * uintmax_t value; UINTMAX_MAX for overflow/negative
+ */
+
+uintmax_t
+asc_umax(char *str, int len, int base)
+{
+ char *stop;
+ uintmax_t tval = 0;
+
+ stop = str + len;
+
+ /*
+ * if the highest bit of first byte is set, it's base-256 encoded
+ * (base-256 is basically (n-1)-bit big endian signed
+ */
+ if (str < stop && (*str & 0x80)) {
+ /*
+ * uintmax_t can't be negative, so fail on negative numbers
+ */
+ if (*str & 0x40)
+ return UINTMAX_MAX;
+
+ tval = *str++ & 0x3f;
+ while (str < stop) {
+ /*
+ * check for overflow
+ */
+ if (tval > (UINTMAX_MAX/256))
+ return UINTMAX_MAX;
+ tval = (tval << 8) | ((*str++) & 0xFF);
+ }
+
+ return tval;
+ }
+
+ /*
+ * skip over leading blanks and zeros
+ */
+ while ((str < stop) && ((*str == ' ') || (*str == '0')))
+ ++str;
+
+ /*
+ * for each valid digit, shift running value (tval) over to next digit
+ * and add next digit
+ */
+ if (base == HEX) {
+ while (str < stop) {
+ if ((*str >= '0') && (*str <= '9'))
+ tval = (tval << 4) + (*str++ - '0');
+ else if ((*str >= 'A') && (*str <= 'F'))
+ tval = (tval << 4) + 10 + (*str++ - 'A');
+ else if ((*str >= 'a') && (*str <= 'f'))
+ tval = (tval << 4) + 10 + (*str++ - 'a');
+ else
+ break;
+ }
+ } else {
+ while ((str < stop) && (*str >= '0') && (*str <= '7'))
+ tval = (tval << 3) + (*str++ - '0');
+ }
+ return tval;
+}
+
+/*
+ * umax_asc()
+ * convert an uintmax_t into a hex/oct ascii string. pads with
+ * LEADING ascii 0's to fill string completely
+ * NOTE: the string created is NOT TERMINATED.
+ */
+
+int
+umax_asc(uintmax_t val, char *str, int len, int base)
+{
+ char *pt;
+ uintmax_t digit;
+
+ /*
+ * WARNING str is not '\0' terminated by this routine
+ */
+ pt = str + len - 1;
+
+ /*
+ * do a tailwise conversion (start at right most end of string to place
+ * least significant digit). Keep shifting until conversion value goes
+ * to zero (all digits were converted)
+ */
+ if (base == HEX) {
+ while (pt >= str) {
+ if ((digit = (val & 0xf)) < 10)
+ *pt-- = '0' + (char)digit;
+ else
+ *pt-- = 'a' + (char)(digit - 10);
+ if ((val = (val >> 4)) == 0)
+ break;
+ }
+ } else {
+ while (pt >= str) {
+ *pt-- = '0' + (char)(val & 0x7);
+ if ((val = (val >> 3)) == 0)
+ break;
+ }
+ }
+
+ /*
+ * pad with leading ascii ZEROS. We return -1 if we ran out of space.
+ */
+ while (pt >= str)
+ *pt-- = '0';
+ if (val != 0)
+ return -1;
+ return 0;
+}
+
+int
+check_Aflag(void)
+{
+
+ if (Aflag > 0)
+ return 1;
+ if (Aflag == 0) {
+ Aflag = -1;
+ tty_warn(0,
+ "Removing leading / from absolute path names in the archive");
+ }
+ return 0;
+}
diff --git a/bin/pax/getoldopt.c b/bin/pax/getoldopt.c
new file mode 100644
index 0000000..2d02e7e
--- /dev/null
+++ b/bin/pax/getoldopt.c
@@ -0,0 +1,92 @@
+/* $NetBSD: getoldopt.c,v 1.23 2012/08/09 11:05:59 christos Exp $ */
+
+/*
+ * Plug-compatible replacement for getopt() for parsing tar-like
+ * arguments. If the first argument begins with "-", it uses getopt;
+ * otherwise, it uses the old rules used by tar, dump, and ps.
+ *
+ * Written 25 August 1985 by John Gilmore (ihnp4!hoptoad!gnu) and placed
+ * in the Public Domain for your edification and enjoyment.
+ */
+
+#if HAVE_NBTOOL_CONFIG_H
+#include "nbtool_config.h"
+#endif
+
+#include <sys/cdefs.h>
+#if !defined(lint)
+__RCSID("$NetBSD: getoldopt.c,v 1.23 2012/08/09 11:05:59 christos Exp $");
+#endif /* not lint */
+
+#if HAVE_NBTOOL_CONFIG_H
+#include "compat_getopt.h"
+#else
+#include <getopt.h>
+#endif
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <stdint.h>
+#include <sys/stat.h>
+#include "pax.h"
+#include "extern.h"
+
+int
+getoldopt(int argc, char **argv, const char *optstring,
+ struct option *longopts, int *idx)
+{
+ static char *key; /* Points to next keyletter */
+ static char use_getopt; /* !=0 if argv[1][0] was '-' */
+ char c;
+ char *place;
+
+ optarg = NULL;
+
+ if (key == NULL) { /* First time */
+ if (argc < 2) return -1;
+ key = argv[1];
+ if (*key == '-')
+ use_getopt++;
+ else
+ optind = 2;
+ }
+
+ c = '\0';
+ if (!use_getopt) {
+ c = *key++;
+ if (c == '\0') {
+ key--;
+ use_getopt = 1;
+ }
+ }
+ if (use_getopt) {
+ if (longopts != NULL) {
+ return getopt_long(argc, argv, optstring,
+ longopts, idx);
+ } else {
+ return getopt(argc, argv, optstring);
+ }
+ }
+
+ place = strchr(optstring, c);
+
+ if (place == NULL || c == ':') {
+ fprintf(stderr, "%s: unknown option %c\n", argv[0], c);
+ return '?';
+ }
+
+ place++;
+ if (*place == ':') {
+ if (optind < argc) {
+ optarg = argv[optind];
+ optind++;
+ } else {
+ fprintf(stderr, "%s: %c argument missing\n",
+ argv[0], c);
+ return '?';
+ }
+ }
+
+ return c;
+}
diff --git a/bin/pax/options.c b/bin/pax/options.c
new file mode 100644
index 0000000..74e1480
--- /dev/null
+++ b/bin/pax/options.c
@@ -0,0 +1,2229 @@
+/* $NetBSD: options.c,v 1.118 2015/12/19 18:45:52 christos Exp $ */
+
+/*-
+ * Copyright (c) 1992 Keith Muller.
+ * Copyright (c) 1992, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Keith Muller of the University of California, San Diego.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#if HAVE_NBTOOL_CONFIG_H
+#include "nbtool_config.h"
+#endif
+
+#include <sys/cdefs.h>
+#if !defined(lint)
+#if 0
+static char sccsid[] = "@(#)options.c 8.2 (Berkeley) 4/18/94";
+#else
+__RCSID("$NetBSD: options.c,v 1.118 2015/12/19 18:45:52 christos Exp $");
+#endif
+#endif /* not lint */
+
+#include <sys/types.h>
+#include <sys/time.h>
+#include <sys/stat.h>
+#include <sys/param.h>
+#include <ctype.h>
+#include <errno.h>
+#if HAVE_NBTOOL_CONFIG_H
+#include "compat_getopt.h"
+#else
+#include <getopt.h>
+#endif
+#include <limits.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <inttypes.h>
+#include <paths.h>
+#include <util.h>
+#include "pax.h"
+#include "options.h"
+#include "cpio.h"
+#include "tar.h"
+#include "extern.h"
+#ifndef SMALL
+#include "mtree.h"
+#endif /* SMALL */
+
+/*
+ * Routines which handle command line options
+ */
+struct stat tst; /* Timestamp to set if non-0 */
+
+static int nopids; /* tar mode: suppress "pids" for -p option */
+static char flgch[] = FLGCH; /* list of all possible flags (pax) */
+static OPLIST *ophead = NULL; /* head for format specific options -x */
+static OPLIST *optail = NULL; /* option tail */
+
+static int opt_add(const char *);
+static int no_op(void);
+static void printflg(unsigned int);
+static int c_frmt(const void *, const void *);
+static off_t str_offt(char *);
+static char *get_line(FILE *fp);
+#ifndef SMALL
+static int set_tstamp(const char *, struct stat *);
+#endif
+static void pax_options(int, char **);
+__dead static void pax_usage(void);
+static void tar_options(int, char **);
+__dead static void tar_usage(void);
+#ifndef NO_CPIO
+static void cpio_options(int, char **);
+__dead static void cpio_usage(void);
+#endif
+
+/* errors from get_line */
+#define GETLINE_FILE_CORRUPT 1
+#define GETLINE_OUT_OF_MEM 2
+static int get_line_error;
+
+#define BZIP2_CMD "bzip2" /* command to run as bzip2 */
+#define GZIP_CMD "gzip" /* command to run as gzip */
+#define XZ_CMD "xz" /* command to run as xz */
+#define COMPRESS_CMD "compress" /* command to run as compress */
+
+/*
+ * Long options.
+ */
+#define OPT_USE_COMPRESS_PROGRAM 0
+#define OPT_CHECKPOINT 1
+#define OPT_UNLINK 2
+#define OPT_HELP 3
+#define OPT_ATIME_PRESERVE 4
+#define OPT_IGNORE_FAILED_READ 5
+#define OPT_REMOVE_FILES 6
+#define OPT_NULL 7
+#define OPT_TOTALS 8
+#define OPT_VERSION 9
+#define OPT_EXCLUDE 10
+#define OPT_BLOCK_COMPRESS 11
+#define OPT_NORECURSE 12
+#define OPT_FORCE_LOCAL 13
+#define OPT_INSECURE 14
+#define OPT_STRICT 15
+#define OPT_SPARSE 16
+#define OPT_XZ 17
+#define OPT_GNU 18
+#define OPT_TIMESTAMP 19
+#if !HAVE_NBTOOL_CONFIG_H
+#define OPT_CHROOT 20
+#endif
+
+/*
+ * Format specific routine table - MUST BE IN SORTED ORDER BY NAME
+ * (see pax.h for description of each function)
+ *
+ * name, blksz, hdsz, udev, hlk, blkagn, inhead, id, st_read,
+ * read, end_read, st_write, write, end_write, trail,
+ * subtrail, rd_data, wr_data, options
+ */
+
+FSUB fsub[] = {
+#ifndef NO_CPIO
+/* 0: OLD BINARY CPIO */
+ { "bcpio", 5120, sizeof(HD_BCPIO), 1, 0, 0, 1, bcpio_id, cpio_strd,
+ bcpio_rd, bcpio_endrd, cpio_stwr, bcpio_wr, cpio_endwr, NULL,
+ cpio_subtrail, rd_wrfile, wr_rdfile, bad_opt },
+
+/* 1: OLD OCTAL CHARACTER CPIO */
+ { "cpio", 5120, sizeof(HD_CPIO), 1, 0, 0, 1, cpio_id, cpio_strd,
+ cpio_rd, cpio_endrd, cpio_stwr, cpio_wr, cpio_endwr, NULL,
+ cpio_subtrail, rd_wrfile, wr_rdfile, bad_opt },
+
+/* 2: SVR4 HEX CPIO */
+ { "sv4cpio", 5120, sizeof(HD_VCPIO), 1, 0, 0, 1, vcpio_id, cpio_strd,
+ vcpio_rd, vcpio_endrd, cpio_stwr, vcpio_wr, cpio_endwr, NULL,
+ cpio_subtrail, rd_wrfile, wr_rdfile, bad_opt },
+
+/* 3: SVR4 HEX CPIO WITH CRC */
+ { "sv4crc", 5120, sizeof(HD_VCPIO), 1, 0, 0, 1, crc_id, crc_strd,
+ vcpio_rd, vcpio_endrd, crc_stwr, vcpio_wr, cpio_endwr, NULL,
+ cpio_subtrail, rd_wrfile, wr_rdfile, bad_opt },
+#endif
+/* 4: OLD TAR */
+ { "tar", 10240, BLKMULT, 0, 1, BLKMULT, 0, tar_id, no_op,
+ tar_rd, tar_endrd, no_op, tar_wr, tar_endwr, tar_trail,
+ NULL, rd_wrfile, wr_rdfile, tar_opt },
+
+/* 5: POSIX USTAR */
+ { "ustar", 10240, BLKMULT, 0, 1, BLKMULT, 0, ustar_id, ustar_strd,
+ ustar_rd, tar_endrd, ustar_stwr, ustar_wr, tar_endwr, tar_trail,
+ NULL, rd_wrfile, wr_rdfile, bad_opt }
+};
+#ifndef NO_CPIO
+#define F_BCPIO 0 /* old binary cpio format */
+#define F_CPIO 1 /* old octal character cpio format */
+#define F_SV4CPIO 2 /* SVR4 hex cpio format */
+#define F_SV4CRC 3 /* SVR4 hex with crc cpio format */
+#define F_TAR 4 /* old V7 UNIX tar format */
+#define F_USTAR 5 /* ustar format */
+#else
+#define F_TAR 0 /* old V7 UNIX tar format */
+#define F_USTAR 1 /* ustar format */
+#endif
+#define DEFLT F_USTAR /* default write format from list above */
+
+/*
+ * ford is the archive search order used by get_arc() to determine what kind
+ * of archive we are dealing with. This helps to properly id archive formats
+ * some formats may be subsets of others....
+ */
+int ford[] = {F_USTAR, F_TAR,
+#ifndef NO_CPIO
+ F_SV4CRC, F_SV4CPIO, F_CPIO, F_BCPIO,
+#endif
+ -1};
+
+/*
+ * filename record separator
+ */
+int sep = '\n';
+
+/*
+ * Do we have -C anywhere?
+ */
+int havechd = 0;
+
+/*
+ * options()
+ * figure out if we are pax, tar or cpio. Call the appropriate options
+ * parser
+ */
+
+void
+options(int argc, char **argv)
+{
+
+ /*
+ * Are we acting like pax, tar or cpio (based on argv[0])
+ */
+ if ((argv0 = strrchr(argv[0], '/')) != NULL)
+ argv0++;
+ else
+ argv0 = argv[0];
+
+ if (strstr(argv0, NM_TAR)) {
+ argv0 = NM_TAR;
+ tar_options(argc, argv);
+#ifndef NO_CPIO
+ } else if (strstr(argv0, NM_CPIO)) {
+ argv0 = NM_CPIO;
+ cpio_options(argc, argv);
+#endif
+ } else {
+ argv0 = NM_PAX;
+ pax_options(argc, argv);
+ }
+}
+
+struct option pax_longopts[] = {
+ { "insecure", no_argument, 0,
+ OPT_INSECURE },
+ { "force-local", no_argument, 0,
+ OPT_FORCE_LOCAL },
+ { "use-compress-program", required_argument, 0,
+ OPT_USE_COMPRESS_PROGRAM },
+ { "xz", no_argument, 0,
+ OPT_XZ },
+ { "gnu", no_argument, 0,
+ OPT_GNU },
+ { "timestamp", required_argument, 0,
+ OPT_TIMESTAMP },
+ { 0, 0, 0,
+ 0 },
+};
+
+/*
+ * pax_options()
+ * look at the user specified flags. set globals as required and check if
+ * the user specified a legal set of flags. If not, complain and exit
+ */
+
+static void
+pax_options(int argc, char **argv)
+{
+ int c;
+ size_t i;
+ u_int64_t flg = 0;
+ u_int64_t bflg = 0;
+ char *pt;
+ FSUB tmp;
+
+ /*
+ * process option flags
+ */
+ while ((c = getopt_long(argc, argv,
+ "0ab:cdf:ijklno:p:rs:tuvwx:zAB:DE:G:HLMN:OPT:U:VXYZ",
+ pax_longopts, NULL)) != -1) {
+ switch (c) {
+ case '0':
+ sep = '\0';
+ break;
+ case 'a':
+ /*
+ * append
+ */
+ flg |= AF;
+ break;
+ case 'b':
+ /*
+ * specify blocksize
+ */
+ flg |= BF;
+ if ((wrblksz = (int)str_offt(optarg)) <= 0) {
+ tty_warn(1, "Invalid block size %s", optarg);
+ pax_usage();
+ }
+ break;
+ case 'c':
+ /*
+ * inverse match on patterns
+ */
+ cflag = 1;
+ flg |= CF;
+ break;
+ case 'd':
+ /*
+ * match only dir on extract, not the subtree at dir
+ */
+ dflag = 1;
+ flg |= DF;
+ break;
+ case 'f':
+ /*
+ * filename where the archive is stored
+ */
+ arcname = optarg;
+ flg |= FF;
+ break;
+ case 'i':
+ /*
+ * interactive file rename
+ */
+ iflag = 1;
+ flg |= IF;
+ break;
+ case 'j':
+ /*
+ * pass through bzip2
+ */
+ gzip_program = BZIP2_CMD;
+ break;
+ case 'k':
+ /*
+ * do not clobber files that exist
+ */
+ kflag = 1;
+ flg |= KF;
+ break;
+ case 'l':
+ /*
+ * try to link src to dest with copy (-rw)
+ */
+ lflag = 1;
+ flg |= LF;
+ break;
+ case 'n':
+ /*
+ * select first match for a pattern only
+ */
+ nflag = 1;
+ flg |= NF;
+ break;
+ case 'o':
+ /*
+ * pass format specific options
+ */
+ flg |= OF;
+ if (opt_add(optarg) < 0)
+ pax_usage();
+ break;
+ case 'p':
+ /*
+ * specify file characteristic options
+ */
+ for (pt = optarg; *pt != '\0'; ++pt) {
+ switch(*pt) {
+ case 'a':
+ /*
+ * do not preserve access time
+ */
+ patime = 0;
+ break;
+ case 'e':
+ /*
+ * preserve user id, group id, file
+ * mode, access/modification times
+ * and file flags.
+ */
+ pids = 1;
+ pmode = 1;
+ patime = 1;
+ pmtime = 1;
+ pfflags = 1;
+ break;
+#if 0
+ case 'f':
+ /*
+ * do not preserve file flags
+ */
+ pfflags = 0;
+ break;
+#endif
+ case 'm':
+ /*
+ * do not preserve modification time
+ */
+ pmtime = 0;
+ break;
+ case 'o':
+ /*
+ * preserve uid/gid
+ */
+ pids = 1;
+ break;
+ case 'p':
+ /*
+ * preserve file mode bits
+ */
+ pmode = 1;
+ break;
+ default:
+ tty_warn(1, "Invalid -p string: %c",
+ *pt);
+ pax_usage();
+ break;
+ }
+ }
+ flg |= PF;
+ break;
+ case 'r':
+ /*
+ * read the archive
+ */
+ flg |= RF;
+ break;
+ case 's':
+ /*
+ * file name substitution name pattern
+ */
+ if (rep_add(optarg) < 0) {
+ pax_usage();
+ break;
+ }
+ flg |= SF;
+ break;
+ case 't':
+ /*
+ * preserve access time on filesystem nodes we read
+ */
+ tflag = 1;
+ flg |= TF;
+ break;
+ case 'u':
+ /*
+ * ignore those older files
+ */
+ uflag = 1;
+ flg |= UF;
+ break;
+ case 'v':
+ /*
+ * verbose operation mode
+ */
+ vflag = 1;
+ flg |= VF;
+ break;
+ case 'w':
+ /*
+ * write an archive
+ */
+ flg |= WF;
+ break;
+ case 'x':
+ /*
+ * specify an archive format on write
+ */
+ tmp.name = optarg;
+ frmt = (FSUB *)bsearch((void *)&tmp, (void *)fsub,
+ sizeof(fsub)/sizeof(FSUB), sizeof(FSUB), c_frmt);
+ if (frmt != NULL) {
+ flg |= XF;
+ break;
+ }
+ tty_warn(1, "Unknown -x format: %s", optarg);
+ (void)fputs("pax: Known -x formats are:", stderr);
+ for (i = 0; i < (sizeof(fsub)/sizeof(FSUB)); ++i)
+ (void)fprintf(stderr, " %s", fsub[i].name);
+ (void)fputs("\n\n", stderr);
+ pax_usage();
+ break;
+ case 'z':
+ /*
+ * use gzip. Non standard option.
+ */
+ gzip_program = GZIP_CMD;
+ break;
+ case 'A':
+ Aflag = 1;
+ flg |= CAF;
+ break;
+ case 'B':
+ /*
+ * non-standard option on number of bytes written on a
+ * single archive volume.
+ */
+ if ((wrlimit = str_offt(optarg)) <= 0) {
+ tty_warn(1, "Invalid write limit %s", optarg);
+ pax_usage();
+ }
+ if (wrlimit % BLKMULT) {
+ tty_warn(1,
+ "Write limit is not a %d byte multiple",
+ BLKMULT);
+ pax_usage();
+ }
+ flg |= CBF;
+ break;
+ case 'D':
+ /*
+ * On extraction check file inode change time before the
+ * modification of the file name. Non standard option.
+ */
+ Dflag = 1;
+ flg |= CDF;
+ break;
+ case 'E':
+ /*
+ * non-standard limit on read faults
+ * 0 indicates stop after first error, values
+ * indicate a limit, "none" try forever
+ */
+ flg |= CEF;
+ if (strcmp(none, optarg) == 0)
+ maxflt = -1;
+ else if ((maxflt = atoi(optarg)) < 0) {
+ tty_warn(1,
+ "Error count value must be positive");
+ pax_usage();
+ }
+ break;
+ case 'G':
+ /*
+ * non-standard option for selecting files within an
+ * archive by group (gid or name)
+ */
+ if (grp_add(optarg) < 0) {
+ pax_usage();
+ break;
+ }
+ flg |= CGF;
+ break;
+ case 'H':
+ /*
+ * follow command line symlinks only
+ */
+ Hflag = 1;
+ flg |= CHF;
+ break;
+ case 'L':
+ /*
+ * follow symlinks
+ */
+ Lflag = 1;
+ flg |= CLF;
+ break;
+#ifdef SMALL
+ case 'M':
+ case 'N':
+ tty_warn(1, "Support for -%c is not compiled in", c);
+ exit(1);
+#else /* !SMALL */
+ case 'M':
+ /*
+ * Treat list of filenames on stdin as an
+ * mtree(8) specfile. Non standard option.
+ */
+ Mflag = 1;
+ flg |= CMF;
+ break;
+ case 'N':
+ /*
+ * Use alternative directory for user db lookups.
+ */
+ if (!setup_getid(optarg)) {
+ tty_warn(1,
+ "Unable to use user and group databases in `%s'",
+ optarg);
+ pax_usage();
+ }
+ break;
+#endif /* !SMALL */
+ case 'O':
+ /*
+ * Force one volume. Non standard option.
+ */
+ force_one_volume = 1;
+ break;
+ case 'P':
+ /*
+ * do NOT follow symlinks (default)
+ */
+ Lflag = 0;
+ flg |= CPF;
+ break;
+ case 'T':
+ /*
+ * non-standard option for selecting files within an
+ * archive by modification time range (lower,upper)
+ */
+ if (trng_add(optarg) < 0) {
+ pax_usage();
+ break;
+ }
+ flg |= CTF;
+ break;
+ case 'U':
+ /*
+ * non-standard option for selecting files within an
+ * archive by user (uid or name)
+ */
+ if (usr_add(optarg) < 0) {
+ pax_usage();
+ break;
+ }
+ flg |= CUF;
+ break;
+ case 'V':
+ /*
+ * somewhat verbose operation mode (no listing)
+ */
+ Vflag = 1;
+ flg |= VSF;
+ break;
+ case 'X':
+ /*
+ * do not pass over mount points in the file system
+ */
+ Xflag = 1;
+ flg |= CXF;
+ break;
+ case 'Y':
+ /*
+ * On extraction check file inode change time after the
+ * modification of the file name. Non standard option.
+ */
+ Yflag = 1;
+ flg |= CYF;
+ break;
+ case 'Z':
+ /*
+ * On extraction check modification time after the
+ * modification of the file name. Non standard option.
+ */
+ Zflag = 1;
+ flg |= CZF;
+ break;
+ case OPT_INSECURE:
+ secure = 0;
+ break;
+ case OPT_FORCE_LOCAL:
+ forcelocal = 1;
+ break;
+ case OPT_USE_COMPRESS_PROGRAM:
+ gzip_program = optarg;
+ break;
+ case OPT_XZ:
+ gzip_program = XZ_CMD;
+ break;
+ case OPT_GNU:
+ is_gnutar = 1;
+ break;
+#ifndef SMALL
+ case OPT_TIMESTAMP:
+ if (set_tstamp(optarg, &tst) == -1) {
+ tty_warn(1, "Invalid timestamp `%s'", optarg);
+ tar_usage();
+ }
+ break;
+#endif
+ case '?':
+ default:
+ pax_usage();
+ break;
+ }
+ }
+
+ /*
+ * figure out the operation mode of pax read,write,extract,copy,append
+ * or list. check that we have not been given a bogus set of flags
+ * for the operation mode.
+ */
+ if (ISLIST(flg)) {
+ act = LIST;
+ listf = stdout;
+ bflg = flg & BDLIST;
+ } else if (ISEXTRACT(flg)) {
+ act = EXTRACT;
+ bflg = flg & BDEXTR;
+ } else if (ISARCHIVE(flg)) {
+ act = ARCHIVE;
+ bflg = flg & BDARCH;
+ } else if (ISAPPND(flg)) {
+ act = APPND;
+ bflg = flg & BDARCH;
+ } else if (ISCOPY(flg)) {
+ act = COPY;
+ bflg = flg & BDCOPY;
+ } else
+ pax_usage();
+ if (bflg) {
+ printflg(flg);
+ pax_usage();
+ }
+
+ /*
+ * if we are writing (ARCHIVE) we use the default format if the user
+ * did not specify a format. when we write during an APPEND, we will
+ * adopt the format of the existing archive if none was supplied.
+ */
+ if (!(flg & XF) && (act == ARCHIVE))
+ frmt = &(fsub[DEFLT]);
+
+ /*
+ * process the args as they are interpreted by the operation mode
+ */
+ switch (act) {
+ case LIST:
+ case EXTRACT:
+ for (; optind < argc; optind++)
+ if (pat_add(argv[optind], NULL, 0) < 0)
+ pax_usage();
+ break;
+ case COPY:
+ if (optind >= argc) {
+ tty_warn(0, "Destination directory was not supplied");
+ pax_usage();
+ }
+ --argc;
+ dirptr = argv[argc];
+ if (mkpath(dirptr) < 0)
+ exit(1);
+ /* FALLTHROUGH */
+ case ARCHIVE:
+ case APPND:
+ for (; optind < argc; optind++)
+ if (ftree_add(argv[optind], 0) < 0)
+ pax_usage();
+ /*
+ * no read errors allowed on updates/append operation!
+ */
+ maxflt = 0;
+ break;
+ }
+}
+
+
+/*
+ * tar_options()
+ * look at the user specified flags. set globals as required and check if
+ * the user specified a legal set of flags. If not, complain and exit
+ */
+
+struct option tar_longopts[] = {
+ { "block-size", required_argument, 0, 'b' },
+ { "bunzip2", no_argument, 0, 'j' },
+ { "bzip2", no_argument, 0, 'j' },
+ { "create", no_argument, 0, 'c' }, /* F */
+ /* -e -- no corresponding long option */
+ { "file", required_argument, 0, 'f' },
+ { "dereference", no_argument, 0, 'h' },
+ { "keep-old-files", no_argument, 0, 'k' },
+ { "one-file-system", no_argument, 0, 'l' },
+ { "modification-time", no_argument, 0, 'm' },
+ { "old-archive", no_argument, 0, 'o' },
+ { "portability", no_argument, 0, 'o' },
+ { "same-permissions", no_argument, 0, 'p' },
+ { "preserve-permissions", no_argument, 0, 'p' },
+ { "preserve", no_argument, 0, 'p' },
+ { "fast-read", no_argument, 0, 'q' },
+ { "append", no_argument, 0, 'r' }, /* F */
+ { "update", no_argument, 0, 'u' }, /* F */
+ { "list", no_argument, 0, 't' }, /* F */
+ { "verbose", no_argument, 0, 'v' },
+ { "interactive", no_argument, 0, 'w' },
+ { "confirmation", no_argument, 0, 'w' },
+ { "extract", no_argument, 0, 'x' }, /* F */
+ { "get", no_argument, 0, 'x' }, /* F */
+ { "gzip", no_argument, 0, 'z' },
+ { "gunzip", no_argument, 0, 'z' },
+ { "read-full-blocks", no_argument, 0, 'B' },
+ { "directory", required_argument, 0, 'C' },
+ { "xz", no_argument, 0, 'J' },
+ { "to-stdout", no_argument, 0, 'O' },
+ { "absolute-paths", no_argument, 0, 'P' },
+ { "sparse", no_argument, 0, 'S' },
+ { "files-from", required_argument, 0, 'T' },
+ { "summary", no_argument, 0, 'V' },
+ { "stats", no_argument, 0, 'V' },
+ { "exclude-from", required_argument, 0, 'X' },
+ { "compress", no_argument, 0, 'Z' },
+ { "uncompress", no_argument, 0, 'Z' },
+ { "strict", no_argument, 0,
+ OPT_STRICT },
+ { "atime-preserve", no_argument, 0,
+ OPT_ATIME_PRESERVE },
+ { "unlink", no_argument, 0,
+ OPT_UNLINK },
+ { "use-compress-program", required_argument, 0,
+ OPT_USE_COMPRESS_PROGRAM },
+ { "force-local", no_argument, 0,
+ OPT_FORCE_LOCAL },
+ { "insecure", no_argument, 0,
+ OPT_INSECURE },
+ { "exclude", required_argument, 0,
+ OPT_EXCLUDE },
+ { "no-recursion", no_argument, 0,
+ OPT_NORECURSE },
+#if !HAVE_NBTOOL_CONFIG_H
+ { "chroot", no_argument, 0,
+ OPT_CHROOT },
+#endif
+ { "timestamp", required_argument, 0,
+ OPT_TIMESTAMP },
+#if 0 /* Not implemented */
+ { "catenate", no_argument, 0, 'A' }, /* F */
+ { "concatenate", no_argument, 0, 'A' }, /* F */
+ { "diff", no_argument, 0, 'd' }, /* F */
+ { "compare", no_argument, 0, 'd' }, /* F */
+ { "checkpoint", no_argument, 0,
+ OPT_CHECKPOINT },
+ { "help", no_argument, 0,
+ OPT_HELP },
+ { "info-script", required_argument, 0, 'F' },
+ { "new-volume-script", required_argument, 0, 'F' },
+ { "incremental", no_argument, 0, 'G' },
+ { "listed-incremental", required_argument, 0, 'g' },
+ { "ignore-zeros", no_argument, 0, 'i' },
+ { "ignore-failed-read", no_argument, 0,
+ OPT_IGNORE_FAILED_READ },
+ { "starting-file", no_argument, 0, 'K' },
+ { "tape-length", required_argument, 0, 'L' },
+ { "multi-volume", no_argument, 0, 'M' },
+ { "after-date", required_argument, 0, 'N' },
+ { "newer", required_argument, 0, 'N' },
+ { "record-number", no_argument, 0, 'R' },
+ { "remove-files", no_argument, 0,
+ OPT_REMOVE_FILES },
+ { "same-order", no_argument, 0, 's' },
+ { "preserve-order", no_argument, 0, 's' },
+ { "null", no_argument, 0,
+ OPT_NULL },
+ { "totals", no_argument, 0,
+ OPT_TOTALS },
+ { "volume-name", required_argument, 0, 'V' }, /* XXX */
+ { "label", required_argument, 0, 'V' }, /* XXX */
+ { "version", no_argument, 0,
+ OPT_VERSION },
+ { "verify", no_argument, 0, 'W' },
+ { "block-compress", no_argument, 0,
+ OPT_BLOCK_COMPRESS },
+#endif
+ { 0, 0, 0, 0 },
+};
+
+static void
+tar_set_action(int op)
+{
+ if (act != ERROR && act != op)
+ tar_usage();
+ act = op;
+}
+
+static void
+tar_options(int argc, char **argv)
+{
+ int c;
+ int fstdin = 0;
+ int Oflag = 0;
+ int nincfiles = 0;
+ int incfiles_max = 0;
+ struct incfile {
+ char *file;
+ char *dir;
+ };
+ struct incfile *incfiles = NULL;
+
+ /*
+ * Set default values.
+ */
+ rmleadslash = 1;
+ is_gnutar = 1;
+
+ /*
+ * process option flags
+ */
+ while ((c = getoldopt(argc, argv,
+ "+b:cef:hjklmopqrs:tuvwxzBC:HI:JOPST:X:Z014578",
+ tar_longopts, NULL))
+ != -1) {
+ switch(c) {
+ case 'b':
+ /*
+ * specify blocksize in 512-byte blocks
+ */
+ if ((wrblksz = (int)str_offt(optarg)) <= 0) {
+ tty_warn(1, "Invalid block size %s", optarg);
+ tar_usage();
+ }
+ wrblksz *= 512; /* XXX - check for int oflow */
+ break;
+ case 'c':
+ /*
+ * create an archive
+ */
+ tar_set_action(ARCHIVE);
+ break;
+ case 'e':
+ /*
+ * stop after first error
+ */
+ maxflt = 0;
+ break;
+ case 'f':
+ /*
+ * filename where the archive is stored
+ */
+ if ((optarg[0] == '-') && (optarg[1]== '\0')) {
+ /*
+ * treat a - as stdin
+ */
+ fstdin = 1;
+ arcname = NULL;
+ break;
+ }
+ fstdin = 0;
+ arcname = optarg;
+ break;
+ case 'h':
+ /*
+ * follow symlinks
+ */
+ Lflag = 1;
+ break;
+ case 'j':
+ /*
+ * pass through bzip2. not a standard option
+ */
+ gzip_program = BZIP2_CMD;
+ break;
+ case 'k':
+ /*
+ * do not clobber files that exist
+ */
+ kflag = 1;
+ break;
+ case 'l':
+ /*
+ * do not pass over mount points in the file system
+ */
+ Xflag = 1;
+ break;
+ case 'm':
+ /*
+ * do not preserve modification time
+ */
+ pmtime = 0;
+ break;
+ case 'o':
+ /*
+ * This option does several things based on whether
+ * this is a create or extract operation.
+ */
+ if (act == ARCHIVE) {
+ /* GNU tar: write V7 format archives. */
+ Oflag = 1;
+ /* 4.2BSD: don't add directory entries. */
+ if (opt_add("write_opt=nodir") < 0)
+ tar_usage();
+
+ } else {
+ /* SUS: don't preserve owner/group. */
+ pids = 0;
+ nopids = 1;
+ }
+ break;
+ case 'p':
+ /*
+ * preserve user id, group id, file
+ * mode, access/modification times
+ */
+ if (!nopids)
+ pids = 1;
+ pmode = 1;
+ patime = 1;
+ pmtime = 1;
+ break;
+ case 'q':
+ /*
+ * select first match for a pattern only
+ */
+ nflag = 1;
+ break;
+ case 'r':
+ case 'u':
+ /*
+ * append to the archive
+ */
+ tar_set_action(APPND);
+ break;
+ case 's':
+ /*
+ * file name substitution name pattern
+ */
+ if (rep_add(optarg) < 0) {
+ tar_usage();
+ break;
+ }
+ break;
+ case 't':
+ /*
+ * list contents of the tape
+ */
+ tar_set_action(LIST);
+ break;
+ case 'v':
+ /*
+ * verbose operation mode
+ */
+ vflag = 1;
+ break;
+ case 'w':
+ /*
+ * interactive file rename
+ */
+ iflag = 1;
+ break;
+ case 'x':
+ /*
+ * extract an archive, preserving mode,
+ * and mtime if possible.
+ */
+ tar_set_action(EXTRACT);
+ pmtime = 1;
+ break;
+ case 'z':
+ /*
+ * use gzip. Non standard option.
+ */
+ gzip_program = GZIP_CMD;
+ break;
+ case 'B':
+ /*
+ * Nothing to do here, this is pax default
+ */
+ break;
+ case 'C':
+ havechd++;
+ chdname = optarg;
+ break;
+ case 'H':
+ /*
+ * follow command line symlinks only
+ */
+ Hflag = 1;
+ break;
+ case 'I':
+ case 'T':
+ if (++nincfiles > incfiles_max) {
+ incfiles_max = nincfiles + 3;
+ incfiles = realloc(incfiles,
+ sizeof(*incfiles) * incfiles_max);
+ if (incfiles == NULL) {
+ tty_warn(0, "Unable to allocate space "
+ "for option list");
+ exit(1);
+ }
+ }
+ incfiles[nincfiles - 1].file = optarg;
+ incfiles[nincfiles - 1].dir = chdname;
+ break;
+ case 'J':
+ gzip_program = XZ_CMD;
+ break;
+ case 'O':
+ Oflag = 1;
+ break;
+ case 'P':
+ /*
+ * do not remove leading '/' from pathnames
+ */
+ rmleadslash = 0;
+ Aflag = 1;
+ break;
+ case 'S':
+ /* do nothing; we already generate sparse files */
+ break;
+ case 'V':
+ /*
+ * semi-verbose operation mode (no listing)
+ */
+ Vflag = 1;
+ break;
+ case 'X':
+ /*
+ * GNU tar compat: exclude the files listed in optarg
+ */
+ if (tar_gnutar_X_compat(optarg) != 0)
+ tar_usage();
+ break;
+ case 'Z':
+ /*
+ * use compress.
+ */
+ gzip_program = COMPRESS_CMD;
+ break;
+ case '0':
+ arcname = DEV_0;
+ break;
+ case '1':
+ arcname = DEV_1;
+ break;
+ case '4':
+ arcname = DEV_4;
+ break;
+ case '5':
+ arcname = DEV_5;
+ break;
+ case '7':
+ arcname = DEV_7;
+ break;
+ case '8':
+ arcname = DEV_8;
+ break;
+ case OPT_ATIME_PRESERVE:
+ patime = 1;
+ break;
+ case OPT_UNLINK:
+ /* Just ignore -- we always unlink first. */
+ break;
+ case OPT_USE_COMPRESS_PROGRAM:
+ gzip_program = optarg;
+ break;
+ case OPT_FORCE_LOCAL:
+ forcelocal = 1;
+ break;
+ case OPT_INSECURE:
+ secure = 0;
+ break;
+ case OPT_STRICT:
+ /* disable gnu extensions */
+ is_gnutar = 0;
+ break;
+ case OPT_EXCLUDE:
+ if (tar_gnutar_minus_minus_exclude(optarg) != 0)
+ tar_usage();
+ break;
+ case OPT_NORECURSE:
+ dflag = 1;
+ break;
+#if !HAVE_NBTOOL_CONFIG_H
+ case OPT_CHROOT:
+ do_chroot = 1;
+ break;
+#endif
+#ifndef SMALL
+ case OPT_TIMESTAMP:
+ if (set_tstamp(optarg, &tst) == -1) {
+ tty_warn(1, "Invalid timestamp `%s'", optarg);
+ tar_usage();
+ }
+ break;
+#endif
+ default:
+ tar_usage();
+ break;
+ }
+ }
+ argc -= optind;
+ argv += optind;
+
+ /* Tar requires an action. */
+ if (act == ERROR)
+ tar_usage();
+
+ /* Traditional tar behaviour (pax uses stderr unless in list mode) */
+ if (fstdin == 1 && act == ARCHIVE)
+ listf = stderr;
+ else
+ listf = stdout;
+
+ /* Traditional tar behaviour (pax wants to read file list from stdin) */
+ if ((act == ARCHIVE || act == APPND) && argc == 0 && nincfiles == 0)
+ exit(0);
+ /*
+ * if we are writing (ARCHIVE) specify tar, otherwise run like pax
+ * (unless -o specified)
+ */
+ if (act == ARCHIVE || act == APPND)
+ frmt = &(fsub[Oflag ? F_TAR : F_USTAR]);
+ else if (Oflag) {
+ if (act == EXTRACT)
+ to_stdout = 1;
+ else {
+ tty_warn(1, "The -O/-o options are only valid when "
+ "writing or extracting an archive");
+ tar_usage();
+ }
+ }
+
+ /*
+ * process the args as they are interpreted by the operation mode
+ */
+ switch (act) {
+ case LIST:
+ case EXTRACT:
+ default:
+ {
+ int sawpat = 0;
+ int dirisnext = 0;
+ char *file, *dir = NULL;
+ int mustfreedir = 0;
+
+ while (nincfiles || *argv != NULL) {
+ /*
+ * If we queued up any include files,
+ * pull them in now. Otherwise, check
+ * for -I and -C positional flags.
+ * Anything else must be a file to
+ * extract.
+ */
+ if (nincfiles) {
+ file = incfiles->file;
+ dir = incfiles->dir;
+ mustfreedir = 0;
+ incfiles++;
+ nincfiles--;
+ } else if (strcmp(*argv, "-I") == 0) {
+ if (*++argv == NULL)
+ break;
+ file = *argv++;
+ dir = chdname;
+ mustfreedir = 0;
+ } else {
+ file = NULL;
+ dir = NULL;
+ mustfreedir = 0;
+ }
+ if (file != NULL) {
+ FILE *fp;
+ char *str;
+
+ if (strcmp(file, "-") == 0)
+ fp = stdin;
+ else if ((fp = fopen(file, "r")) == NULL) {
+ tty_warn(1, "Unable to open file '%s' for read", file);
+ tar_usage();
+ }
+ while ((str = get_line(fp)) != NULL) {
+ if (dirisnext) {
+ if (dir && mustfreedir)
+ free(dir);
+ dir = str;
+ mustfreedir = 1;
+ dirisnext = 0;
+ continue;
+ }
+ if (strcmp(str, "-C") == 0) {
+ havechd++;
+ dirisnext = 1;
+ free(str);
+ continue;
+ }
+ if (strncmp(str, "-C ", 3) == 0) {
+ havechd++;
+ if (dir && mustfreedir)
+ free(dir);
+ dir = strdup(str + 3);
+ mustfreedir = 1;
+ free(str);
+ continue;
+ }
+ if (pat_add(str, dir, NOGLOB_MTCH) < 0)
+ tar_usage();
+ sawpat = 1;
+ }
+ /* Bomb if given -C w/out a dir. */
+ if (dirisnext)
+ tar_usage();
+ if (dir && mustfreedir)
+ free(dir);
+ if (strcmp(file, "-") != 0)
+ fclose(fp);
+ if (get_line_error) {
+ tty_warn(1, "Problem with file '%s'", file);
+ tar_usage();
+ }
+ } else if (strcmp(*argv, "-C") == 0) {
+ if (*++argv == NULL)
+ break;
+ chdname = *argv++;
+ havechd++;
+ } else if (pat_add(*argv++, chdname, 0) < 0)
+ tar_usage();
+ else
+ sawpat = 1;
+ }
+ /*
+ * if patterns were added, we are doing chdir()
+ * on a file-by-file basis, else, just one
+ * global chdir (if any) after opening input.
+ */
+ if (sawpat > 0)
+ chdname = NULL;
+ }
+ break;
+ case ARCHIVE:
+ case APPND:
+ if (chdname != NULL) { /* initial chdir() */
+ if (ftree_add(chdname, 1) < 0)
+ tar_usage();
+ }
+
+ while (nincfiles || *argv != NULL) {
+ char *file, *dir;
+
+ /*
+ * If we queued up any include files, pull them in
+ * now. Otherwise, check for -I and -C positional
+ * flags. Anything else must be a file to include
+ * in the archive.
+ */
+ if (nincfiles) {
+ file = incfiles->file;
+ dir = incfiles->dir;
+ incfiles++;
+ nincfiles--;
+ } else if (strcmp(*argv, "-I") == 0) {
+ if (*++argv == NULL)
+ break;
+ file = *argv++;
+ dir = NULL;
+ } else {
+ file = NULL;
+ dir = NULL;
+ }
+ if (file != NULL) {
+ FILE *fp;
+ char *str;
+ int dirisnext = 0;
+
+ /* Set directory if needed */
+ if (dir) {
+ if (ftree_add(dir, 1) < 0)
+ tar_usage();
+ }
+
+ if (strcmp(file, "-") == 0)
+ fp = stdin;
+ else if ((fp = fopen(file, "r")) == NULL) {
+ tty_warn(1, "Unable to open file '%s' for read", file);
+ tar_usage();
+ }
+ while ((str = get_line(fp)) != NULL) {
+ if (dirisnext) {
+ if (ftree_add(str, 1) < 0)
+ tar_usage();
+ dirisnext = 0;
+ continue;
+ }
+ if (strcmp(str, "-C") == 0) {
+ dirisnext = 1;
+ continue;
+ }
+ if (strncmp(str, "-C ", 3) == 0) {
+ if (ftree_add(str + 3, 1) < 0)
+ tar_usage();
+ continue;
+ }
+ if (ftree_add(str, 0) < 0)
+ tar_usage();
+ }
+ /* Bomb if given -C w/out a dir. */
+ if (dirisnext)
+ tar_usage();
+ if (strcmp(file, "-") != 0)
+ fclose(fp);
+ if (get_line_error) {
+ tty_warn(1, "Problem with file '%s'",
+ file);
+ tar_usage();
+ }
+ } else if (strcmp(*argv, "-C") == 0) {
+ if (*++argv == NULL)
+ break;
+ if (ftree_add(*argv++, 1) < 0)
+ tar_usage();
+ } else if (ftree_add(*argv++, 0) < 0)
+ tar_usage();
+ }
+ /*
+ * no read errors allowed on updates/append operation!
+ */
+ maxflt = 0;
+ break;
+ }
+ if (!fstdin && ((arcname == NULL) || (*arcname == '\0'))) {
+ arcname = getenv("TAPE");
+ if ((arcname == NULL) || (*arcname == '\0'))
+ arcname = _PATH_DEFTAPE;
+ }
+}
+
+int
+mkpath(char *path)
+{
+ char *slash;
+ int done = 0;
+
+ slash = path;
+
+ while (!done) {
+ slash += strspn(slash, "/");
+ slash += strcspn(slash, "/");
+
+ done = (*slash == '\0');
+ *slash = '\0';
+
+ if (domkdir(path, 0777) == -1)
+ goto out;
+
+ if (!done)
+ *slash = '/';
+ }
+
+ return 0;
+out:
+ /* Can't create or or not a directory */
+ syswarn(1, errno, "Cannot create directory `%s'", path);
+ return -1;
+}
+
+
+#ifndef NO_CPIO
+struct option cpio_longopts[] = {
+ { "reset-access-time", no_argument, 0, 'a' },
+ { "make-directories", no_argument, 0, 'd' },
+ { "nonmatching", no_argument, 0, 'f' },
+ { "extract", no_argument, 0, 'i' },
+ { "link", no_argument, 0, 'l' },
+ { "preserve-modification-time", no_argument, 0, 'm' },
+ { "create", no_argument, 0, 'o' },
+ { "pass-through", no_argument, 0, 'p' },
+ { "rename", no_argument, 0, 'r' },
+ { "list", no_argument, 0, 't' },
+ { "unconditional", no_argument, 0, 'u' },
+ { "verbose", no_argument, 0, 'v' },
+ { "append", no_argument, 0, 'A' },
+ { "pattern-file", required_argument, 0, 'E' },
+ { "file", required_argument, 0, 'F' },
+ { "force-local", no_argument, 0,
+ OPT_FORCE_LOCAL },
+ { "format", required_argument, 0, 'H' },
+ { "dereference", no_argument, 0, 'L' },
+ { "swap-halfwords", no_argument, 0, 'S' },
+ { "summary", no_argument, 0, 'V' },
+ { "stats", no_argument, 0, 'V' },
+ { "insecure", no_argument, 0,
+ OPT_INSECURE },
+ { "sparse", no_argument, 0,
+ OPT_SPARSE },
+ { "xz", no_argument, 0,
+ OPT_XZ },
+
+#ifdef notyet
+/* Not implemented */
+ { "null", no_argument, 0, '0' },
+ { "swap", no_argument, 0, 'b' },
+ { "numeric-uid-gid", no_argument, 0, 'n' },
+ { "swap-bytes", no_argument, 0, 's' },
+ { "message", required_argument, 0, 'M' },
+ { "owner", required_argument, 0 'R' },
+ { "dot", no_argument, 0, 'V' }, /* xxx */
+ { "block-size", required_argument, 0,
+ OPT_BLOCK_SIZE },
+ { "no-absolute-pathnames", no_argument, 0,
+ OPT_NO_ABSOLUTE_PATHNAMES },
+ { "no-preserve-owner", no_argument, 0,
+ OPT_NO_PRESERVE_OWNER },
+ { "only-verify-crc", no_argument, 0,
+ OPT_ONLY_VERIFY_CRC },
+ { "rsh-command", required_argument, 0,
+ OPT_RSH_COMMAND },
+ { "version", no_argument, 0,
+ OPT_VERSION },
+#endif
+ { 0, 0, 0, 0 },
+};
+
+static void
+cpio_set_action(int op)
+{
+ if ((act == APPND && op == ARCHIVE) || (act == ARCHIVE && op == APPND))
+ act = APPND;
+ else if (act == EXTRACT && op == LIST)
+ act = op;
+ else if (act != ERROR && act != op)
+ cpio_usage();
+ else
+ act = op;
+}
+
+/*
+ * cpio_options()
+ * look at the user specified flags. set globals as required and check if
+ * the user specified a legal set of flags. If not, complain and exit
+ */
+
+static void
+cpio_options(int argc, char **argv)
+{
+ FSUB tmp;
+ u_int64_t flg = 0;
+ u_int64_t bflg = 0;
+ int c;
+ size_t i;
+ FILE *fp;
+ char *str;
+
+ uflag = 1;
+ kflag = 1;
+ pids = 1;
+ pmode = 1;
+ pmtime = 0;
+ arcname = NULL;
+ dflag = 1;
+ nodirs = 1;
+ /*
+ * process option flags
+ */
+ while ((c = getoldopt(argc, argv,
+ "+abcdfiklmoprstuvzABC:E:F:H:I:LM:O:R:SVZ6",
+ cpio_longopts, NULL)) != -1) {
+ switch(c) {
+ case 'a':
+ /*
+ * preserve access time on filesystem nodes we read
+ */
+ tflag = 1;
+ flg |= TF;
+ break;
+#ifdef notyet
+ case 'b':
+ /*
+ * swap bytes and half-words when reading data
+ */
+ break;
+#endif
+ case 'c':
+ /*
+ * ASCII cpio header
+ */
+ frmt = &fsub[F_SV4CPIO];
+ break;
+ case 'd':
+ /*
+ * create directories as needed
+ * pax does this by default ..
+ */
+ nodirs = 0;
+ break;
+ case 'f':
+ /*
+ * inverse match on patterns
+ */
+ cflag = 1;
+ flg |= CF;
+ break;
+ case 'i':
+ /*
+ * read the archive
+ */
+ cpio_set_action(EXTRACT);
+ flg |= RF;
+ break;
+#ifdef notyet
+ case 'k':
+ break;
+#endif
+ case 'l':
+ /*
+ * try to link src to dest with copy (-rw)
+ */
+ lflag = 1;
+ flg |= LF;
+ break;
+ case 'm':
+ /*
+ * preserve mtime
+ */
+ flg |= PF;
+ pmtime = 1;
+ break;
+ case 'o':
+ /*
+ * write an archive
+ */
+ cpio_set_action(ARCHIVE);
+ frmt = &(fsub[F_SV4CRC]);
+ flg |= WF;
+ break;
+ case 'p':
+ /*
+ * cpio -p is like pax -rw
+ */
+ cpio_set_action(COPY);
+ flg |= RF | WF;
+ break;
+ case 'r':
+ /*
+ * interactive file rename
+ */
+ iflag = 1;
+ flg |= IF;
+ break;
+#ifdef notyet
+ case 's':
+ /*
+ * swap bytes after reading data
+ */
+ break;
+#endif
+ case 't':
+ /*
+ * list contents of archive
+ */
+ cpio_set_action(LIST);
+ listf = stdout;
+ flg &= ~RF;
+ break;
+ case 'u':
+ /*
+ * don't ignore those older files
+ */
+ uflag = 0;
+ kflag = 0;
+ flg |= UF;
+ break;
+ case 'v':
+ /*
+ * verbose operation mode
+ */
+ vflag = 1;
+ flg |= VF;
+ break;
+ case 'z':
+ /*
+ * use gzip. Non standard option.
+ */
+ gzip_program = GZIP_CMD;
+ break;
+ case 'A':
+ /*
+ * append to an archive
+ */
+ cpio_set_action(APPND);
+ flg |= AF;
+ break;
+ case 'B':
+ /*
+ * set blocksize to 5120
+ */
+ blksz = 5120;
+ break;
+ case 'C':
+ /*
+ * specify blocksize
+ */
+ if ((blksz = (int)str_offt(optarg)) <= 0) {
+ tty_warn(1, "Invalid block size %s", optarg);
+ cpio_usage();
+ }
+ break;
+ case 'E':
+ /*
+ * file with patterns to extract or list
+ */
+ if ((fp = fopen(optarg, "r")) == NULL) {
+ tty_warn(1, "Unable to open file '%s' for read",
+ optarg);
+ cpio_usage();
+ }
+ while ((str = get_line(fp)) != NULL) {
+ pat_add(str, NULL, 0);
+ }
+ fclose(fp);
+ if (get_line_error) {
+ tty_warn(1, "Problem with file '%s'", optarg);
+ cpio_usage();
+ }
+ break;
+ case 'H':
+ /*
+ * specify an archive format on write
+ */
+ tmp.name = optarg;
+ frmt = (FSUB *)bsearch((void *)&tmp, (void *)fsub,
+ sizeof(fsub)/sizeof(FSUB), sizeof(FSUB), c_frmt);
+ if (frmt != NULL) {
+ flg |= XF;
+ break;
+ }
+ tty_warn(1, "Unknown -H format: %s", optarg);
+ (void)fputs("cpio: Known -H formats are:", stderr);
+ for (i = 0; i < (sizeof(fsub)/sizeof(FSUB)); ++i)
+ (void)fprintf(stderr, " %s", fsub[i].name);
+ (void)fputs("\n\n", stderr);
+ cpio_usage();
+ break;
+ case 'F':
+ case 'I':
+ case 'O':
+ /*
+ * filename where the archive is stored
+ */
+ if ((optarg[0] == '-') && (optarg[1]== '\0')) {
+ /*
+ * treat a - as stdin
+ */
+ arcname = NULL;
+ break;
+ }
+ arcname = optarg;
+ break;
+ case 'L':
+ /*
+ * follow symlinks
+ */
+ Lflag = 1;
+ flg |= CLF;
+ break;
+#ifdef notyet
+ case 'M':
+ arg = optarg;
+ break;
+ case 'R':
+ arg = optarg;
+ break;
+#endif
+ case 'S':
+ /*
+ * swap halfwords after reading data
+ */
+ cpio_swp_head = 1;
+ break;
+#ifdef notyet
+ case 'V': /* print a '.' for each file processed */
+ break;
+#endif
+ case 'V':
+ /*
+ * semi-verbose operation mode (no listing)
+ */
+ Vflag = 1;
+ flg |= VF;
+ break;
+ case 'Z':
+ /*
+ * use compress. Non standard option.
+ */
+ gzip_program = COMPRESS_CMD;
+ break;
+ case '6':
+ /*
+ * process Version 6 cpio format
+ */
+ frmt = &(fsub[F_BCPIO]);
+ break;
+ case OPT_FORCE_LOCAL:
+ forcelocal = 1;
+ break;
+ case OPT_INSECURE:
+ secure = 0;
+ break;
+ case OPT_SPARSE:
+ /* do nothing; we already generate sparse files */
+ break;
+ case OPT_XZ:
+ gzip_program = XZ_CMD;
+ break;
+ default:
+ cpio_usage();
+ break;
+ }
+ }
+
+ /*
+ * figure out the operation mode of cpio. check that we have not been
+ * given a bogus set of flags for the operation mode.
+ */
+ if (ISLIST(flg)) {
+ act = LIST;
+ bflg = flg & BDLIST;
+ } else if (ISEXTRACT(flg)) {
+ act = EXTRACT;
+ bflg = flg & BDEXTR;
+ } else if (ISARCHIVE(flg)) {
+ act = ARCHIVE;
+ bflg = flg & BDARCH;
+ } else if (ISAPPND(flg)) {
+ act = APPND;
+ bflg = flg & BDARCH;
+ } else if (ISCOPY(flg)) {
+ act = COPY;
+ bflg = flg & BDCOPY;
+ } else
+ cpio_usage();
+ if (bflg) {
+ cpio_usage();
+ }
+
+ /*
+ * if we are writing (ARCHIVE) we use the default format if the user
+ * did not specify a format. when we write during an APPEND, we will
+ * adopt the format of the existing archive if none was supplied.
+ */
+ if (!(flg & XF) && (act == ARCHIVE))
+ frmt = &(fsub[F_BCPIO]);
+
+ /*
+ * process the args as they are interpreted by the operation mode
+ */
+ switch (act) {
+ case LIST:
+ case EXTRACT:
+ for (; optind < argc; optind++)
+ if (pat_add(argv[optind], NULL, 0) < 0)
+ cpio_usage();
+ break;
+ case COPY:
+ if (optind >= argc) {
+ tty_warn(0, "Destination directory was not supplied");
+ cpio_usage();
+ }
+ --argc;
+ dirptr = argv[argc];
+ /* FALLTHROUGH */
+ case ARCHIVE:
+ case APPND:
+ if (argc != optind) {
+ for (; optind < argc; optind++)
+ if (ftree_add(argv[optind], 0) < 0)
+ cpio_usage();
+ break;
+ }
+ /*
+ * no read errors allowed on updates/append operation!
+ */
+ maxflt = 0;
+ while ((str = get_line(stdin)) != NULL) {
+ ftree_add(str, 0);
+ }
+ if (get_line_error) {
+ tty_warn(1, "Problem while reading stdin");
+ cpio_usage();
+ }
+ break;
+ default:
+ cpio_usage();
+ break;
+ }
+}
+#endif
+
+/*
+ * printflg()
+ * print out those invalid flag sets found to the user
+ */
+
+static void
+printflg(unsigned int flg)
+{
+ int nxt;
+
+ (void)fprintf(stderr,"%s: Invalid combination of options:", argv0);
+ while ((nxt = ffs(flg)) != 0) {
+ flg &= ~(1 << (nxt - 1));
+ (void)fprintf(stderr, " -%c", flgch[nxt - 1]);
+ }
+ (void)putc('\n', stderr);
+}
+
+/*
+ * c_frmt()
+ * comparison routine used by bsearch to find the format specified
+ * by the user
+ */
+
+static int
+c_frmt(const void *a, const void *b)
+{
+ return strcmp(((const FSUB *)a)->name, ((const FSUB *)b)->name);
+}
+
+/*
+ * opt_next()
+ * called by format specific options routines to get each format specific
+ * flag and value specified with -o
+ * Return:
+ * pointer to next OPLIST entry or NULL (end of list).
+ */
+
+OPLIST *
+opt_next(void)
+{
+ OPLIST *opt;
+
+ if ((opt = ophead) != NULL)
+ ophead = ophead->fow;
+ return opt;
+}
+
+/*
+ * bad_opt()
+ * generic routine used to complain about a format specific options
+ * when the format does not support options.
+ */
+
+int
+bad_opt(void)
+{
+ OPLIST *opt;
+
+ if (ophead == NULL)
+ return 0;
+ /*
+ * print all we were given
+ */
+ tty_warn(1," These format options are not supported for %s",
+ frmt->name);
+ while ((opt = opt_next()) != NULL)
+ (void)fprintf(stderr, "\t%s = %s\n", opt->name, opt->value);
+ if (strcmp(NM_TAR, argv0) == 0)
+ tar_usage();
+#ifndef NO_CPIO
+ else if (strcmp(NM_CPIO, argv0) == 0)
+ cpio_usage();
+#endif
+ else
+ pax_usage();
+ return 0;
+}
+
+/*
+ * opt_add()
+ * breaks the value supplied to -o into a option name and value. options
+ * are given to -o in the form -o name-value,name=value
+ * multiple -o may be specified.
+ * Return:
+ * 0 if format in name=value format, -1 if -o is passed junk
+ */
+
+int
+opt_add(const char *str)
+{
+ OPLIST *opt;
+ char *frpt;
+ char *pt;
+ char *endpt;
+ char *dstr;
+
+ if ((str == NULL) || (*str == '\0')) {
+ tty_warn(0, "Invalid option name");
+ return -1;
+ }
+ if ((dstr = strdup(str)) == NULL) {
+ tty_warn(0, "Unable to allocate space for option list");
+ return -1;
+ }
+ frpt = endpt = dstr;
+
+ /*
+ * break into name and values pieces and stuff each one into a
+ * OPLIST structure. When we know the format, the format specific
+ * option function will go through this list
+ */
+ while ((frpt != NULL) && (*frpt != '\0')) {
+ if ((endpt = strchr(frpt, ',')) != NULL)
+ *endpt = '\0';
+ if ((pt = strchr(frpt, '=')) == NULL) {
+ tty_warn(0, "Invalid options format");
+ free(dstr);
+ return -1;
+ }
+ if ((opt = (OPLIST *)malloc(sizeof(OPLIST))) == NULL) {
+ tty_warn(0, "Unable to allocate space for option list");
+ free(dstr);
+ return -1;
+ }
+ *pt++ = '\0';
+ opt->name = frpt;
+ opt->value = pt;
+ opt->fow = NULL;
+ if (endpt != NULL)
+ frpt = endpt + 1;
+ else
+ frpt = NULL;
+ if (ophead == NULL) {
+ optail = ophead = opt;
+ continue;
+ }
+ optail->fow = opt;
+ optail = opt;
+ }
+ return 0;
+}
+
+/*
+ * str_offt()
+ * Convert an expression of the following forms to an off_t > 0.
+ * 1) A positive decimal number.
+ * 2) A positive decimal number followed by a b (mult by 512).
+ * 3) A positive decimal number followed by a k (mult by 1024).
+ * 4) A positive decimal number followed by a m (mult by 512).
+ * 5) A positive decimal number followed by a w (mult by sizeof int)
+ * 6) Two or more positive decimal numbers (with/without k,b or w).
+ * separated by x (also * for backwards compatibility), specifying
+ * the product of the indicated values.
+ * Return:
+ * 0 for an error, a positive value o.w.
+ */
+
+static off_t
+str_offt(char *val)
+{
+ char *expr;
+ off_t num, t;
+
+ num = STRTOOFFT(val, &expr, 0);
+ if ((num == OFFT_MAX) || (num <= 0) || (expr == val))
+ return 0;
+
+ switch(*expr) {
+ case 'b':
+ t = num;
+ num *= 512;
+ if (t > num)
+ return 0;
+ ++expr;
+ break;
+ case 'k':
+ t = num;
+ num *= 1024;
+ if (t > num)
+ return 0;
+ ++expr;
+ break;
+ case 'm':
+ t = num;
+ num *= 1048576;
+ if (t > num)
+ return 0;
+ ++expr;
+ break;
+ case 'w':
+ t = num;
+ num *= sizeof(int);
+ if (t > num)
+ return 0;
+ ++expr;
+ break;
+ }
+
+ switch(*expr) {
+ case '\0':
+ break;
+ case '*':
+ case 'x':
+ t = num;
+ num *= str_offt(expr + 1);
+ if (t > num)
+ return 0;
+ break;
+ default:
+ return 0;
+ }
+ return num;
+}
+
+static char *
+get_line(FILE *f)
+{
+ char *name, *temp;
+ size_t len;
+
+ name = fgetln(f, &len);
+ if (!name) {
+ get_line_error = ferror(f) ? GETLINE_FILE_CORRUPT : 0;
+ return 0;
+ }
+ if (name[len-1] != '\n')
+ len++;
+ temp = malloc(len);
+ if (!temp) {
+ get_line_error = GETLINE_OUT_OF_MEM;
+ return 0;
+ }
+ memcpy(temp, name, len-1);
+ temp[len-1] = 0;
+ return temp;
+}
+
+#ifndef SMALL
+/*
+ * set_tstamp()
+ * Use a specific timestamp for all individual files created in the
+ * archive
+ */
+static int
+set_tstamp(const char *b, struct stat *st)
+{
+ time_t when;
+ char *eb;
+ long long l;
+
+ if (stat(b, st) != -1)
+ return 0;
+
+#ifndef HAVE_NBTOOL_CONFIG_H
+ errno = 0;
+ if ((when = parsedate(b, NULL, NULL)) == -1 && errno != 0)
+#endif
+ {
+ errno = 0;
+ l = strtoll(b, &eb, 0);
+ if (b == eb || *eb || errno)
+ return -1;
+ when = (time_t)l;
+ }
+
+ st->st_ino = 1;
+#if HAVE_STRUCT_STAT_BIRTHTIME
+ st->st_birthtime =
+#endif
+ st->st_mtime = st->st_ctime = st->st_atime = when;
+ return 0;
+}
+#endif
+
+/*
+ * no_op()
+ * for those option functions where the archive format has nothing to do.
+ * Return:
+ * 0
+ */
+
+static int
+no_op(void)
+{
+ return 0;
+}
+
+/*
+ * pax_usage()
+ * print the usage summary to the user
+ */
+
+static void
+pax_usage(void)
+{
+ fprintf(stderr,
+"usage: pax [-0cdjnvzVO] [-E limit] [-f archive] [-N dbdir] [-s replstr] ...\n"
+" [-U user] ... [-G group] ... [-T [from_date][,to_date]] ...\n"
+" [pattern ...]\n");
+ fprintf(stderr,
+" pax -r [-cdijknuvzADOVYZ] [-E limit] [-f archive] [-N dbdir]\n"
+" [-o options] ... [-p string] ... [-s replstr] ... [-U user] ...\n"
+" [-G group] ... [-T [from_date][,to_date]] ... [pattern ...]\n");
+ fprintf(stderr,
+" pax -w [-dijtuvzAHLMOPVX] [-b blocksize] [[-a] [-f archive]] [-x format]\n"
+" [-B bytes] [-N dbdir] [-o options] ... [-s replstr] ...\n"
+" [-U user] ... [-G group] ...\n"
+" [-T [from_date][,to_date][/[c][m]]] ... [file ...]\n");
+ fprintf(stderr,
+" pax -r -w [-dijklntuvzADHLMOPVXYZ] [-N dbdir] [-p string] ...\n"
+" [-s replstr] ... [-U user] ... [-G group] ...\n"
+" [-T [from_date][,to_date][/[c][m]]] ... [file ...] directory\n");
+ exit(1);
+ /* NOTREACHED */
+}
+
+/*
+ * tar_usage()
+ * print the usage summary to the user
+ */
+
+static void
+tar_usage(void)
+{
+ (void)fputs("usage: tar [-]{crtux}[-befhjklmopqvwzHJOPSXZ014578] "
+ "[archive] [blocksize]\n"
+ " [-C directory] [-T file] [-s replstr] "
+ "[file ...]\n", stderr);
+ exit(1);
+ /* NOTREACHED */
+}
+
+#ifndef NO_CPIO
+/*
+ * cpio_usage()
+ * print the usage summary to the user
+ */
+
+static void
+cpio_usage(void)
+{
+
+ (void)fputs("usage: cpio -o [-aABcLvzZ] [-C bytes] [-F archive] "
+ "[-H format] [-O archive]\n"
+ " < name-list [> archive]\n"
+ " cpio -i [-bBcdfmrsStuvzZ6] [-C bytes] [-E file] "
+ "[-F archive] [-H format] \n"
+ " [-I archive] "
+ "[pattern ...] [< archive]\n"
+ " cpio -p [-adlLmuv] destination-directory "
+ "< name-list\n", stderr);
+ exit(1);
+ /* NOTREACHED */
+}
+#endif
diff --git a/bin/pax/options.h b/bin/pax/options.h
new file mode 100644
index 0000000..6517174
--- /dev/null
+++ b/bin/pax/options.h
@@ -0,0 +1,116 @@
+/* $NetBSD: options.h,v 1.11 2007/04/23 18:40:22 christos Exp $ */
+
+/*-
+ * Copyright (c) 1992 Keith Muller.
+ * Copyright (c) 1992, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Keith Muller of the University of California, San Diego.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * @(#)options.h 8.2 (Berkeley) 4/18/94
+ */
+
+/*
+ * argv[0] names. Used for tar and cpio emulation
+ */
+
+#define NM_TAR "tar"
+#define NM_CPIO "cpio"
+#define NM_PAX "pax"
+
+/* special value for -E */
+#define none "none"
+
+/*
+ * Constants used to specify the legal sets of flags in pax. For each major
+ * operation mode of pax, a set of illegal flags is defined. If any one of
+ * those illegal flags are found set, we scream and exit
+ */
+
+/*
+ * flags (one for each option).
+ */
+#define AF 0x000000001ULL
+#define BF 0x000000002ULL
+#define CF 0x000000004ULL
+#define DF 0x000000008ULL
+#define FF 0x000000010ULL
+#define IF 0x000000020ULL
+#define KF 0x000000040ULL
+#define LF 0x000000080ULL
+#define NF 0x000000100ULL
+#define OF 0x000000200ULL
+#define PF 0x000000400ULL
+#define RF 0x000000800ULL
+#define SF 0x000001000ULL
+#define TF 0x000002000ULL
+#define UF 0x000004000ULL
+#define VF 0x000008000ULL
+#define WF 0x000010000ULL
+#define XF 0x000020000ULL
+#define CAF 0x000040000ULL /* nonstandard extension */
+#define CBF 0x000080000ULL /* nonstandard extension */
+#define CDF 0x000100000ULL /* nonstandard extension */
+#define CEF 0x000200000ULL /* nonstandard extension */
+#define CGF 0x000400000ULL /* nonstandard extension */
+#define CHF 0x000800000ULL /* nonstandard extension */
+#define CLF 0x001000000ULL /* nonstandard extension */
+#define CMF 0x002000000ULL /* nonstandard extension */
+#define CPF 0x004000000ULL /* nonstandard extension */
+#define CTF 0x008000000ULL /* nonstandard extension */
+#define CUF 0x010000000ULL /* nonstandard extension */
+#define VSF 0x020000000ULL /* non-standard */
+#define CXF 0x040000000ULL
+#define CYF 0x080000000ULL /* nonstandard extension */
+#define CZF 0x100000000ULL /* nonstandard extension */
+
+/*
+ * ascii string indexed by bit position above (alter the above and you must
+ * alter this string) used to tell the user what flags caused us to complain
+ */
+#define FLGCH "abcdfiklnoprstuvwxABDEGHLMPTUVXYZ"
+
+/*
+ * legal pax operation bit patterns
+ */
+
+#define ISLIST(x) (((x) & (RF|WF)) == 0)
+#define ISEXTRACT(x) (((x) & (RF|WF)) == RF)
+#define ISARCHIVE(x) (((x) & (AF|RF|WF)) == WF)
+#define ISAPPND(x) (((x) & (AF|RF|WF)) == (AF|WF))
+#define ISCOPY(x) (((x) & (RF|WF)) == (RF|WF))
+#define ISWRITE(x) (((x) & (RF|WF)) == WF)
+
+/*
+ * Illegal option flag subsets based on pax operation
+ */
+
+#define BDEXTR (AF|BF|LF|TF|WF|XF|CBF|CHF|CLF|CMF|CPF|CXF)
+#define BDARCH (CF|KF|LF|NF|PF|RF|CDF|CEF|CYF|CZF)
+#define BDCOPY (AF|BF|FF|OF|XF|CAF|CBF|CEF)
+#define BDLIST (AF|BF|IF|KF|LF|OF|PF|RF|TF|UF|WF|XF|CBF|CDF|CHF|CLF|CMF|CPF|CXF|CYF|CZF)
diff --git a/bin/pax/pat_rep.c b/bin/pax/pat_rep.c
new file mode 100644
index 0000000..7dbcda7
--- /dev/null
+++ b/bin/pax/pat_rep.c
@@ -0,0 +1,1139 @@
+/* $NetBSD: pat_rep.c,v 1.30 2018/06/13 15:14:40 christos Exp $ */
+
+/*-
+ * Copyright (c) 1992 Keith Muller.
+ * Copyright (c) 1992, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Keith Muller of the University of California, San Diego.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#if HAVE_NBTOOL_CONFIG_H
+#include "nbtool_config.h"
+#endif
+
+#include <sys/cdefs.h>
+#if !defined(lint)
+#if 0
+static char sccsid[] = "@(#)pat_rep.c 8.2 (Berkeley) 4/18/94";
+#else
+__RCSID("$NetBSD: pat_rep.c,v 1.30 2018/06/13 15:14:40 christos Exp $");
+#endif
+#endif /* not lint */
+
+#include <sys/types.h>
+#include <sys/time.h>
+#include <sys/stat.h>
+#include <sys/param.h>
+#include <stdio.h>
+#include <ctype.h>
+#include <string.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include "pax.h"
+#include "pat_rep.h"
+#include "extern.h"
+
+/*
+ * routines to handle pattern matching, name modification (regular expression
+ * substitution and interactive renames), and destination name modification for
+ * copy (-rw). Both file name and link names are adjusted as required in these
+ * routines.
+ */
+
+#define MAXSUBEXP 10 /* max subexpressions, DO NOT CHANGE */
+static PATTERN *pathead = NULL; /* file pattern match list head */
+static PATTERN *pattail = NULL; /* file pattern match list tail */
+static REPLACE *rephead = NULL; /* replacement string list head */
+static REPLACE *reptail = NULL; /* replacement string list tail */
+
+static int rep_name(char *, size_t, int *, int);
+static int tty_rename(ARCHD *);
+static int fix_path(char *, int *, char *, int);
+static int fn_match(char *, char *, char **, int);
+static char * range_match(char *, int);
+static int checkdotdot(const char *);
+static int resub(regex_t *, regmatch_t *, char *, char *, char *, char *);
+
+/*
+ * rep_add()
+ * parses the -s replacement string; compiles the regular expression
+ * and stores the compiled value and its replacement string together in
+ * replacement string list. Input to this function is of the form:
+ * /old/new/pg
+ * The first char in the string specifies the delimiter used by this
+ * replacement string. "Old" is a regular expression in "ed" format which
+ * is compiled by regcomp() and is applied to filenames. "new" is the
+ * substitution string; p and g are options flags for printing and global
+ * replacement (over the single filename)
+ * Return:
+ * 0 if a proper replacement string and regular expression was added to
+ * the list of replacement patterns; -1 otherwise.
+ */
+
+int
+rep_add(char *str)
+{
+ char *pt1;
+ char *pt2;
+ REPLACE *rep;
+ int res;
+ char rebuf[BUFSIZ];
+
+ /*
+ * throw out the bad parameters
+ */
+ if ((str == NULL) || (*str == '\0')) {
+ tty_warn(1, "Empty replacement string");
+ return -1;
+ }
+
+ /*
+ * first character in the string specifies what the delimiter is for
+ * this expression.
+ */
+ for (pt1 = str+1; *pt1; pt1++) {
+ if (*pt1 == '\\') {
+ pt1++;
+ continue;
+ }
+ if (*pt1 == *str)
+ break;
+ }
+ if (*pt1 == 0) {
+ tty_warn(1, "Invalid replacement string %s", str);
+ return -1;
+ }
+
+ /*
+ * allocate space for the node that handles this replacement pattern
+ * and split out the regular expression and try to compile it
+ */
+ if ((rep = (REPLACE *)malloc(sizeof(REPLACE))) == NULL) {
+ tty_warn(1, "Unable to allocate memory for replacement string");
+ return -1;
+ }
+
+ *pt1 = '\0';
+ if ((res = regcomp(&(rep->rcmp), str+1, 0)) != 0) {
+ regerror(res, &(rep->rcmp), rebuf, sizeof(rebuf));
+ tty_warn(1, "%s while compiling regular expression %s", rebuf,
+ str);
+ (void)free((char *)rep);
+ return -1;
+ }
+
+ /*
+ * put the delimiter back in case we need an error message and
+ * locate the delimiter at the end of the replacement string
+ * we then point the node at the new substitution string
+ */
+ *pt1++ = *str;
+ for (pt2 = pt1; *pt2; pt2++) {
+ if (*pt2 == '\\') {
+ pt2++;
+ continue;
+ }
+ if (*pt2 == *str)
+ break;
+ }
+ if (*pt2 == 0) {
+ regfree(&(rep->rcmp));
+ (void)free((char *)rep);
+ tty_warn(1, "Invalid replacement string %s", str);
+ return -1;
+ }
+
+ *pt2 = '\0';
+
+ /* Make sure to dup replacement, who knows where it came from! */
+ if ((rep->nstr = strdup(pt1)) == NULL) {
+ regfree(&(rep->rcmp));
+ (void)free((char *)rep);
+ tty_warn(1, "Unable to allocate memory for replacement string");
+ return -1;
+ }
+
+ pt1 = pt2++;
+ rep->flgs = 0;
+
+ /*
+ * set the options if any
+ */
+ while (*pt2 != '\0') {
+ switch(*pt2) {
+ case 'g':
+ case 'G':
+ rep->flgs |= GLOB;
+ break;
+ case 'p':
+ case 'P':
+ rep->flgs |= PRNT;
+ break;
+ case 's':
+ case 'S':
+ rep->flgs |= SYML;
+ break;
+ default:
+ regfree(&(rep->rcmp));
+ (void)free((char *)rep);
+ *pt1 = *str;
+ tty_warn(1, "Invalid replacement string option %s",
+ str);
+ return -1;
+ }
+ ++pt2;
+ }
+
+ /*
+ * all done, link it in at the end
+ */
+ rep->fow = NULL;
+ if (rephead == NULL) {
+ reptail = rephead = rep;
+ return 0;
+ }
+ reptail->fow = rep;
+ reptail = rep;
+ return 0;
+}
+
+/*
+ * pat_add()
+ * add a pattern match to the pattern match list. Pattern matches are used
+ * to select which archive members are extracted. (They appear as
+ * arguments to pax in the list and read modes). If no patterns are
+ * supplied to pax, all members in the archive will be selected (and the
+ * pattern match list is empty).
+ *
+ * Return:
+ * 0 if the pattern was added to the list, -1 otherwise
+ */
+
+int
+pat_add(char *str, char *chdn, int flags)
+{
+ PATTERN *pt;
+
+ /*
+ * throw out the junk
+ */
+ if ((str == NULL) || (*str == '\0')) {
+ tty_warn(1, "Empty pattern string");
+ return -1;
+ }
+
+ /*
+ * allocate space for the pattern and store the pattern. the pattern is
+ * part of argv so do not bother to copy it, just point at it. Add the
+ * node to the end of the pattern list
+ */
+ if ((pt = (PATTERN *)malloc(sizeof(PATTERN))) == NULL) {
+ tty_warn(1, "Unable to allocate memory for pattern string");
+ return -1;
+ }
+
+ pt->pstr = str;
+ pt->pend = NULL;
+ pt->plen = strlen(str);
+ pt->fow = NULL;
+ pt->flgs = flags;
+ pt->chdname = chdn;
+ if (pathead == NULL) {
+ pattail = pathead = pt;
+ return 0;
+ }
+ pattail->fow = pt;
+ pattail = pt;
+ return 0;
+}
+
+/*
+ * pat_chk()
+ * complain if any the user supplied pattern did not result in a match to
+ * a selected archive member.
+ */
+
+void
+pat_chk(void)
+{
+ PATTERN *pt;
+ int wban = 0;
+
+ /*
+ * walk down the list checking the flags to make sure MTCH was set,
+ * if not complain
+ */
+ for (pt = pathead; pt != NULL; pt = pt->fow) {
+ if (pt->flgs & MTCH)
+ continue;
+ if (!wban) {
+ tty_warn(1, "WARNING! These patterns were not matched:");
+ ++wban;
+ }
+ (void)fprintf(stderr, "%s\n", pt->pstr);
+ }
+}
+
+/*
+ * pat_sel()
+ * the archive member which matches a pattern was selected. Mark the
+ * pattern as having selected an archive member. arcn->pat points at the
+ * pattern that was matched. arcn->pat is set in pat_match()
+ *
+ * NOTE: When the -c option is used, we are called when there was no match
+ * by pat_match() (that means we did match before the inverted sense of
+ * the logic). Now this seems really strange at first, but with -c we
+ * need to keep track of those patterns that cause an archive member to
+ * NOT be selected (it found an archive member with a specified pattern)
+ * Return:
+ * 0 if the pattern pointed at by arcn->pat was tagged as creating a
+ * match, -1 otherwise.
+ */
+
+int
+pat_sel(ARCHD *arcn)
+{
+ PATTERN *pt;
+ PATTERN **ppt;
+ int len;
+
+ /*
+ * if no patterns just return
+ */
+ if ((pathead == NULL) || ((pt = arcn->pat) == NULL))
+ return 0;
+
+ /*
+ * when we are NOT limited to a single match per pattern mark the
+ * pattern and return
+ */
+ if (!nflag) {
+ pt->flgs |= MTCH;
+ return 0;
+ }
+
+ /*
+ * we reach this point only when we allow a single selected match per
+ * pattern, if the pattern matches a directory and we do not have -d
+ * (dflag) we are done with this pattern. We may also be handed a file
+ * in the subtree of a directory. in that case when we are operating
+ * with -d, this pattern was already selected and we are done
+ */
+ if (pt->flgs & DIR_MTCH)
+ return 0;
+
+ if (!dflag && ((pt->pend != NULL) || (arcn->type == PAX_DIR))) {
+ /*
+ * ok we matched a directory and we are allowing
+ * subtree matches but because of the -n only its children will
+ * match. This is tagged as a DIR_MTCH type.
+ * WATCH IT, the code assumes that pt->pend points
+ * into arcn->name and arcn->name has not been modified.
+ * If not we will have a big mess. Yup this is another kludge
+ */
+
+ /*
+ * if this was a prefix match, remove trailing part of path
+ * so we can copy it. Future matches will be exact prefix match
+ */
+ if (pt->pend != NULL)
+ *pt->pend = '\0';
+
+ if ((pt->pstr = strdup(arcn->name)) == NULL) {
+ tty_warn(1, "Pattern select out of memory");
+ if (pt->pend != NULL)
+ *pt->pend = '/';
+ pt->pend = NULL;
+ return -1;
+ }
+
+ /*
+ * put the trailing / back in the source string
+ */
+ if (pt->pend != NULL) {
+ *pt->pend = '/';
+ pt->pend = NULL;
+ }
+ pt->plen = strlen(pt->pstr);
+
+ /*
+ * strip off any trailing /, this should really never happen
+ */
+ len = pt->plen - 1;
+ if (*(pt->pstr + len) == '/') {
+ *(pt->pstr + len) = '\0';
+ pt->plen = len;
+ }
+ pt->flgs = DIR_MTCH | MTCH;
+ arcn->pat = pt;
+ return 0;
+ }
+
+ /*
+ * we are then done with this pattern, so we delete it from the list
+ * because it can never be used for another match.
+ * Seems kind of strange to do for a -c, but the pax spec is really
+ * vague on the interaction of -c, -n, and -d. We assume that when -c
+ * and the pattern rejects a member (i.e. it matched it) it is done.
+ * In effect we place the order of the flags as having -c last.
+ */
+ pt = pathead;
+ ppt = &pathead;
+ while ((pt != NULL) && (pt != arcn->pat)) {
+ ppt = &(pt->fow);
+ pt = pt->fow;
+ }
+
+ if (pt == NULL) {
+ /*
+ * should never happen....
+ */
+ tty_warn(1, "Pattern list inconsistent");
+ return -1;
+ }
+ *ppt = pt->fow;
+ (void)free((char *)pt);
+ arcn->pat = NULL;
+ return 0;
+}
+
+/*
+ * pat_match()
+ * see if this archive member matches any supplied pattern, if a match
+ * is found, arcn->pat is set to point at the potential pattern. Later if
+ * this archive member is "selected" we process and mark the pattern as
+ * one which matched a selected archive member (see pat_sel())
+ * Return:
+ * 0 if this archive member should be processed, 1 if it should be
+ * skipped and -1 if we are done with all patterns (and pax should quit
+ * looking for more members)
+ */
+
+int
+pat_match(ARCHD *arcn)
+{
+ PATTERN *pt;
+
+ arcn->pat = NULL;
+
+ /*
+ * if there are no more patterns and we have -n (and not -c) we are
+ * done. otherwise with no patterns to match, matches all
+ */
+ if (pathead == NULL) {
+ if (nflag && !cflag)
+ return -1;
+ return 0;
+ }
+
+ /*
+ * have to search down the list one at a time looking for a match.
+ */
+ pt = pathead;
+ while (pt != NULL) {
+ /*
+ * check for a file name match unless we have DIR_MTCH set in
+ * this pattern then we want a prefix match
+ */
+ if (pt->flgs & DIR_MTCH) {
+ /*
+ * this pattern was matched before to a directory
+ * as we must have -n set for this (but not -d). We can
+ * only match CHILDREN of that directory so we must use
+ * an exact prefix match (no wildcards).
+ */
+ if ((arcn->name[pt->plen] == '/') &&
+ (strncmp(pt->pstr, arcn->name, pt->plen) == 0))
+ break;
+ } else if (fn_match(pt->pstr, arcn->name, &pt->pend,
+ pt->flgs & NOGLOB_MTCH) == 0)
+ break;
+ pt = pt->fow;
+ }
+
+ /*
+ * return the result, remember that cflag (-c) inverts the sense of a
+ * match
+ */
+ if (pt == NULL)
+ return cflag ? 0 : 1;
+
+ /*
+ * we had a match, now when we invert the sense (-c) we reject this
+ * member. However we have to tag the pattern a being successful, (in a
+ * match, not in selecting an archive member) so we call pat_sel()
+ * here.
+ */
+ arcn->pat = pt;
+ if (!cflag)
+ return 0;
+
+ if (pat_sel(arcn) < 0)
+ return -1;
+ arcn->pat = NULL;
+ return 1;
+}
+
+/*
+ * fn_match()
+ * Return:
+ * 0 if this archive member should be processed, 1 if it should be
+ * skipped and -1 if we are done with all patterns (and pax should quit
+ * looking for more members)
+ * Note: *pend may be changed to show where the prefix ends.
+ */
+
+static int
+fn_match(char *pattern, char *string, char **pend, int noglob)
+{
+ char c;
+ char test;
+
+ *pend = NULL;
+ for (;;) {
+ switch (c = *pattern++) {
+ case '\0':
+ /*
+ * Ok we found an exact match
+ */
+ if (*string == '\0')
+ return 0;
+
+ /*
+ * Check if it is a prefix match
+ */
+ if ((dflag == 1) || (*string != '/'))
+ return -1;
+
+ /*
+ * It is a prefix match, remember where the trailing
+ * / is located
+ */
+ *pend = string;
+ return 0;
+ case '?':
+ if (noglob)
+ goto regular;
+ if ((test = *string++) == '\0')
+ return (-1);
+ break;
+ case '*':
+ if (noglob)
+ goto regular;
+ c = *pattern;
+ /*
+ * Collapse multiple *'s.
+ */
+ while (c == '*')
+ c = *++pattern;
+
+ /*
+ * Optimized hack for pattern with a * at the end
+ */
+ if (c == '\0')
+ return (0);
+
+ /*
+ * General case, use recursion.
+ */
+ while ((test = *string) != '\0') {
+ if (!fn_match(pattern, string, pend, noglob))
+ return (0);
+ ++string;
+ }
+ return (-1);
+ case '[':
+ if (noglob)
+ goto regular;
+ /*
+ * range match
+ */
+ if (((test = *string++) == '\0') ||
+ ((pattern = range_match(pattern, test)) == NULL))
+ return (-1);
+ break;
+ case '\\':
+ default:
+ regular:
+ if (c != *string++)
+ return (-1);
+ break;
+ }
+ }
+ /* NOTREACHED */
+}
+
+static char *
+range_match(char *pattern, int test)
+{
+ char c;
+ char c2;
+ int negate;
+ int ok = 0;
+
+ if ((negate = (*pattern == '!')) != 0)
+ ++pattern;
+
+ while ((c = *pattern++) != ']') {
+ /*
+ * Illegal pattern
+ */
+ if (c == '\0')
+ return (NULL);
+
+ if ((*pattern == '-') && ((c2 = pattern[1]) != '\0') &&
+ (c2 != ']')) {
+ if ((c <= test) && (test <= c2))
+ ok = 1;
+ pattern += 2;
+ } else if (c == test)
+ ok = 1;
+ }
+ return (ok == negate ? NULL : pattern);
+}
+
+/*
+ * mod_name()
+ * modify a selected file name. first attempt to apply replacement string
+ * expressions, then apply interactive file rename. We apply replacement
+ * string expressions to both filenames and file links (if we didn't the
+ * links would point to the wrong place, and we could never be able to
+ * move an archive that has a file link in it). When we rename files
+ * interactively, we store that mapping (old name to user input name) so
+ * if we spot any file links to the old file name in the future, we will
+ * know exactly how to fix the file link.
+ * Return:
+ * 0 continue to process file, 1 skip this file, -1 pax is finished
+ */
+
+int
+mod_name(ARCHD *arcn, int flags)
+{
+ int res = 0;
+
+ if (secure) {
+ if (checkdotdot(arcn->name)) {
+ tty_warn(0, "Ignoring file containing `..' (%s)",
+ arcn->name);
+ return 1;
+ }
+#ifdef notdef
+ if (checkdotdot(arcn->ln_name)) {
+ tty_warn(0, "Ignoring link containing `..' (%s)",
+ arcn->ln_name);
+ return 1;
+ }
+#endif
+ }
+
+ /*
+ * IMPORTANT: We have a problem. what do we do with symlinks?
+ * Modifying a hard link name makes sense, as we know the file it
+ * points at should have been seen already in the archive (and if it
+ * wasn't seen because of a read error or a bad archive, we lose
+ * anyway). But there are no such requirements for symlinks. On one
+ * hand the symlink that refers to a file in the archive will have to
+ * be modified to so it will still work at its new location in the
+ * file system. On the other hand a symlink that points elsewhere (and
+ * should continue to do so) should not be modified. There is clearly
+ * no perfect solution here. So we handle them like hardlinks. Clearly
+ * a replacement made by the interactive rename mapping is very likely
+ * to be correct since it applies to a single file and is an exact
+ * match. The regular expression replacements are a little harder to
+ * justify though. We claim that the symlink name is only likely
+ * to be replaced when it points within the file tree being moved and
+ * in that case it should be modified. what we really need to do is to
+ * call an oracle here. :)
+ */
+ if (rephead != NULL) {
+ flags |= (flags & RENM) ? PRNT : 0;
+ /*
+ * we have replacement strings, modify the name and the link
+ * name if any.
+ */
+ if ((res = rep_name(arcn->name, sizeof(arcn->name),
+ &(arcn->nlen), flags)) != 0)
+ return res;
+
+ if (((arcn->type == PAX_SLK) || (arcn->type == PAX_HLK) ||
+ (arcn->type == PAX_HRG)) &&
+ ((res = rep_name(arcn->ln_name,
+ sizeof(arcn->ln_name), &(arcn->ln_nlen),
+ flags | (arcn->type == PAX_SLK ? SYML : 0))) != 0))
+ return res;
+ }
+
+ if (iflag) {
+ /*
+ * perform interactive file rename, then map the link if any
+ */
+ if ((res = tty_rename(arcn)) != 0)
+ return res;
+ if ((arcn->type == PAX_SLK) || (arcn->type == PAX_HLK) ||
+ (arcn->type == PAX_HRG))
+ sub_name(arcn->ln_name, &(arcn->ln_nlen), sizeof(arcn->ln_name));
+ }
+
+ /*
+ * Strip off leading '/' if appropriate.
+ * Currently, this option is only set for the tar format.
+ */
+ if (rmleadslash && arcn->name[0] == '/') {
+ if (arcn->name[1] == '\0') {
+ arcn->name[0] = '.';
+ } else {
+ (void)memmove(arcn->name, &arcn->name[1],
+ strlen(arcn->name));
+ arcn->nlen--;
+ }
+ if (rmleadslash < 2) {
+ rmleadslash = 2;
+ tty_warn(0, "Removing leading / from absolute path names in the archive");
+ }
+ }
+ if (rmleadslash && arcn->ln_name[0] == '/' &&
+ (arcn->type == PAX_HLK || arcn->type == PAX_HRG)) {
+ if (arcn->ln_name[1] == '\0') {
+ arcn->ln_name[0] = '.';
+ } else {
+ (void)memmove(arcn->ln_name, &arcn->ln_name[1],
+ strlen(arcn->ln_name));
+ arcn->ln_nlen--;
+ }
+ if (rmleadslash < 2) {
+ rmleadslash = 2;
+ tty_warn(0, "Removing leading / from absolute path names in the archive");
+ }
+ }
+
+ return res;
+}
+
+/*
+ * tty_rename()
+ * Prompt the user for a replacement file name. A "." keeps the old name,
+ * a empty line skips the file, and an EOF on reading the tty, will cause
+ * pax to stop processing and exit. Otherwise the file name input, replaces
+ * the old one.
+ * Return:
+ * 0 process this file, 1 skip this file, -1 we need to exit pax
+ */
+
+static int
+tty_rename(ARCHD *arcn)
+{
+ char tmpname[PAXPATHLEN+2];
+ int res;
+
+ /*
+ * prompt user for the replacement name for a file, keep trying until
+ * we get some reasonable input. Archives may have more than one file
+ * on them with the same name (from updates etc). We print verbose info
+ * on the file so the user knows what is up.
+ */
+ tty_prnt("\nATTENTION: %s interactive file rename operation.\n", argv0);
+
+ for (;;) {
+ ls_tty(arcn);
+ tty_prnt("Input new name, or a \".\" to keep the old name, ");
+ tty_prnt("or a \"return\" to skip this file.\n");
+ tty_prnt("Input > ");
+ if (tty_read(tmpname, sizeof(tmpname)) < 0)
+ return -1;
+ if (strcmp(tmpname, "..") == 0) {
+ tty_prnt("Try again, illegal file name: ..\n");
+ continue;
+ }
+ if (strlen(tmpname) > PAXPATHLEN) {
+ tty_prnt("Try again, file name too long\n");
+ continue;
+ }
+ break;
+ }
+
+ /*
+ * empty file name, skips this file. a "." leaves it alone
+ */
+ if (tmpname[0] == '\0') {
+ tty_prnt("Skipping file.\n");
+ return 1;
+ }
+ if ((tmpname[0] == '.') && (tmpname[1] == '\0')) {
+ tty_prnt("Processing continues, name unchanged.\n");
+ return 0;
+ }
+
+ /*
+ * ok the name changed. We may run into links that point at this
+ * file later. we have to remember where the user sent the file
+ * in order to repair any links.
+ */
+ tty_prnt("Processing continues, name changed to: %s\n", tmpname);
+ res = add_name(arcn->name, arcn->nlen, tmpname);
+ arcn->nlen = strlcpy(arcn->name, tmpname, sizeof(arcn->name));
+ if (res < 0)
+ return -1;
+ return 0;
+}
+
+/*
+ * set_dest()
+ * fix up the file name and the link name (if any) so this file will land
+ * in the destination directory (used during copy() -rw).
+ * Return:
+ * 0 if ok, -1 if failure (name too long)
+ */
+
+int
+set_dest(ARCHD *arcn, char *dest_dir, int dir_len)
+{
+ if (fix_path(arcn->name, &(arcn->nlen), dest_dir, dir_len) < 0)
+ return -1;
+
+ /*
+ * It is really hard to deal with symlinks here, we cannot be sure
+ * if the name they point was moved (or will be moved). It is best to
+ * leave them alone.
+ */
+ if ((arcn->type != PAX_HLK) && (arcn->type != PAX_HRG))
+ return 0;
+
+ if (fix_path(arcn->ln_name, &(arcn->ln_nlen), dest_dir, dir_len) < 0)
+ return -1;
+ return 0;
+}
+
+/*
+ * fix_path
+ * concatenate dir_name and or_name and store the result in or_name (if
+ * it fits). This is one ugly function.
+ * Return:
+ * 0 if ok, -1 if the final name is too long
+ */
+
+static int
+fix_path( char *or_name, int *or_len, char *dir_name, int dir_len)
+{
+ char *src;
+ char *dest;
+ char *start;
+ int len;
+
+ /*
+ * we shift the or_name to the right enough to tack in the dir_name
+ * at the front. We make sure we have enough space for it all before
+ * we start. since dest always ends in a slash, we skip of or_name
+ * if it also starts with one.
+ */
+ start = or_name;
+ src = start + *or_len;
+ dest = src + dir_len;
+ if (*start == '/') {
+ ++start;
+ --dest;
+ }
+ if ((len = dest - or_name) > PAXPATHLEN) {
+ tty_warn(1, "File name %s/%s, too long", dir_name, start);
+ return -1;
+ }
+ *or_len = len;
+
+ /*
+ * enough space, shift
+ */
+ while (src >= start)
+ *dest-- = *src--;
+ src = dir_name + dir_len - 1;
+
+ /*
+ * splice in the destination directory name
+ */
+ while (src >= dir_name)
+ *dest-- = *src--;
+
+ *(or_name + len) = '\0';
+ return 0;
+}
+
+/*
+ * rep_name()
+ * walk down the list of replacement strings applying each one in order.
+ * when we find one with a successful substitution, we modify the name
+ * as specified. if required, we print the results. if the resulting name
+ * is empty, we will skip this archive member. We use the regexp(3)
+ * routines (regexp() ought to win a prize as having the most cryptic
+ * library function manual page).
+ * --Parameters--
+ * name is the file name we are going to apply the regular expressions to
+ * (and may be modified)
+ * namelen the size of the name buffer.
+ * nlen is the length of this name (and is modified to hold the length of
+ * the final string).
+ * prnt is a flag that says whether to print the final result.
+ * Return:
+ * 0 if substitution was successful, 1 if we are to skip the file (the name
+ * ended up empty)
+ */
+
+static int
+rep_name(char *name, size_t namelen, int *nlen, int flags)
+{
+ REPLACE *pt;
+ char *inpt;
+ char *outpt;
+ char *endpt;
+ char *rpt;
+ int found = 0;
+ int res;
+ regmatch_t pm[MAXSUBEXP];
+ char nname[PAXPATHLEN+1]; /* final result of all replacements */
+ char buf1[PAXPATHLEN+1]; /* where we work on the name */
+
+ /*
+ * copy the name into buf1, where we will work on it. We need to keep
+ * the orig string around so we can print out the result of the final
+ * replacement. We build up the final result in nname. inpt points at
+ * the string we apply the regular expression to. prnt is used to
+ * suppress printing when we handle replacements on the link field
+ * (the user already saw that substitution go by)
+ */
+ pt = rephead;
+ (void)strlcpy(buf1, name, sizeof(buf1));
+ inpt = buf1;
+ outpt = nname;
+ endpt = outpt + PAXPATHLEN;
+
+ /*
+ * try each replacement string in order
+ */
+ while (pt != NULL) {
+ do {
+ if ((flags & SYML) && (pt->flgs & SYML))
+ continue;
+ /*
+ * check for a successful substitution, if not go to
+ * the next pattern, or cleanup if we were global
+ */
+ if (regexec(&(pt->rcmp), inpt, MAXSUBEXP, pm, 0) != 0)
+ break;
+
+ /*
+ * ok we found one. We have three parts, the prefix
+ * which did not match, the section that did and the
+ * tail (that also did not match). Copy the prefix to
+ * the final output buffer (watching to make sure we
+ * do not create a string too long).
+ */
+ found = 1;
+ rpt = inpt + pm[0].rm_so;
+
+ while ((inpt < rpt) && (outpt < endpt))
+ *outpt++ = *inpt++;
+ if (outpt == endpt)
+ break;
+
+ /*
+ * for the second part (which matched the regular
+ * expression) apply the substitution using the
+ * replacement string and place it the prefix in the
+ * final output. If we have problems, skip it.
+ */
+ if ((res =
+ resub(&(pt->rcmp),pm,pt->nstr,inpt, outpt,endpt)
+ ) < 0) {
+ if (flags & PRNT)
+ tty_warn(1, "Replacement name error %s",
+ name);
+ return 1;
+ }
+ outpt += res;
+
+ /*
+ * we set up to look again starting at the first
+ * character in the tail (of the input string right
+ * after the last character matched by the regular
+ * expression (inpt always points at the first char in
+ * the string to process). If we are not doing a global
+ * substitution, we will use inpt to copy the tail to
+ * the final result. Make sure we do not overrun the
+ * output buffer
+ */
+ inpt += pm[0].rm_eo - pm[0].rm_so;
+
+ if ((outpt == endpt) || (*inpt == '\0'))
+ break;
+
+ /*
+ * if the user wants global we keep trying to
+ * substitute until it fails, then we are done.
+ */
+ } while (pt->flgs & GLOB);
+
+ if (found)
+ break;
+
+ /*
+ * a successful substitution did NOT occur, try the next one
+ */
+ pt = pt->fow;
+ }
+
+ if (found) {
+ /*
+ * we had a substitution, copy the last tail piece (if there is
+ * room) to the final result
+ */
+ while ((outpt < endpt) && (*inpt != '\0'))
+ *outpt++ = *inpt++;
+
+ *outpt = '\0';
+ if ((outpt == endpt) && (*inpt != '\0')) {
+ if (flags & PRNT)
+ tty_warn(1,"Replacement name too long %s >> %s",
+ name, nname);
+ return 1;
+ }
+
+ /*
+ * inform the user of the result if wanted
+ */
+ if ((flags & PRNT) && (pt->flgs & PRNT)) {
+ if (*nname == '\0')
+ (void)fprintf(stderr,"%s >> <empty string>\n",
+ name);
+ else
+ (void)fprintf(stderr,"%s >> %s\n", name, nname);
+ }
+
+ /*
+ * if empty inform the caller this file is to be skipped
+ * otherwise copy the new name over the orig name and return
+ */
+ if (*nname == '\0')
+ return 1;
+ if (flags & RENM)
+ *nlen = strlcpy(name, nname, namelen);
+ }
+ return 0;
+}
+
+
+/*
+ * checkdotdot()
+ * Return true if a component of the name contains a reference to ".."
+ */
+static int
+checkdotdot(const char *name)
+{
+ const char *p;
+ /* 1. "..{[/],}" */
+ if (name[0] == '.' && name[1] == '.' &&
+ (name[2] == '/' || name[2] == '\0'))
+ return 1;
+
+ /* 2. "*[/]..[/]*" */
+ if (strstr(name, "/../") != NULL)
+ return 1;
+
+ /* 3. "*[/].." */
+ for (p = name; *p; p++)
+ continue;
+ if (p - name < 3)
+ return 0;
+ if (p[-1] == '.' && p[-2] == '.' && p[-3] == '/')
+ return 1;
+
+ return 0;
+}
+
+
+/*
+ * resub()
+ * apply the replacement to the matched expression. expand out the old
+ * style ed(1) subexpression expansion.
+ * Return:
+ * -1 if error, or the number of characters added to the destination.
+ */
+
+static int
+resub(regex_t *rp, regmatch_t *pm, char *src, char *txt, char *dest,
+ char *destend)
+{
+ char *spt;
+ char *dpt;
+ char c;
+ regmatch_t *pmpt;
+ int len;
+ int subexcnt;
+
+ spt = src;
+ dpt = dest;
+ subexcnt = rp->re_nsub;
+ while ((dpt < destend) && ((c = *spt++) != '\0')) {
+ /*
+ * see if we just have an ordinary replacement character
+ * or we refer to a subexpression.
+ */
+ if (c == '&') {
+ pmpt = pm;
+ } else if ((c == '\\') && (*spt >= '1') && (*spt <= '9')) {
+ /*
+ * make sure there is a subexpression as specified
+ */
+ if ((len = *spt++ - '0') > subexcnt)
+ return -1;
+ pmpt = pm + len;
+ } else {
+ /*
+ * Ordinary character, just copy it
+ */
+ if ((c == '\\') && ((*spt == '\\') || (*spt == '&')))
+ c = *spt++;
+ *dpt++ = c;
+ continue;
+ }
+
+ /*
+ * continue if the subexpression is bogus
+ */
+ if ((pmpt->rm_so < 0) || (pmpt->rm_eo < 0) ||
+ ((len = pmpt->rm_eo - pmpt->rm_so) <= 0))
+ continue;
+
+ /*
+ * copy the subexpression to the destination.
+ * fail if we run out of space or the match string is damaged
+ */
+ if (len > (destend - dpt))
+ return -1;
+ strncpy(dpt, txt + pmpt->rm_so, len);
+ dpt += len;
+ }
+ return dpt - dest;
+}
diff --git a/bin/pax/pat_rep.h b/bin/pax/pat_rep.h
new file mode 100644
index 0000000..f2e0cbe
--- /dev/null
+++ b/bin/pax/pat_rep.h
@@ -0,0 +1,51 @@
+/* $NetBSD: pat_rep.h,v 1.7 2008/02/24 20:42:46 joerg Exp $ */
+
+/*-
+ * Copyright (c) 1992 Keith Muller.
+ * Copyright (c) 1992, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Keith Muller of the University of California, San Diego.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * @(#)pat_rep.h 8.1 (Berkeley) 5/31/93
+ */
+
+#include <regex.h>
+/*
+ * data structure for storing user supplied replacement strings (-s)
+ */
+typedef struct replace {
+ char *nstr; /* the new string we will substitute with */
+ regex_t rcmp; /* compiled regular expression used to match */
+ int flgs; /* print conversions? global in operation? */
+#define PRNT 0x1
+#define GLOB 0x2
+#define RENM 0x4
+#define SYML 0x8
+ struct replace *fow; /* pointer to next pattern */
+} REPLACE;
diff --git a/bin/pax/pax.1 b/bin/pax/pax.1
new file mode 100644
index 0000000..8203a94
--- /dev/null
+++ b/bin/pax/pax.1
@@ -0,0 +1,1304 @@
+.\" $NetBSD: pax.1,v 1.69 2017/07/03 21:33:23 wiz Exp $
+.\"
+.\" Copyright (c) 1992 Keith Muller.
+.\" Copyright (c) 1992, 1993
+.\" The Regents of the University of California. All rights reserved.
+.\"
+.\" This code is derived from software contributed to Berkeley by
+.\" Keith Muller of the University of California, San Diego.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\" notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must reproduce the above copyright
+.\" notice, this list of conditions and the following disclaimer in the
+.\" documentation and/or other materials provided with the distribution.
+.\" 3. Neither the name of the University nor the names of its contributors
+.\" may be used to endorse or promote products derived from this software
+.\" without specific prior written permission.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+.\" ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+.\" SUCH DAMAGE.
+.\"
+.\" @(#)pax.1 8.4 (Berkeley) 4/18/94
+.\"
+.Dd August 12, 2016
+.Dt PAX 1
+.Os
+.Sh NAME
+.Nm pax
+.Nd read and write file archives and copy directory hierarchies
+.Sh SYNOPSIS
+.Nm
+.Op Fl 0cdjnOVvz
+.Op Fl E Ar limit
+.Op Fl f Ar archive
+.Op Fl N Ar dbdir
+.Op Fl s Ar replstr
+.Ar ...\&
+.Op Fl U Ar user
+.Ar ...\&
+.Op Fl G Ar group
+.Ar ...\&
+.Oo
+.Fl T
+.Sm off
+.Op Ar from_date
+.Oo , Ar to_date Oc
+.Sm on
+.Oc
+.Ar ...\&
+.Op Ar pattern ...\&
+.Nm
+.Fl r
+.Op Fl AcDdijknOuVvYZz
+.Op Fl E Ar limit
+.Op Fl f Ar archive
+.Op Fl N Ar dbdir
+.Op Fl o Ar options
+.Ar ...\&
+.Op Fl p Ar string
+.Ar ...\&
+.Op Fl s Ar replstr
+.Ar ...\&
+.Op Fl U Ar user
+.Ar ...\&
+.Op Fl G Ar group
+.Ar ...\&
+.Oo
+.Fl T
+.Sm off
+.Op Ar from_date
+.Oo , Ar to_date Oc
+.Sm on
+.Oc
+.Ar ...\&
+.Op Ar pattern ...\&
+.Nm
+.Fl w
+.Op Fl AdHijLMOPtuVvXz
+.Op Fl b Ar blocksize
+.Oo
+.Op Fl a
+.Op Fl f Ar archive
+.Oc
+.Op Fl x Ar format
+.Op Fl B Ar bytes
+.Op Fl N Ar dbdir
+.Op Fl o Ar options
+.Ar ...\&
+.Op Fl s Ar replstr
+.Ar ...\&
+.Op Fl U Ar user
+.Ar ...\&
+.Op Fl G Ar group
+.Ar ...\&
+.Oo
+.Fl T
+.Sm off
+.Op Ar from_date
+.Oo , Ar to_date Oc
+.Oo /[ Cm c ] [ Cm m ] Oc
+.Sm on
+.Oc
+.Ar ...\&
+.Op Ar file ...\&
+.Nm
+.Fl r
+.Fl w
+.Op Fl ADdHijkLlMnOPtuVvXYZz
+.Op Fl N Ar dbdir
+.Op Fl p Ar string
+.Ar ...\&
+.Op Fl s Ar replstr
+.Ar ...\&
+.Op Fl U Ar user
+.Ar ...\&
+.Op Fl G Ar group
+.Ar ...\&
+.Oo
+.Fl T
+.Sm off
+.Op Ar from_date
+.Oo , Ar to_date Oc
+.Oo /[ Cm c ] [ Cm m ] Oc
+.Sm on
+.Oc
+.Ar ...\&
+.Op Ar file ...\&
+.Ar directory
+.Sh DESCRIPTION
+.Nm
+will read, write, and list the members of an archive file,
+and will copy directory hierarchies.
+If the archive file is of the form:
+.Ar [[user@]host:]file
+then the archive will be processed using
+.Xr rmt 8 .
+.Pp
+.Nm
+operation is independent of the specific archive format,
+and supports a wide variety of different archive formats.
+A list of supported archive formats can be found under the description of the
+.Fl x
+option.
+.Pp
+The presence of the
+.Fl r
+and the
+.Fl w
+options specifies which of the following functional modes
+.Nm
+will operate under:
+.Em list , read , write ,
+and
+.Em copy .
+.Bl -tag -width 6n
+.It Aq none
+.Em List .
+.Nm
+will write to
+.Dv standard output
+a table of contents of the members of the archive file read from
+.Dv standard input ,
+whose pathnames match the specified
+.Ar patterns .
+The table of contents contains one filename per line
+and is written using single line buffering.
+.It Fl r
+.Em Read .
+.Nm
+extracts the members of the archive file read from the
+.Dv standard input ,
+with pathnames matching the specified
+.Ar patterns .
+The archive format and blocking is automatically determined on input.
+When an extracted file is a directory, the entire file hierarchy
+rooted at that directory is extracted.
+All extracted files are created relative to the current file hierarchy.
+The setting of ownership, access and modification times, and file mode of
+the extracted files are discussed in more detail under the
+.Fl p
+option.
+.It Fl w
+.Em Write .
+.Nm
+writes an archive containing the
+.Ar file
+operands to
+.Dv standard output
+using the specified archive format.
+When no
+.Ar file
+operands are specified, a list of files to copy with one per line is read from
+.Dv standard input .
+When a
+.Ar file
+operand is also a directory, the entire file hierarchy rooted
+at that directory will be included.
+.It Fl r Fl w
+.Em Copy .
+.Nm
+copies the
+.Ar file
+operands to the destination
+.Ar directory .
+When no
+.Ar file
+operands are specified, a list of files to copy with one per line is read from
+the
+.Dv standard input .
+When a
+.Ar file
+operand is also a directory the entire file
+hierarchy rooted at that directory will be included.
+The effect of the
+.Em copy
+is as if the copied files were written to an archive file and then
+subsequently extracted, except that there may be hard links between
+the original and the copied files (see the
+.Fl l
+option below).
+.Pp
+.Em Warning :
+The destination
+.Ar directory
+must not be one of the
+.Ar file
+operands or a member of a file hierarchy rooted at one of the
+.Ar file
+operands.
+The result of a
+.Em copy
+under these conditions is unpredictable.
+.El
+.Pp
+While processing a damaged archive during a
+.Em read
+or
+.Em list
+operation,
+.Nm
+will attempt to recover from media defects and will search through the archive
+to locate and process the largest number of archive members possible (see the
+.Fl E
+option for more details on error handling).
+.Sh OPERANDS
+The
+.Ar directory
+operand specifies a destination directory pathname.
+If the
+.Ar directory
+operand does not exist, or it is not writable by the user,
+or it is not of type directory,
+.Nm
+will exit with a non-zero exit status.
+.Pp
+The
+.Ar pattern
+operand is used to select one or more pathnames of archive members.
+Archive members are selected using the pattern matching notation described
+by
+.Xr fnmatch 3 .
+When the
+.Ar pattern
+operand is not supplied, all members of the archive will be selected.
+When a
+.Ar pattern
+matches a directory, the entire file hierarchy rooted at that directory will
+be selected.
+When a
+.Ar pattern
+operand does not select at least one archive member,
+.Nm
+will write these
+.Ar pattern
+operands in a diagnostic message to
+.Dv standard error
+and then exit with a non-zero exit status.
+.Pp
+The
+.Ar file
+operand specifies the pathname of a file to be copied or archived.
+When a
+.Ar file
+operand does not select at least one archive member,
+.Nm
+will write these
+.Ar file
+operand pathnames in a diagnostic message to
+.Dv standard error
+and then exit with a non-zero exit status.
+.Sh OPTIONS
+The following options are supported:
+.Bl -tag -width 4n
+.It Fl r
+Read an archive file from
+.Dv standard input
+and extract the specified
+.Ar files .
+If any intermediate directories are needed in order to extract an archive
+member, these directories will be created as if
+.Xr mkdir 2
+was called with the bitwise inclusive
+.Dv OR
+of
+.Dv S_IRWXU , S_IRWXG ,
+and
+.Dv S_IRWXO
+as the mode argument.
+When the selected archive format supports the specification of linked
+files and these files cannot be linked while the archive is being extracted,
+.Nm
+will write a diagnostic message to
+.Dv standard error
+and exit with a non-zero exit status at the completion of operation.
+.It Fl w
+Write files to the
+.Dv standard output
+in the specified archive format.
+When no
+.Ar file
+operands are specified,
+.Dv standard input
+is read for a list of pathnames with one per line without any leading or
+trailing
+.Aq blanks .
+.It Fl a
+Append
+.Ar files
+to the end of an archive that was previously written.
+If an archive format is not specified with a
+.Fl x
+option, the format currently being used in the archive will be selected.
+Any attempt to append to an archive in a format different from the
+format already used in the archive will cause
+.Nm
+to exit immediately
+with a non-zero exit status.
+The blocking size used in the archive volume where writing starts
+will continue to be used for the remainder of that archive volume.
+.Pp
+.Em Warning :
+Many storage devices are not able to support the operations necessary
+to perform an append operation.
+Any attempt to append to an archive stored on such a device may damage the
+archive or have other unpredictable results.
+Tape drives in particular are more likely to not support an append operation.
+An archive stored in a regular file system file or on a disk device will
+usually support an append operation.
+.It Fl b Ar blocksize
+When
+.Em writing
+an archive,
+block the output at a positive decimal integer number of
+bytes per write to the archive file.
+The
+.Ar blocksize
+must be a multiple of 512 bytes with a maximum of 32256 bytes.
+A
+.Ar blocksize
+can end with
+.Li k
+or
+.Li b
+to specify multiplication by 1024 (1K) or 512, respectively.
+A pair of
+.Ar blocksizes
+can be separated by
+.Li x
+to indicate a product.
+A specific archive device may impose additional restrictions on the size
+of blocking it will support.
+When blocking is not specified, the default
+.Ar blocksize
+is dependent on the specific archive format being used (see the
+.Fl x
+option).
+.It Fl c
+Match all file or archive members
+.Em except
+those specified by the
+.Ar pattern
+and
+.Ar file
+operands.
+.It Fl d
+Cause files of type directory being copied or archived, or archive members of
+type directory being extracted, to match only the directory file or archive
+member and not the file hierarchy rooted at the directory.
+.It Fl f Ar archive
+Specify
+.Ar archive
+as the pathname of the input or output archive, overriding the default
+.Dv standard input
+(for
+.Em list
+and
+.Em read )
+or
+.Dv standard output
+(for
+.Em write ) .
+A single archive may span multiple files and different archive devices.
+When required,
+.Nm
+will prompt for the pathname of the file or device of the next volume in the
+archive.
+.It Fl i
+Interactively rename files or archive members.
+For each archive member matching a
+.Ar pattern
+operand or each file matching a
+.Ar file
+operand,
+.Nm
+will prompt to
+.Pa /dev/tty
+giving the name of the file, its file mode and its modification time.
+.Nm
+will then read a line from
+.Pa /dev/tty .
+If this line is blank, the file or archive member is skipped.
+If this line consists of a single period, the
+file or archive member is processed with no modification to its name.
+Otherwise, its name is replaced with the contents of the line.
+.Nm
+will immediately exit with a non-zero exit status if
+.Aq Dv EOF
+is encountered when reading a response or if
+.Pa /dev/tty
+cannot be opened for reading and writing.
+.It Fl j
+Use
+.Xr bzip2 1
+for compression when reading or writing archive files.
+.It Fl k
+Do not overwrite existing files.
+.It Fl l
+Link files.
+(The letter ell).
+In the
+.Em copy
+mode
+.Fl ( r
+.Fl w ) ,
+hard links are made between the source and destination file hierarchies
+whenever possible.
+.It Fl n
+Select the first archive member that matches each
+.Ar pattern
+operand.
+No more than one archive member is matched for each
+.Ar pattern .
+When members of type directory are matched, the file hierarchy rooted at that
+directory is also matched (unless
+.Fl d
+is also specified).
+.It Fl o Ar options
+Information to modify the algorithm for extracting or writing archive files
+which is specific to the archive format specified by
+.Fl x .
+In general,
+.Ar options
+take the form:
+.Cm name=value
+.It Fl p Ar string
+Specify one or more file characteristic options (privileges).
+The
+.Ar string
+option-argument is a string specifying file characteristics to be retained or
+discarded on extraction.
+The string consists of the specification characters
+.Cm a , e ,
+.Cm m , o ,
+and
+.Cm p .
+Multiple characteristics can be concatenated within the same string
+and multiple
+.Fl p
+options can be specified.
+The meaning of the specification characters are as follows:
+.Bl -tag -width 2n
+.It Cm a
+Do not preserve file access times.
+By default, file access times are preserved whenever possible.
+.It Cm e
+.Sq Preserve everything ,
+the user ID, group ID, file mode bits,
+file access time, and file modification time.
+This is intended to be used by
+.Em root ,
+someone with all the appropriate privileges, in order to preserve all
+aspects of the files as they are recorded in the archive.
+The
+.Cm e
+flag is the sum of the
+.Cm o
+and
+.Cm p
+flags.
+.\" .It Cm f
+.\" Do not preserve file flags.
+.\" By default, file flags are preserved whenever possible.
+.It Cm m
+Do not preserve file modification times.
+By default, file modification times are preserved whenever possible.
+.It Cm o
+Preserve the user ID and group ID.
+.It Cm p
+.Sq Preserve
+the file mode bits.
+This is intended to be used by a
+.Em user
+with regular privileges who wants to preserve all aspects of the file other
+than the ownership.
+The file times are preserved by default, but two other flags are offered to
+disable this and use the time of extraction instead.
+.El
+.Pp
+In the preceding list,
+.Sq preserve
+indicates that an attribute stored in the archive is given to the
+extracted file, subject to the permissions of the invoking
+process.
+Otherwise the attribute of the extracted file is determined as
+part of the normal file creation action.
+If neither the
+.Cm e
+nor the
+.Cm o
+specification character is specified, or the user ID and group ID are not
+preserved for any reason,
+.Nm
+will not set the
+.Dv S_ISUID
+.Em ( setuid )
+and
+.Dv S_ISGID
+.Em ( setgid )
+bits of the file mode.
+If the preservation of any of these items fails for any reason,
+.Nm
+will write a diagnostic message to
+.Dv standard error .
+Failure to preserve these items will affect the final exit status,
+but will not cause the extracted file to be deleted.
+If the file characteristic letters in any of the string option-arguments are
+duplicated or conflict with each other, the one(s) given last will take
+precedence.
+For example, if
+.Dl Fl p Ar eme
+is specified, file modification times are still preserved.
+.It Fl s Ar replstr
+Modify the file or archive member names specified by the
+.Ar pattern
+or
+.Ar file
+operands according to the substitution expression
+.Ar replstr ,
+using the syntax of the
+.Xr ed 1
+utility regular expressions.
+The format of these regular expressions are:
+.Dl /old/new/[gp]
+As in
+.Xr ed 1 ,
+.Cm old
+is a basic regular expression and
+.Cm new
+can contain an ampersand (&), \en (where n is a digit) back-references,
+or subexpression matching.
+The
+.Cm old
+string may also contain
+.Aq Dv newline
+characters.
+Any non-null character can be used as a delimiter (/ is shown here).
+Multiple
+.Fl s
+expressions can be specified.
+The expressions are applied in the order they are specified on the
+command line, terminating with the first successful substitution.
+The optional trailing
+.Cm g
+continues to apply the substitution expression to the pathname substring
+which starts with the first character following the end of the last successful
+substitution.
+The first unsuccessful substitution stops the operation of the
+.Cm g
+option.
+The optional trailing
+.Cm p
+will cause the final result of a successful substitution to be written to
+.Dv standard error
+in the following format:
+.Dl Ao "original pathname" Ac >> Ao "new pathname" Ac
+File or archive member names that substitute to the empty string
+are not selected and will be skipped.
+.It Fl t
+Reset the access times of any file or directory read or accessed by
+.Nm
+to be the same as they were before being read or accessed by
+.Nm ,
+if the user has the appropriate permissions required by
+.Xr utime 3 .
+.It Fl u
+Ignore files that are older (having a less recent file modification time)
+than a pre-existing file or archive member with the same name.
+During
+.Em read ,
+an archive member with the same name as a file in the file system will be
+extracted if the archive member is newer than the file.
+During
+.Em write ,
+a file system member with the same name as an archive member will be
+written to the archive if it is newer than the archive member.
+During
+.Em copy ,
+the file in the destination hierarchy is replaced by the file in the source
+hierarchy or by a link to the file in the source hierarchy if the file in
+the source hierarchy is newer.
+.It Fl v
+During a
+.Em list
+operation, produce a verbose table of contents using the format of the
+.Xr ls 1
+utility with the
+.Fl l
+option.
+For pathnames representing a hard link to a previous member of the archive,
+the output has the format:
+.Dl Ao "ls -l listing" Ac == Ao "link name" Ac
+Where
+.Aq "ls -l listing"
+is the output format specified by the
+.Xr ls 1
+utility when used with the
+.Fl l
+option.
+.Pp
+Otherwise for all the other operational modes
+.Em ( read , write ,
+and
+.Em copy ) ,
+pathnames are written and flushed to
+.Dv standard error
+without a trailing
+.Aq Dv newline
+as soon as processing begins on that file or
+archive member.
+The trailing
+.Aq Dv newline ,
+is not buffered, and is written only after the file has been read or written.
+.Pp
+A final summary of archive operations is printed after they have been
+completed.
+.It Fl x Ar format
+Specify the output archive format, with the default format being
+.Ar ustar .
+.Nm
+currently supports the following formats:
+.Bl -tag -width "sv4cpio"
+.It Ar cpio
+The extended cpio interchange format specified in the
+.St -p1003.2
+standard.
+The default blocksize for this format is 5120 bytes.
+Inode and device information about a file (used for detecting file hard links
+by this format) which may be truncated by this format is detected by
+.Nm
+and is repaired.
+.It Ar bcpio
+The old binary cpio format.
+The default blocksize for this format is 5120 bytes.
+This format is not very portable and should not be used when other formats
+are available.
+Inode and device information about a file (used for detecting file hard links
+by this format) which may be truncated by this format is detected by
+.Nm
+and is repaired.
+.It Ar sv4cpio
+The
+.At V.4
+cpio.
+The default blocksize for this format is 5120 bytes.
+Inode and device information about a file (used for detecting file hard links
+by this format) which may be truncated by this format is detected by
+.Nm
+and is repaired.
+.It Ar sv4crc
+The
+.At V.4
+cpio with file crc checksums.
+The default blocksize for this format is 5120 bytes.
+Inode and device information about a file (used for detecting file hard links
+by this format) which may be truncated by this format is detected by
+.Nm
+and is repaired.
+.It Ar tar
+The old
+.Bx
+tar format as found in
+.Bx 4.3 .
+The default blocksize for this format is 10240 bytes.
+Pathnames stored by this format must be 100 characters or less in length.
+Only
+.Em regular
+files,
+.Em hard links , soft links ,
+and
+.Em directories
+will be archived (other file types are not supported).
+For backward compatibility with even older tar formats, a
+.Fl o
+option can be used when writing an archive to omit the storage of directories.
+This option takes the form:
+.Dl Fl o Cm write_opt=nodir
+.It Ar ustar
+The extended tar interchange format specified in the
+.St -p1003.2
+standard.
+The default blocksize for this format is 10240 bytes.
+Pathnames stored by this format must be 250 characters or less in length.
+.El
+.Pp
+.Nm
+will detect and report any file that it is unable to store or extract
+as the result of any specific archive format restrictions.
+The individual archive formats may impose additional restrictions on use.
+Typical archive format restrictions include (but are not limited to):
+file pathname length, file size, link pathname length and the type of the file.
+.It Fl Fl gnu
+Recognize GNU tar extensions.
+.It Fl Fl timestamp Ar timestamp
+Store all modification times in the archive with the
+.Ar timestamp
+given instead of the actual modification time of the individual archive member
+so that repeatable builds are possible.
+The
+.Ar timestamp
+can be a
+.Pa pathname ,
+where the timestamps are derived from that file, a parseable date for
+.Xr parsedate 3
+(this option is not yet available in the tools build), or an integer value
+interpreted as the number of seconds from the Epoch.
+.It Fl Fl xz
+Use
+.Xr xz 1
+compression, when reading or writing archive files.
+.It Fl z
+Use
+.Xr gzip 1
+compression, when reading or writing archive files.
+.It Fl A
+Do not strip leading `/'s from file names.
+.It Fl B Ar bytes
+Limit the number of bytes written to a single archive volume to
+.Ar bytes .
+The
+.Ar bytes
+limit can end with
+.Li m ,
+.Li k ,
+or
+.Li b
+to specify multiplication by 1048576 (1M), 1024 (1K) or 512, respectively.
+A pair of
+.Ar bytes
+limits can be separated by
+.Li x
+to indicate a product.
+.Pp
+.Em Warning :
+Only use this option when writing an archive to a device which supports
+an end of file read condition based on last (or largest) write offset
+(such as a regular file or a tape drive).
+The use of this option with a floppy or hard disk is not recommended.
+.It Fl D
+This option is the same as the
+.Fl u
+option, except that the file inode change time is checked instead of the
+file modification time.
+The file inode change time can be used to select files whose inode information
+(e.g. uid, gid, etc.) is newer than a copy of the file in the destination
+.Ar directory .
+.It Fl E Ar limit
+Limit the number of consecutive read faults while trying to read a flawed
+archives to
+.Ar limit .
+With a positive
+.Ar limit ,
+.Nm
+will attempt to recover from an archive read error and will
+continue processing starting with the next file stored in the archive.
+A
+.Ar limit
+of 0 will cause
+.Nm
+to stop operation after the first read error is detected on an archive volume.
+A
+.Ar limit
+of
+.Li NONE
+will cause
+.Nm
+to attempt to recover from read errors forever.
+The default
+.Ar limit
+is a small positive number of retries.
+.Pp
+.Em Warning :
+Using this option with
+.Li NONE
+should be used with extreme caution as
+.Nm
+may get stuck in an infinite loop on a very badly flawed archive.
+.It Fl G Ar group
+Select a file based on its
+.Ar group
+name, or when starting with a
+.Cm # ,
+a numeric gid.
+A '\e' can be used to escape the
+.Cm # .
+Multiple
+.Fl G
+options may be supplied and checking stops with the first match.
+.It Fl H
+Follow only command line symbolic links while performing a physical file
+system traversal.
+.It Fl L
+Follow all symbolic links to perform a logical file system traversal.
+.It Fl M
+During a
+.Em write
+or
+.Em copy
+operation, treat the list of files on
+.Dv standard input
+as an
+.Xr mtree 8
+.Sq specfile
+specification, and write or copy only those items in the specfile.
+.Pp
+If the file exists in the underlying file system, its permissions and
+modification time will be used unless specifically overridden by the specfile.
+An error will be raised if the type of entry in the specfile conflicts
+with that of an existing file.
+A directory entry that is marked
+.Sq Sy optional
+will not be copied (even though its contents will be).
+.Pp
+Otherwise, the entry will be
+.Sq faked-up ,
+and it is necessary to specify at least the following parameters
+in the specfile:
+.Sy type ,
+.Sy mode ,
+.Sy gname
+or
+.Sy gid ,
+and
+.Sy uname
+or
+.Sy uid ,
+.Sy device
+(in the case of block or character devices), and
+.Sy link
+(in the case of symbolic links).
+If
+.Sy time
+isn't provided, the current time will be used.
+A
+.Sq faked-up
+entry that is marked
+.Sq Sy optional
+will not be copied.
+.It Fl N Ar dbdir
+Except for lookups for the
+.Fl G
+and
+.Fl U
+options,
+use the user database text file
+.Pa master.passwd
+and group database text file
+.Pa group
+from
+.Ar dbdir ,
+rather than using the results from the system's
+.Xr getpwnam 3
+and
+.Xr getgrnam 3
+(and related) library calls.
+.It Fl O
+Force the archive to be one volume.
+If a volume ends prematurely,
+.Nm
+will not prompt for a new volume.
+This option can be useful for
+automated tasks where error recovery cannot be performed by a human.
+.It Fl P
+Do not follow symbolic links, perform a physical file system traversal.
+This is the default mode.
+.It Fl T Ar [from_date][,to_date][/[c][m]]
+Allow files to be selected based on a file modification or inode change
+time falling within a specified time range of
+.Ar from_date
+to
+.Ar to_date
+(the dates are inclusive).
+If only a
+.Ar from_date
+is supplied, all files with a modification or inode change time
+equal to or younger are selected.
+If only a
+.Ar to_date
+is supplied, all files with a modification or inode change time
+equal to or older will be selected.
+When the
+.Ar from_date
+is equal to the
+.Ar to_date ,
+only files with a modification or inode change time of exactly that
+time will be selected.
+.Pp
+When
+.Nm
+is in the
+.Em write
+or
+.Em copy
+mode, the optional trailing field
+.Ar [c][m]
+can be used to determine which file time (inode change, file modification or
+both) are used in the comparison.
+If neither is specified, the default is to use file modification time only.
+The
+.Ar m
+specifies the comparison of file modification time (the time when
+the file was last written).
+The
+.Ar c
+specifies the comparison of inode change time (the time when the file
+inode was last changed; e.g. a change of owner, group, mode, etc).
+When
+.Ar c
+and
+.Ar m
+are both specified, then the modification and inode change times are
+both compared.
+The inode change time comparison is useful in selecting files whose
+attributes were recently changed or selecting files which were recently
+created and had their modification time reset to an older time (as what
+happens when a file is extracted from an archive and the modification time
+is preserved).
+Time comparisons using both file times is useful when
+.Nm
+is used to create a time based incremental archive (only files that were
+changed during a specified time range will be archived).
+.Pp
+A time range is made up of seven different fields and each field must contain
+two digits.
+The format is:
+.Dl [[[[[cc]yy]mm]dd]hh]mm[\&.ss]
+where
+.Cm cc
+is the first two digits of the year (the century),
+.Cm yy
+is the last two digits of the year,
+the first
+.Cm mm
+is the month (from 01 to 12),
+.Cm dd
+is the day of the month (from 01 to 31),
+.Cm hh
+is the hour of the day (from 00 to 23),
+the second
+.Cm mm
+is the minute (from 00 to 59),
+and
+.Cm ss
+is the seconds (from 00 to 61).
+Only the minute field
+.Cm mm
+is required; the others will default to the current system values.
+The
+.Cm ss
+field may be added independently of the other fields.
+If the century is not specified, it defaults to 1900 for
+years between 69 and 99, or 2000 for years between 0 and 68.
+Time ranges are relative to the current time, so
+.Dl Fl T Ar 1234/cm
+would select all files with a modification or inode change time
+of 12:34 PM today or later.
+Multiple
+.Fl T
+time range can be supplied and checking stops with the first match.
+.It Fl U Ar user
+Select a file based on its
+.Ar user
+name, or when starting with a
+.Cm # ,
+a numeric uid.
+A '\e' can be used to escape the
+.Cm # .
+Multiple
+.Fl U
+options may be supplied and checking stops with the first match.
+.It Fl V
+A final summary of archive operations is printed after they have been
+completed.
+Some potentially long-running tape operations are noted.
+.It Fl X
+When traversing the file hierarchy specified by a pathname,
+do not descend into directories that have a different device ID.
+See the
+.Li st_dev
+field as described in
+.Xr stat 2
+for more information about device ID's.
+.It Fl Y
+This option is the same as the
+.Fl D
+option, except that the inode change time is checked using the
+pathname created after all the file name modifications have completed.
+.It Fl Z
+This option is the same as the
+.Fl u
+option, except that the modification time is checked using the
+pathname created after all the file name modifications have completed.
+.It Fl 0
+Use the nul character instead of \en as the file separator when reading
+files from standard input.
+.It Fl Fl force-local
+Do not interpret filenames that contain a `:' as remote files.
+.It Fl Fl insecure
+Normally
+.Nm
+ignores filenames that contain
+.Dq ..
+as a path component.
+With this option,
+files that contain
+.Dq ..
+can be processed.
+.It Fl Fl use-compress-program
+Use the named program as the program to decompress the input or compress
+the output.
+.El
+.Pp
+The options that operate on the names of files or archive members
+.Fl ( c ,
+.Fl i ,
+.Fl n ,
+.Fl s ,
+.Fl u ,
+.Fl v ,
+.Fl D ,
+.Fl G ,
+.Fl T ,
+.Fl U ,
+.Fl Y ,
+and
+.Fl Z )
+interact as follows.
+.Pp
+When extracting files during a
+.Em read
+operation, archive members are
+.Sq selected ,
+based only on the user specified pattern operands as modified by the
+.Fl c ,
+.Fl n ,
+.Fl u ,
+.Fl D ,
+.Fl G ,
+.Fl T ,
+.Fl U
+options.
+Then any
+.Fl s
+and
+.Fl i
+options will modify in that order, the names of these selected files.
+Then the
+.Fl Y
+and
+.Fl Z
+options will be applied based on the final pathname.
+Finally the
+.Fl v
+option will write the names resulting from these modifications.
+.Pp
+When archiving files during a
+.Em write
+operation, or copying files during a
+.Em copy
+operation, archive members are
+.Sq selected ,
+based only on the user specified pathnames as modified by the
+.Fl n ,
+.Fl u ,
+.Fl D ,
+.Fl G ,
+.Fl T ,
+and
+.Fl U
+options (the
+.Fl D
+option only applies during a copy operation).
+Then any
+.Fl s
+and
+.Fl i
+options will modify in that order, the names of these selected files.
+Then during a
+.Em copy
+operation the
+.Fl Y
+and the
+.Fl Z
+options will be applied based on the final pathname.
+Finally the
+.Fl v
+option will write the names resulting from these modifications.
+.Pp
+When one or both of the
+.Fl u
+or
+.Fl D
+options are specified along with the
+.Fl n
+option, a file is not considered selected unless it is newer
+than the file to which it is compared.
+.Sh EXIT STATUS
+.Nm
+will exit with one of the following values:
+.Bl -tag -width 2n
+.It 0
+All files were processed successfully.
+.It 1
+An error occurred.
+.El
+.Pp
+Whenever
+.Nm
+cannot create a file or a link when reading an archive or cannot
+find a file when writing an archive, or cannot preserve the user ID,
+group ID, or file mode when the
+.Fl p
+option is specified, a diagnostic message is written to
+.Dv standard error
+and a non-zero exit status will be returned, but processing will continue.
+In the case where pax cannot create a link to a file,
+.Nm
+will not create a second copy of the file.
+.Pp
+If the extraction of a file from an archive is prematurely terminated by
+a signal or error,
+.Nm
+may have only partially extracted a file the user wanted.
+Additionally, the file modes of extracted files and directories
+may have incorrect file bits, and the modification and access times may be
+wrong.
+.Pp
+If the creation of an archive is prematurely terminated by a signal or error,
+.Nm
+may have only partially created the archive which may violate the specific
+archive format specification.
+.Pp
+If while doing a
+.Em copy ,
+.Nm
+detects a file is about to overwrite itself, the file is not copied,
+a diagnostic message is written to
+.Dv standard error
+and when
+.Nm
+completes it will exit with a non-zero exit status.
+.Sh EXAMPLES
+The command:
+.Dl pax -w -f /dev/rst0 \&.
+copies the contents of the current directory to the device
+.Pa /dev/rst0 .
+.Pp
+The command:
+.Dl pax -v -f filename
+gives the verbose table of contents for an archive stored in
+.Pa filename .
+.Pp
+The following commands:
+.Dl mkdir newdir
+.Dl cd olddir
+.Dl pax -rw -pp .\ ../newdir
+will copy the entire
+.Pa olddir
+directory hierarchy to
+.Pa newdir ,
+preserving permissions and access times.
+.Pp
+When running as root, one may also wish to preserve file
+ownership when copying directory trees.
+This can be done with the following commands:
+.Dl cd olddir
+.Dl pax -rw -pe .\ ../newdir
+which will copy the contents of
+.Pa olddir
+into
+.Pa ../newdir ,
+preserving ownership, permissions and access times.
+.Pp
+The command:
+.Dl pax -r -s ',^//*usr//*,,' -f a.pax
+reads the archive
+.Pa a.pax ,
+with all files rooted in ``/usr'' into the archive extracted relative to the
+current directory.
+.Pp
+The command:
+.Dl pax -rw -i .\ dest_dir
+can be used to interactively select the files to copy from the current
+directory to
+.Pa dest_dir .
+.Pp
+The command:
+.Dl pax -r -pe -U root -G bin -f a.pax
+will extract all files from the archive
+.Pa a.pax
+which are owned by
+.Em root
+with group
+.Em bin
+and will preserve all file permissions.
+.Pp
+The command:
+.Dl pax -r -w -v -Y -Z home /backup
+will update (and list) only those files in the destination directory
+.Pa /backup
+which are older (less recent inode change or file modification times) than
+files with the same name found in the source file tree
+.Pa home .
+.Sh SEE ALSO
+.Xr cpio 1 ,
+.Xr tar 1 ,
+.Xr symlink 7 ,
+.Xr mtree 8
+.Sh STANDARDS
+The
+.Nm
+utility is a superset of the
+.St -p1003.2
+standard.
+The options
+.Fl B ,
+.Fl D ,
+.Fl E ,
+.Fl G ,
+.Fl H ,
+.Fl L ,
+.Fl M ,
+.Fl O ,
+.Fl P ,
+.Fl T ,
+.Fl U ,
+.Fl Y ,
+.Fl Z ,
+.Fl z ,
+the archive formats
+.Ar bcpio ,
+.Ar sv4cpio ,
+.Ar sv4crc ,
+.Ar tar ,
+and the flawed archive handling during
+.Ar list
+and
+.Ar read
+operations are extensions to the
+.Tn POSIX
+standard.
+.Sh HISTORY
+A
+.Nm
+utility appeared in
+.Bx 4.4 .
+.Sh AUTHORS
+.An -nosplit
+.An Keith Muller
+at the University of California, San Diego.
+.An Luke Mewburn
+implemented
+.Fl M .
diff --git a/bin/pax/pax.c b/bin/pax/pax.c
new file mode 100644
index 0000000..3906569
--- /dev/null
+++ b/bin/pax/pax.c
@@ -0,0 +1,492 @@
+/* $NetBSD: pax.c,v 1.48 2017/10/02 21:55:35 joerg Exp $ */
+
+/*-
+ * Copyright (c) 1992 Keith Muller.
+ * Copyright (c) 1992, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Keith Muller of the University of California, San Diego.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#if HAVE_NBTOOL_CONFIG_H
+#include "nbtool_config.h"
+#endif
+
+#include <sys/cdefs.h>
+#if !defined(lint)
+__COPYRIGHT("@(#) Copyright (c) 1992, 1993\
+ The Regents of the University of California. All rights reserved.");
+#if 0
+static char sccsid[] = "@(#)pax.c 8.2 (Berkeley) 4/18/94";
+#else
+__RCSID("$NetBSD: pax.c,v 1.48 2017/10/02 21:55:35 joerg Exp $");
+#endif
+#endif /* not lint */
+
+#include <sys/param.h>
+#include <sys/resource.h>
+#include <sys/stat.h>
+#include <sys/time.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <paths.h>
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <unistd.h>
+#include <util.h>
+#include "pax.h"
+#include "extern.h"
+static int gen_init(void);
+
+/*
+ * PAX main routines, general globals and some simple start up routines
+ */
+
+/*
+ * Variables that can be accessed by any routine within pax
+ */
+int act = ERROR; /* read/write/append/copy */
+FSUB *frmt = NULL; /* archive format type */
+int cflag; /* match all EXCEPT pattern/file */
+int cwdfd = -1; /* starting cwd */
+int dflag; /* directory member match only */
+int iflag; /* interactive file/archive rename */
+int kflag; /* do not overwrite existing files */
+int lflag; /* use hard links when possible */
+int nflag; /* select first archive member match */
+int tflag; /* restore access time after read */
+int uflag; /* ignore older modification time files */
+int vflag; /* produce verbose output */
+int Aflag; /* honor absolute path */
+int Dflag; /* same as uflag except inode change time */
+int Hflag; /* follow command line symlinks (write only) */
+int Lflag; /* follow symlinks when writing */
+int Mflag; /* treat stdin as an mtree(8) specfile */
+int Vflag; /* produce somewhat verbose output (no listing) */
+int Xflag; /* archive files with same device id only */
+int Yflag; /* same as Dflg except after name mode */
+int Zflag; /* same as uflg except after name mode */
+int vfpart; /* is partial verbose output in progress */
+int patime = 1; /* preserve file access time */
+int pmtime = 1; /* preserve file modification times */
+int nodirs; /* do not create directories as needed */
+int pfflags = 1; /* preserve file flags */
+int pmode; /* preserve file mode bits */
+int pids; /* preserve file uid/gid */
+int rmleadslash = 0; /* remove leading '/' from pathnames */
+int exit_val; /* exit value */
+int docrc; /* check/create file crc */
+int to_stdout; /* extract to stdout */
+char *dirptr; /* destination dir in a copy */
+char *ltmfrmt; /* -v locale time format (if any) */
+const char *argv0; /* root of argv[0] */
+sigset_t s_mask; /* signal mask for cleanup critical sect */
+FILE *listf; /* file pointer to print file list to */
+char *tempfile; /* tempfile to use for mkstemp(3) */
+char *tempbase; /* basename of tempfile to use for mkstemp(3) */
+int forcelocal; /* force local operation even if the name
+ * contains a :
+ */
+int secure = 1; /* don't extract names that contain .. */
+
+/*
+ * PAX - Portable Archive Interchange
+ *
+ * A utility to read, write, and write lists of the members of archive
+ * files and copy directory hierarchies. A variety of archive formats
+ * are supported (some are described in POSIX 1003.1 10.1):
+ *
+ * ustar - 10.1.1 extended tar interchange format
+ * cpio - 10.1.2 extended cpio interchange format
+ * tar - old BSD 4.3 tar format
+ * binary cpio - old cpio with binary header format
+ * sysVR4 cpio - with and without CRC
+ *
+ * This version is a superset of IEEE Std 1003.2b-d3
+ *
+ * Summary of Extensions to the IEEE Standard:
+ *
+ * 1 READ ENHANCEMENTS
+ * 1.1 Operations which read archives will continue to operate even when
+ * processing archives which may be damaged, truncated, or fail to meet
+ * format specs in several different ways. Damaged sections of archives
+ * are detected and avoided if possible. Attempts will be made to resync
+ * archive read operations even with badly damaged media.
+ * 1.2 Blocksize requirements are not strictly enforced on archive read.
+ * Tapes which have variable sized records can be read without errors.
+ * 1.3 The user can specify via the non-standard option flag -E if error
+ * resync operation should stop on a media error, try a specified number
+ * of times to correct, or try to correct forever.
+ * 1.4 Sparse files (lseek holes) stored on the archive (but stored with blocks
+ * of all zeros will be restored with holes appropriate for the target
+ * filesystem
+ * 1.5 The user is notified whenever something is found during archive
+ * read operations which violates spec (but the read will continue).
+ * 1.6 Multiple archive volumes can be read and may span over different
+ * archive devices
+ * 1.7 Rigidly restores all file attributes exactly as they are stored on the
+ * archive.
+ * 1.8 Modification change time ranges can be specified via multiple -T
+ * options. These allow a user to select files whose modification time
+ * lies within a specific time range.
+ * 1.9 Files can be selected based on owner (user name or uid) via one or more
+ * -U options.
+ * 1.10 Files can be selected based on group (group name or gid) via one o
+ * more -G options.
+ * 1.11 File modification time can be checked against existing file after
+ * name modification (-Z)
+ *
+ * 2 WRITE ENHANCEMENTS
+ * 2.1 Write operation will stop instead of allowing a user to create a flawed
+ * flawed archive (due to any problem).
+ * 2.2 Archives written by pax are forced to strictly conform to both the
+ * archive and pax the specific format specifications.
+ * 2.3 Blocking size and format is rigidly enforced on writes.
+ * 2.4 Formats which may exhibit header overflow problems (they have fields
+ * too small for large file systems, such as inode number storage), use
+ * routines designed to repair this problem. These techniques still
+ * conform to both pax and format specifications, but no longer truncate
+ * these fields. This removes any restrictions on using these archive
+ * formats on large file systems.
+ * 2.5 Multiple archive volumes can be written and may span over different
+ * archive devices
+ * 2.6 A archive volume record limit allows the user to specify the number
+ * of bytes stored on an archive volume. When reached the user is
+ * prompted for the next archive volume. This is specified with the
+ * non-standard -B flag. The limit is rounded up to the next blocksize.
+ * 2.7 All archive padding during write use zero filled sections. This makes
+ * it much easier to pull data out of flawed archive during read
+ * operations.
+ * 2.8 Access time reset with the -t applies to all file nodes (including
+ * directories).
+ * 2.9 Symbolic links can be followed with -L (optional in the spec).
+ * 2.10 Modification or inode change time ranges can be specified via
+ * multiple -T options. These allow a user to select files whose
+ * modification or inode change time lies within a specific time range.
+ * 2.11 Files can be selected based on owner (user name or uid) via one or more
+ * -U options.
+ * 2.12 Files can be selected based on group (group name or gid) via one o
+ * more -G options.
+ * 2.13 Symlinks which appear on the command line can be followed (without
+ * following other symlinks; -H flag)
+ *
+ * 3 COPY ENHANCEMENTS
+ * 3.1 Sparse files (lseek holes) can be copied without expanding the holes
+ * into zero filled blocks. The file copy is created with holes which are
+ * appropriate for the target filesystem
+ * 3.2 Access time as well as modification time on copied file trees can be
+ * preserved with the appropriate -p options.
+ * 3.3 Access time reset with the -t applies to all file nodes (including
+ * directories).
+ * 3.4 Symbolic links can be followed with -L (optional in the spec).
+ * 3.5 Modification or inode change time ranges can be specified via
+ * multiple -T options. These allow a user to select files whose
+ * modification or inode change time lies within a specific time range.
+ * 3.6 Files can be selected based on owner (user name or uid) via one or more
+ * -U options.
+ * 3.7 Files can be selected based on group (group name or gid) via one o
+ * more -G options.
+ * 3.8 Symlinks which appear on the command line can be followed (without
+ * following other symlinks; -H flag)
+ * 3.9 File inode change time can be checked against existing file before
+ * name modification (-D)
+ * 3.10 File inode change time can be checked against existing file after
+ * name modification (-Y)
+ * 3.11 File modification time can be checked against existing file after
+ * name modification (-Z)
+ *
+ * 4 GENERAL ENHANCEMENTS
+ * 4.1 Internal structure is designed to isolate format dependent and
+ * independent functions. Formats are selected via a format driver table.
+ * This encourages the addition of new archive formats by only having to
+ * write those routines which id, read and write the archive header.
+ */
+
+/*
+ * main()
+ * parse options, set up and operate as specified by the user.
+ * any operational flaw will set exit_val to non-zero
+ * Return: 0 if ok, 1 otherwise
+ */
+
+int
+main(int argc, char **argv)
+{
+ const char *tmpdir;
+ size_t tdlen;
+ int rval;
+
+ setprogname(argv[0]);
+
+ listf = stderr;
+
+ /*
+ * parse options, determine operational mode
+ */
+ options(argc, argv);
+
+ /*
+ * general init
+ */
+ if ((gen_init() < 0) || (tty_init() < 0))
+ return exit_val;
+
+ /*
+ * Keep a reference to cwd, so we can always come back home.
+ */
+ cwdfd = open(".", O_RDONLY);
+ if (cwdfd < 0) {
+ syswarn(1, errno, "Can't open current working directory.");
+ return exit_val;
+ }
+ if (updatepath() == -1)
+ return exit_val;
+
+ /*
+ * Where should we put temporary files?
+ */
+ if ((tmpdir = getenv("TMPDIR")) == NULL || *tmpdir == '\0')
+ tmpdir = _PATH_TMP;
+ tdlen = strlen(tmpdir);
+ while(tdlen > 0 && tmpdir[tdlen - 1] == '/')
+ tdlen--;
+ tempfile = malloc(tdlen + 1 + sizeof(_TFILE_BASE));
+ if (tempfile == NULL) {
+ tty_warn(1, "Cannot allocate memory for temp file name.");
+ return exit_val;
+ }
+ if (tdlen)
+ memcpy(tempfile, tmpdir, tdlen);
+ tempbase = tempfile + tdlen;
+ *tempbase++ = '/';
+
+ (void)time(&starttime);
+#ifdef SIGINFO
+ (void)signal(SIGINFO, ar_summary);
+#endif
+ /*
+ * select a primary operation mode
+ */
+ switch (act) {
+ case EXTRACT:
+ rval = extract();
+ break;
+ case ARCHIVE:
+ rval = archive();
+ break;
+ case APPND:
+ if (gzip_program != NULL)
+ err(1, "cannot gzip while appending");
+ rval = append();
+ /*
+ * Check if we tried to append on an empty file and
+ * turned into ARCHIVE mode.
+ */
+ if (act == -ARCHIVE) {
+ act = ARCHIVE;
+ rval = archive();
+ }
+ break;
+ case COPY:
+ rval = copy();
+ break;
+ default:
+ case LIST:
+ rval = list();
+ break;
+ }
+ if (rval != 0)
+ exit_val = 1;
+ return exit_val;
+}
+
+/*
+ * sig_cleanup()
+ * when interrupted we try to do whatever delayed processing we can.
+ * This is not critical, but we really ought to limit our damage when we
+ * are aborted by the user.
+ * Return:
+ * never....
+ */
+
+__dead static void
+sig_cleanup(int which_sig)
+{
+ /*
+ * restore modes and times for any dirs we may have created
+ * or any dirs we may have read. Set vflag and vfpart so the user
+ * will clearly see the message on a line by itself.
+ */
+ vflag = vfpart = 1;
+#ifdef SIGXCPU
+ if (which_sig == SIGXCPU)
+ tty_warn(1, "CPU time limit reached, cleaning up.");
+ else
+#endif
+ tty_warn(1, "Signal caught, cleaning up.");
+
+ /* delete any open temporary file */
+ if (xtmp_name)
+ (void)unlink(xtmp_name);
+ ar_close();
+ proc_dir();
+ if (tflag)
+ atdir_end();
+
+ (void)raise_default_signal(which_sig);
+ exit(1);
+}
+
+/*
+ * gen_init()
+ * general setup routines. Not all are required, but they really help
+ * when dealing with a medium to large sized archives.
+ */
+
+static int
+gen_init(void)
+{
+ struct rlimit reslimit;
+ struct sigaction n_hand;
+ struct sigaction o_hand;
+
+ /*
+ * Really needed to handle large archives. We can run out of memory for
+ * internal tables really fast when we have a whole lot of files...
+ */
+ if (getrlimit(RLIMIT_DATA , &reslimit) == 0){
+ reslimit.rlim_cur = reslimit.rlim_max;
+ (void)setrlimit(RLIMIT_DATA , &reslimit);
+ }
+
+ /*
+ * should file size limits be waived? if the os limits us, this is
+ * needed if we want to write a large archive
+ */
+ if (getrlimit(RLIMIT_FSIZE , &reslimit) == 0){
+ reslimit.rlim_cur = reslimit.rlim_max;
+ (void)setrlimit(RLIMIT_FSIZE , &reslimit);
+ }
+
+ /*
+ * increase the size the stack can grow to
+ */
+ if (getrlimit(RLIMIT_STACK , &reslimit) == 0){
+ reslimit.rlim_cur = reslimit.rlim_max;
+ (void)setrlimit(RLIMIT_STACK , &reslimit);
+ }
+
+#ifdef RLIMIT_RSS
+ /*
+ * not really needed, but doesn't hurt
+ */
+ if (getrlimit(RLIMIT_RSS , &reslimit) == 0){
+ reslimit.rlim_cur = reslimit.rlim_max;
+ (void)setrlimit(RLIMIT_RSS , &reslimit);
+ }
+#endif
+
+ /*
+ * Handle posix locale
+ *
+ * set user defines time printing format for -v option
+ */
+ ltmfrmt = getenv("LC_TIME");
+
+ /*
+ * signal handling to reset stored directory times and modes. Since
+ * we deal with broken pipes via failed writes we ignore it. We also
+ * deal with any file size limit through failed writes. CPU time
+ * limits are caught and a cleanup is forced.
+ */
+ if ((sigemptyset(&s_mask) < 0) || (sigaddset(&s_mask, SIGTERM) < 0) ||
+ (sigaddset(&s_mask,SIGINT) < 0)||(sigaddset(&s_mask,SIGHUP) < 0) ||
+ (sigaddset(&s_mask,SIGPIPE) < 0)||(sigaddset(&s_mask,SIGQUIT)<0)){
+ tty_warn(1, "Unable to set up signal mask");
+ return -1;
+ }
+#ifdef SIGXCPU
+ if (sigaddset(&s_mask,SIGXCPU) < 0) {
+ tty_warn(1, "Unable to set up signal mask");
+ return -1;
+ }
+#endif
+#ifdef SIGXFSZ
+ if (sigaddset(&s_mask,SIGXFSZ) < 0) {
+ tty_warn(1, "Unable to set up signal mask");
+ return -1;
+ }
+#endif
+
+ memset(&n_hand, 0, sizeof n_hand);
+ n_hand.sa_mask = s_mask;
+ n_hand.sa_flags = 0;
+ n_hand.sa_handler = sig_cleanup;
+
+ if ((sigaction(SIGHUP, &n_hand, &o_hand) < 0) &&
+ (o_hand.sa_handler == SIG_IGN) &&
+ (sigaction(SIGHUP, &o_hand, &o_hand) < 0))
+ goto out;
+
+ if ((sigaction(SIGTERM, &n_hand, &o_hand) < 0) &&
+ (o_hand.sa_handler == SIG_IGN) &&
+ (sigaction(SIGTERM, &o_hand, &o_hand) < 0))
+ goto out;
+
+ if ((sigaction(SIGINT, &n_hand, &o_hand) < 0) &&
+ (o_hand.sa_handler == SIG_IGN) &&
+ (sigaction(SIGINT, &o_hand, &o_hand) < 0))
+ goto out;
+
+ if ((sigaction(SIGQUIT, &n_hand, &o_hand) < 0) &&
+ (o_hand.sa_handler == SIG_IGN) &&
+ (sigaction(SIGQUIT, &o_hand, &o_hand) < 0))
+ goto out;
+
+#ifdef SIGXCPU
+ if ((sigaction(SIGXCPU, &n_hand, &o_hand) < 0) &&
+ (o_hand.sa_handler == SIG_IGN) &&
+ (sigaction(SIGXCPU, &o_hand, &o_hand) < 0))
+ goto out;
+#endif
+ n_hand.sa_handler = SIG_IGN;
+ if (sigaction(SIGPIPE, &n_hand, &o_hand) < 0)
+ goto out;
+#ifdef SIGXFSZ
+ if (sigaction(SIGXFSZ, &n_hand, &o_hand) < 0)
+ goto out;
+#endif
+ return 0;
+
+ out:
+ syswarn(1, errno, "Unable to set up signal handler");
+ return -1;
+}
diff --git a/bin/pax/pax.h b/bin/pax/pax.h
new file mode 100644
index 0000000..ccc0fb7
--- /dev/null
+++ b/bin/pax/pax.h
@@ -0,0 +1,283 @@
+/* $NetBSD: pax.h,v 1.31 2012/08/09 08:09:21 christos Exp $ */
+
+/*-
+ * Copyright (c) 1992 Keith Muller.
+ * Copyright (c) 1992, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Keith Muller of the University of California, San Diego.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * @(#)pax.h 8.2 (Berkeley) 4/18/94
+ */
+
+#if ! HAVE_NBTOOL_CONFIG_H
+#define HAVE_LUTIMES 1
+#define HAVE_STRUCT_STAT_ST_FLAGS 1
+#endif
+
+/*
+ * BSD PAX global data structures and constants.
+ */
+
+#define MAXBLK 32256 /* MAX blocksize supported (posix SPEC) */
+ /* WARNING: increasing MAXBLK past 32256 */
+ /* will violate posix spec. */
+#define BLKMULT 512 /* blocksize must be even mult of 512 bytes */
+ /* Don't even think of changing this */
+#define DEVBLK 8192 /* default read blksize for devices */
+#define FILEBLK 10240 /* default read blksize for files */
+#define PAXPATHLEN 3072 /* maximum path length for pax. MUST be */
+ /* longer than the system MAXPATHLEN */
+
+/*
+ * Pax modes of operation
+ */
+#define ERROR -1 /* nothing selected */
+#define LIST 0 /* List the file in an archive */
+#define EXTRACT 1 /* extract the files in an archive */
+#define ARCHIVE 2 /* write a new archive */
+#define APPND 3 /* append to the end of an archive */
+#define COPY 4 /* copy files to destination dir */
+
+/*
+ * Device type of the current archive volume
+ */
+#define ISREG 0 /* regular file */
+#define ISCHR 1 /* character device */
+#define ISBLK 2 /* block device */
+#define ISTAPE 3 /* tape drive */
+#define ISPIPE 4 /* pipe/socket */
+#ifdef SUPPORT_RMT
+#define ISRMT 5 /* rmt */
+#endif
+
+/*
+ * Pattern matching structure
+ *
+ * Used to store command line patterns
+ */
+typedef struct pattern {
+ char *pstr; /* pattern to match, user supplied */
+ char *pend; /* end of a prefix match */
+ char *chdname; /* the dir to change to if not NULL. */
+ int plen; /* length of pstr */
+ int flgs; /* processing/state flags */
+#define MTCH 0x1 /* pattern has been matched */
+#define DIR_MTCH 0x2 /* pattern matched a directory */
+#define NOGLOB_MTCH 0x4 /* non-globbing match */
+ struct pattern *fow; /* next pattern */
+} PATTERN;
+
+/*
+ * General Archive Structure (used internal to pax)
+ *
+ * This structure is used to pass information about archive members between
+ * the format independent routines and the format specific routines. When
+ * new archive formats are added, they must accept requests and supply info
+ * encoded in a structure of this type. The name fields are declared statically
+ * here, as there is only ONE of these floating around, size is not a major
+ * consideration. Eventually converting the name fields to a dynamic length
+ * may be required if and when the supporting operating system removes all
+ * restrictions on the length of pathnames it will resolve.
+ */
+typedef struct {
+ int nlen; /* file name length */
+ char name[PAXPATHLEN+1]; /* file name */
+ int ln_nlen; /* link name length */
+ char ln_name[PAXPATHLEN+1]; /* name to link to (if any) */
+ char *org_name; /* orig name in file system */
+ char fts_name[PAXPATHLEN+1]; /* name from fts (for *org_name) */
+ char *tmp_name; /* tmp name used to restore */
+ PATTERN *pat; /* ptr to pattern match (if any) */
+ struct stat sb; /* stat buffer see stat(2) */
+ off_t pad; /* bytes of padding after file xfer */
+ off_t skip; /* bytes of real data after header */
+ /* IMPORTANT. The st_size field does */
+ /* not always indicate the amount of */
+ /* data following the header. */
+ u_long crc; /* file crc */
+ int type; /* type of file node */
+#define PAX_DIR 1 /* directory */
+#define PAX_CHR 2 /* character device */
+#define PAX_BLK 3 /* block device */
+#define PAX_REG 4 /* regular file */
+#define PAX_SLK 5 /* symbolic link */
+#define PAX_SCK 6 /* socket */
+#define PAX_FIF 7 /* fifo */
+#define PAX_HLK 8 /* hard link */
+#define PAX_HRG 9 /* hard link to a regular file */
+#define PAX_CTG 10 /* high performance file */
+#define PAX_GLL 11 /* GNU long symlink */
+#define PAX_GLF 12 /* GNU long file */
+} ARCHD;
+
+/*
+ * Format Specific Routine Table
+ *
+ * The format specific routine table allows new archive formats to be quickly
+ * added. Overall pax operation is independent of the actual format used to
+ * form the archive. Only those routines which deal directly with the archive
+ * are tailored to the oddities of the specific format. All other routines are
+ * independent of the archive format. Data flow in and out of the format
+ * dependent routines pass pointers to ARCHD structure (described below).
+ */
+typedef struct {
+ const char *name; /* name of format, this is the name the user */
+ /* gives to -x option to select it. */
+ int bsz; /* default block size. used when the user */
+ /* does not specify a blocksize for writing */
+ /* Appends continue to with the blocksize */
+ /* the archive is currently using.*/
+ int hsz; /* Header size in bytes. this is the size of */
+ /* the smallest header this format supports. */
+ /* Headers are assumed to fit in a BLKMULT. */
+ /* If they are bigger, get_head() and */
+ /* get_arc() must be adjusted */
+ int udev; /* does append require unique dev/ino? some */
+ /* formats use the device and inode fields */
+ /* to specify hard links. when members in */
+ /* the archive have the same inode/dev they */
+ /* are assumed to be hard links. During */
+ /* append we may have to generate unique ids */
+ /* to avoid creating incorrect hard links */
+ int hlk; /* does archive store hard links info? if */
+ /* not, we do not bother to look for them */
+ /* during archive write operations */
+ int blkalgn; /* writes must be aligned to blkalgn boundary */
+ int inhead; /* is the trailer encoded in a valid header? */
+ /* if not, trailers are assumed to be found */
+ /* in invalid headers (i.e like tar) */
+ int (*id)(char *, int); /* checks if a buffer is a valid header */
+ /* returns 1 if it is, o.w. returns a 0 */
+ int (*st_rd)(void); /* initialize routine for read. so format */
+ /* can set up tables etc before it starts */
+ /* reading an archive */
+ int (*rd) /* read header routine. passed a pointer to */
+ (ARCHD *, char *); /* ARCHD. It must extract the info */
+ /* from the format and store it in the ARCHD */
+ /* struct. This routine is expected to fill */
+ /* all the fields in the ARCHD (including */
+ /* stat buf). 0 is returned when a valid */
+ /* header is found. -1 when not valid. This */
+ /* routine set the skip and pad fields so the */
+ /* format independent routines know the */
+ /* amount of padding and the number of bytes */
+ /* of data which follow the header. This info */
+ /* is used to skip to the next file header */
+ off_t (*end_rd)(void); /* read cleanup. Allows format to clean up */
+ /* and MUST RETURN THE LENGTH OF THE TRAILER */
+ /* RECORD (so append knows how many bytes */
+ /* to move back to rewrite the trailer) */
+ int (*st_wr)(void); /* initialize routine for write operations */
+ int (*wr)(ARCHD *); /* write archive header. Passed an ARCHD */
+ /* filled with the specs on the next file to */
+ /* archived. Returns a 1 if no file data is */
+ /* is to be stored; 0 if file data is to be */
+ /* added. A -1 is returned if a write */
+ /* operation to the archive failed. this */
+ /* function sets the skip and pad fields so */
+ /* the proper padding can be added after */
+ /* file data. This routine must NEVER write */
+ /* a flawed archive header. */
+ int (*end_wr)(void); /* end write. write the trailer and do any */
+ /* other format specific functions needed */
+ /* at the end of an archive write */
+ int (*trail) /* returns 0 if a valid trailer, -1 if not */
+ (char *, int, int *); /* For formats which encode the */
+ /* trailer outside of a valid header, a */
+ /* return value of 1 indicates that the block */
+ /* passed to it can never contain a valid */
+ /* header (skip this block, no point in */
+ /* looking at it) */
+ int (*subtrail) /* read/process file data from the archive */
+ (ARCHD *); /* this function is called for trailers */
+ /* inside headers. */
+ int (*rd_data) /* read/process file data from the archive */
+ (ARCHD *, int, off_t *);
+ int (*wr_data) /* write/process file data to the archive */
+ (ARCHD *, int, off_t *);
+ int (*options)(void); /* process format specific options (-o) */
+} FSUB;
+
+/*
+ * Format Specific Options List
+ *
+ * Used to pass format options to the format options handler
+ */
+typedef struct oplist {
+ char *name; /* option variable name e.g. name= */
+ char *value; /* value for option variable */
+ struct oplist *fow; /* next option */
+} OPLIST;
+
+/*
+ * General Macros
+ */
+#ifndef MIN
+#define MIN(a,b) (((a)<(b))?(a):(b))
+#endif
+
+#ifdef HOSTPROG
+# include "pack_dev.h" /* explicitly use NetBSD's macros */
+# define MAJOR(x) major_netbsd(x)
+# define MINOR(x) minor_netbsd(x)
+# define TODEV(x, y) makedev_netbsd((x), (y))
+#else
+# define MAJOR(x) major(x)
+# define MINOR(x) minor(x)
+# define TODEV(x, y) makedev((x), (y))
+#endif
+
+/*
+ * General Defines
+ */
+#define HEX 16
+#define OCT 8
+#define _PAX_ 1
+
+/*
+ * Pathname base component of the temporary file template, to be created in
+ * ${TMPDIR} or, as a fall-back, _PATH_TMP.
+ */
+#define _TFILE_BASE "paxXXXXXXXXXX"
+
+/*
+ * Macros to manipulate off_t as uintmax_t
+ */
+#define OFFT_F "%" PRIuMAX
+#define OFFT_FP(x) "%" x PRIuMAX
+#define OFFT_T uintmax_t
+#define ASC_OFFT(x,y,z) asc_umax(x,y,z)
+#define OFFT_ASC(w,x,y,z) umax_asc((uintmax_t)w,x,y,z)
+#define OFFT_OCT(w,x,y,z) umax_oct((uintmax_t)w,x,y,z)
+#define STRTOOFFT(x,y,z) strtoimax(x,y,z)
+#define OFFT_MAX INTMAX_MAX
+
+#define TOP_HALF 0xffffffff00000000ULL
+#define BOTTOM_HALF 0x00000000ffffffffULL
+
diff --git a/bin/pax/sel_subs.c b/bin/pax/sel_subs.c
new file mode 100644
index 0000000..87f1e79
--- /dev/null
+++ b/bin/pax/sel_subs.c
@@ -0,0 +1,617 @@
+/* $NetBSD: sel_subs.c,v 1.24 2011/08/31 16:24:54 plunky Exp $ */
+
+/*-
+ * Copyright (c) 1992 Keith Muller.
+ * Copyright (c) 1992, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Keith Muller of the University of California, San Diego.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#if HAVE_NBTOOL_CONFIG_H
+#include "nbtool_config.h"
+#endif
+
+#include <sys/cdefs.h>
+#if !defined(lint)
+#if 0
+static char sccsid[] = "@(#)sel_subs.c 8.1 (Berkeley) 5/31/93";
+#else
+__RCSID("$NetBSD: sel_subs.c,v 1.24 2011/08/31 16:24:54 plunky Exp $");
+#endif
+#endif /* not lint */
+
+#include <sys/types.h>
+#include <sys/time.h>
+#include <sys/stat.h>
+#include <sys/param.h>
+
+#include <pwd.h>
+#include <grp.h>
+#include <stdio.h>
+#include <ctype.h>
+#include <string.h>
+#include <strings.h>
+#include <time.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <tzfile.h>
+
+#include "pax.h"
+#include "sel_subs.h"
+#include "extern.h"
+
+static int str_sec(const char *, time_t *);
+static int usr_match(ARCHD *);
+static int grp_match(ARCHD *);
+static int trng_match(ARCHD *);
+
+static TIME_RNG *trhead = NULL; /* time range list head */
+static TIME_RNG *trtail = NULL; /* time range list tail */
+static USRT **usrtb = NULL; /* user selection table */
+static GRPT **grptb = NULL; /* group selection table */
+
+/*
+ * Routines for selection of archive members
+ */
+
+/*
+ * sel_chk()
+ * check if this file matches a specified uid, gid or time range
+ * Return:
+ * 0 if this archive member should be processed, 1 if it should be skipped
+ */
+
+int
+sel_chk(ARCHD *arcn)
+{
+ if (((usrtb != NULL) && usr_match(arcn)) ||
+ ((grptb != NULL) && grp_match(arcn)) ||
+ ((trhead != NULL) && trng_match(arcn)))
+ return 1;
+ return 0;
+}
+
+/*
+ * User/group selection routines
+ *
+ * Routines to handle user selection of files based on the file uid/gid. To
+ * add an entry, the user supplies either the name or the uid/gid starting with
+ * a # on the command line. A \# will escape the #.
+ */
+
+/*
+ * usr_add()
+ * add a user match to the user match hash table
+ * Return:
+ * 0 if added ok, -1 otherwise;
+ */
+
+int
+usr_add(char *str)
+{
+ u_int indx;
+ USRT *pt;
+ struct passwd *pw;
+ uid_t uid;
+
+ /*
+ * create the table if it doesn't exist
+ */
+ if ((str == NULL) || (*str == '\0'))
+ return -1;
+ if ((usrtb == NULL) &&
+ ((usrtb = (USRT **)calloc(USR_TB_SZ, sizeof(USRT *))) == NULL)) {
+ tty_warn(1,
+ "Unable to allocate memory for user selection table");
+ return -1;
+ }
+
+ /*
+ * figure out user spec
+ */
+ if (str[0] != '#') {
+ /*
+ * it is a user name, \# escapes # as first char in user name
+ */
+ if ((str[0] == '\\') && (str[1] == '#'))
+ ++str;
+ if ((pw = getpwnam(str)) == NULL) {
+ tty_warn(1, "Unable to find uid for user: %s", str);
+ return -1;
+ }
+ uid = (uid_t)pw->pw_uid;
+ } else
+ uid = (uid_t)strtoul(str+1, NULL, 10);
+ endpwent();
+
+ /*
+ * hash it and go down the hash chain (if any) looking for it
+ */
+ indx = ((unsigned)uid) % USR_TB_SZ;
+ if ((pt = usrtb[indx]) != NULL) {
+ while (pt != NULL) {
+ if (pt->uid == uid)
+ return 0;
+ pt = pt->fow;
+ }
+ }
+
+ /*
+ * uid is not yet in the table, add it to the front of the chain
+ */
+ if ((pt = (USRT *)malloc(sizeof(USRT))) != NULL) {
+ pt->uid = uid;
+ pt->fow = usrtb[indx];
+ usrtb[indx] = pt;
+ return 0;
+ }
+ tty_warn(1, "User selection table out of memory");
+ return -1;
+}
+
+/*
+ * usr_match()
+ * check if this files uid matches a selected uid.
+ * Return:
+ * 0 if this archive member should be processed, 1 if it should be skipped
+ */
+
+static int
+usr_match(ARCHD *arcn)
+{
+ USRT *pt;
+
+ /*
+ * hash and look for it in the table
+ */
+ pt = usrtb[((unsigned)arcn->sb.st_uid) % USR_TB_SZ];
+ while (pt != NULL) {
+ if (pt->uid == arcn->sb.st_uid)
+ return 0;
+ pt = pt->fow;
+ }
+
+ /*
+ * not found
+ */
+ return 1;
+}
+
+/*
+ * grp_add()
+ * add a group match to the group match hash table
+ * Return:
+ * 0 if added ok, -1 otherwise;
+ */
+
+int
+grp_add(char *str)
+{
+ u_int indx;
+ GRPT *pt;
+ struct group *gr;
+ gid_t gid;
+
+ /*
+ * create the table if it doesn't exist
+ */
+ if ((str == NULL) || (*str == '\0'))
+ return -1;
+ if ((grptb == NULL) &&
+ ((grptb = (GRPT **)calloc(GRP_TB_SZ, sizeof(GRPT *))) == NULL)) {
+ tty_warn(1,
+ "Unable to allocate memory fo group selection table");
+ return -1;
+ }
+
+ /*
+ * figure out user spec
+ */
+ if (str[0] != '#') {
+ /*
+ * it is a group name, \# escapes # as first char in group name
+ */
+ if ((str[0] == '\\') && (str[1] == '#'))
+ ++str;
+ if ((gr = getgrnam(str)) == NULL) {
+ tty_warn(1,
+ "Cannot determine gid for group name: %s", str);
+ return -1;
+ }
+ gid = (gid_t)gr->gr_gid;
+ } else
+ gid = (gid_t)strtoul(str+1, NULL, 10);
+ endgrent();
+
+ /*
+ * hash it and go down the hash chain (if any) looking for it
+ */
+ indx = ((unsigned)gid) % GRP_TB_SZ;
+ if ((pt = grptb[indx]) != NULL) {
+ while (pt != NULL) {
+ if (pt->gid == gid)
+ return 0;
+ pt = pt->fow;
+ }
+ }
+
+ /*
+ * gid not in the table, add it to the front of the chain
+ */
+ if ((pt = (GRPT *)malloc(sizeof(GRPT))) != NULL) {
+ pt->gid = gid;
+ pt->fow = grptb[indx];
+ grptb[indx] = pt;
+ return 0;
+ }
+ tty_warn(1, "Group selection table out of memory");
+ return -1;
+}
+
+/*
+ * grp_match()
+ * check if this files gid matches a selected gid.
+ * Return:
+ * 0 if this archive member should be processed, 1 if it should be skipped
+ */
+
+static int
+grp_match(ARCHD *arcn)
+{
+ GRPT *pt;
+
+ /*
+ * hash and look for it in the table
+ */
+ pt = grptb[((unsigned)arcn->sb.st_gid) % GRP_TB_SZ];
+ while (pt != NULL) {
+ if (pt->gid == arcn->sb.st_gid)
+ return 0;
+ pt = pt->fow;
+ }
+
+ /*
+ * not found
+ */
+ return 1;
+}
+
+/*
+ * Time range selection routines
+ *
+ * Routines to handle user selection of files based on the modification and/or
+ * inode change time falling within a specified time range (the non-standard
+ * -T flag). The user may specify any number of different file time ranges.
+ * Time ranges are checked one at a time until a match is found (if at all).
+ * If the file has a mtime (and/or ctime) which lies within one of the time
+ * ranges, the file is selected. Time ranges may have a lower and/or a upper
+ * value. These ranges are inclusive. When no time ranges are supplied to pax
+ * with the -T option, all members in the archive will be selected by the time
+ * range routines. When only a lower range is supplied, only files with a
+ * mtime (and/or ctime) equal to or younger are selected. When only a upper
+ * range is supplied, only files with a mtime (and/or ctime) equal to or older
+ * are selected. When the lower time range is equal to the upper time range,
+ * only files with a mtime (or ctime) of exactly that time are selected.
+ */
+
+/*
+ * trng_add()
+ * add a time range match to the time range list.
+ * This is a non-standard pax option. Lower and upper ranges are in the
+ * format: [yy[mm[dd[hh]]]]mm[.ss] and are comma separated.
+ * Time ranges are based on current time, so 1234 would specify a time of
+ * 12:34 today.
+ * Return:
+ * 0 if the time range was added to the list, -1 otherwise
+ */
+
+int
+trng_add(char *str)
+{
+ TIME_RNG *pt;
+ char *up_pt = NULL;
+ char *stpt;
+ char *flgpt;
+ int dot = 0;
+
+ /*
+ * throw out the badly formed time ranges
+ */
+ if ((str == NULL) || (*str == '\0')) {
+ tty_warn(1, "Empty time range string");
+ return -1;
+ }
+
+ /*
+ * locate optional flags suffix /{cm}.
+ */
+ if ((flgpt = strrchr(str, '/')) != NULL)
+ *flgpt++ = '\0';
+
+ for (stpt = str; *stpt != '\0'; ++stpt) {
+ if ((*stpt >= '0') && (*stpt <= '9'))
+ continue;
+ if ((*stpt == ',') && (up_pt == NULL)) {
+ *stpt = '\0';
+ up_pt = stpt + 1;
+ dot = 0;
+ continue;
+ }
+
+ /*
+ * allow only one dot per range (secs)
+ */
+ if ((*stpt == '.') && (!dot)) {
+ ++dot;
+ continue;
+ }
+ tty_warn(1, "Improperly specified time range: %s", str);
+ goto out;
+ }
+
+ /*
+ * allocate space for the time range and store the limits
+ */
+ if ((pt = malloc(sizeof(TIME_RNG))) == NULL) {
+ tty_warn(1, "Unable to allocate memory for time range");
+ return -1;
+ }
+
+ /*
+ * by default we only will check file mtime, but user can specify
+ * mtime, ctime (inode change time) or both.
+ */
+ if ((flgpt == NULL) || (*flgpt == '\0'))
+ pt->flgs = CMPMTME;
+ else {
+ pt->flgs = 0;
+ while (*flgpt != '\0') {
+ switch(*flgpt) {
+ case 'M':
+ case 'm':
+ pt->flgs |= CMPMTME;
+ break;
+ case 'C':
+ case 'c':
+ pt->flgs |= CMPCTME;
+ break;
+ default:
+ tty_warn(1, "Bad option %c with time range %s",
+ *flgpt, str);
+ free(pt);
+ goto out;
+ }
+ ++flgpt;
+ }
+ }
+
+ /*
+ * start off with the current time
+ */
+ pt->low_time = pt->high_time = time(NULL);
+ if (*str != '\0') {
+ /*
+ * add lower limit
+ */
+ if (str_sec(str, &(pt->low_time)) < 0) {
+ tty_warn(1, "Illegal lower time range %s", str);
+ free(pt);
+ goto out;
+ }
+ pt->flgs |= HASLOW;
+ }
+
+ if ((up_pt != NULL) && (*up_pt != '\0')) {
+ /*
+ * add upper limit
+ */
+ if (str_sec(up_pt, &(pt->high_time)) < 0) {
+ tty_warn(1, "Illegal upper time range %s", up_pt);
+ free(pt);
+ goto out;
+ }
+ pt->flgs |= HASHIGH;
+
+ /*
+ * check that the upper and lower do not overlap
+ */
+ if (pt->flgs & HASLOW) {
+ if (pt->low_time > pt->high_time) {
+ tty_warn(1,
+ "Upper %s and lower %s time overlap",
+ up_pt, str);
+ free(pt);
+ return -1;
+ }
+ }
+ }
+
+ pt->fow = NULL;
+ if (trhead == NULL) {
+ trtail = trhead = pt;
+ return 0;
+ }
+ trtail->fow = pt;
+ trtail = pt;
+ return 0;
+
+ out:
+ tty_warn(1, "Time range format is: [yy[mm[dd[hh]]]]mm[.ss][/[c][m]]");
+ return -1;
+}
+
+/*
+ * trng_match()
+ * check if this files mtime/ctime falls within any supplied time range.
+ * Return:
+ * 0 if this archive member should be processed, 1 if it should be skipped
+ */
+
+static int
+trng_match(ARCHD *arcn)
+{
+ TIME_RNG *pt;
+
+ /*
+ * have to search down the list one at a time looking for a match.
+ * remember time range limits are inclusive.
+ */
+ pt = trhead;
+ while (pt != NULL) {
+ switch(pt->flgs & CMPBOTH) {
+ case CMPBOTH:
+ /*
+ * user wants both mtime and ctime checked for this
+ * time range
+ */
+ if (((pt->flgs & HASLOW) &&
+ (arcn->sb.st_mtime < pt->low_time) &&
+ (arcn->sb.st_ctime < pt->low_time)) ||
+ ((pt->flgs & HASHIGH) &&
+ (arcn->sb.st_mtime > pt->high_time) &&
+ (arcn->sb.st_ctime > pt->high_time))) {
+ pt = pt->fow;
+ continue;
+ }
+ break;
+ case CMPCTME:
+ /*
+ * user wants only ctime checked for this time range
+ */
+ if (((pt->flgs & HASLOW) &&
+ (arcn->sb.st_ctime < pt->low_time)) ||
+ ((pt->flgs & HASHIGH) &&
+ (arcn->sb.st_ctime > pt->high_time))) {
+ pt = pt->fow;
+ continue;
+ }
+ break;
+ case CMPMTME:
+ default:
+ /*
+ * user wants only mtime checked for this time range
+ */
+ if (((pt->flgs & HASLOW) &&
+ (arcn->sb.st_mtime < pt->low_time)) ||
+ ((pt->flgs & HASHIGH) &&
+ (arcn->sb.st_mtime > pt->high_time))) {
+ pt = pt->fow;
+ continue;
+ }
+ break;
+ }
+ break;
+ }
+
+ if (pt == NULL)
+ return 1;
+ return 0;
+}
+
+/*
+ * str_sec()
+ * Convert a time string in the format of [yy[mm[dd[hh]]]]mm[.ss] to gmt
+ * seconds. Tval already has current time loaded into it at entry.
+ * Return:
+ * 0 if converted ok, -1 otherwise
+ */
+
+#define ATOI2(s) ((s) += 2, ((s)[-2] - '0') * 10 + ((s)[-1] - '0'))
+
+static int
+str_sec(const char *p, time_t *tval)
+{
+ struct tm *lt;
+ const char *dot, *t;
+ int yearset, len;
+
+ for (t = p, dot = NULL; *t; ++t) {
+ if (isdigit((unsigned char)*t))
+ continue;
+ if (*t == '.' && dot == NULL) {
+ dot = t;
+ continue;
+ }
+ return -1;
+ }
+
+ lt = localtime(tval);
+
+ if (dot != NULL) {
+ len = strlen(dot);
+ if (len != 3)
+ return -1;
+ ++dot;
+ lt->tm_sec = ATOI2(dot);
+ } else {
+ len = 0;
+ lt->tm_sec = 0;
+ }
+
+ yearset = 0;
+ switch (strlen(p) - len) {
+ case 12:
+ lt->tm_year = ATOI2(p) * 100 - TM_YEAR_BASE;
+ yearset = 1;
+ /* FALLTHROUGH */
+ case 10:
+ if (yearset) {
+ lt->tm_year += ATOI2(p);
+ } else {
+ yearset = ATOI2(p);
+ if (yearset < 69)
+ lt->tm_year = yearset + 2000 - TM_YEAR_BASE;
+ else
+ lt->tm_year = yearset + 1900 - TM_YEAR_BASE;
+ }
+ /* FALLTHROUGH */
+ case 8:
+ lt->tm_mon = ATOI2(p);
+ --lt->tm_mon;
+ /* FALLTHROUGH */
+ case 6:
+ lt->tm_mday = ATOI2(p);
+ /* FALLTHROUGH */
+ case 4:
+ lt->tm_hour = ATOI2(p);
+ /* FALLTHROUGH */
+ case 2:
+ lt->tm_min = ATOI2(p);
+ break;
+ default:
+ return -1;
+ }
+
+ /*
+ * convert broken-down time to GMT clock time seconds
+ */
+ if ((*tval = mktime(lt)) == -1)
+ return -1;
+ return 0;
+}
diff --git a/bin/pax/sel_subs.h b/bin/pax/sel_subs.h
new file mode 100644
index 0000000..c91f3ea
--- /dev/null
+++ b/bin/pax/sel_subs.h
@@ -0,0 +1,69 @@
+/* $NetBSD: sel_subs.h,v 1.6 2003/10/13 07:41:22 agc Exp $ */
+
+/*-
+ * Copyright (c) 1992 Keith Muller.
+ * Copyright (c) 1992, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Keith Muller of the University of California, San Diego.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * @(#)sel_subs.h 8.1 (Berkeley) 5/31/93
+ */
+
+/*
+ * data structure for storing uid/grp selects (-U, -G non standard options)
+ */
+
+#define USR_TB_SZ 317 /* user selection table size */
+#define GRP_TB_SZ 317 /* user selection table size */
+
+typedef struct usrt {
+ uid_t uid;
+ struct usrt *fow; /* next uid */
+} USRT;
+
+typedef struct grpt {
+ gid_t gid;
+ struct grpt *fow; /* next gid */
+} GRPT;
+
+/*
+ * data structure for storing user supplied time ranges (-T option)
+ */
+
+typedef struct time_rng {
+ time_t low_time; /* lower inclusive time limit */
+ time_t high_time; /* higher inclusive time limit */
+ int flgs; /* option flags */
+#define HASLOW 0x01 /* has lower time limit */
+#define HASHIGH 0x02 /* has higher time limit */
+#define CMPMTME 0x04 /* compare file modification time */
+#define CMPCTME 0x08 /* compare inode change time */
+#define CMPBOTH (CMPMTME|CMPCTME) /* compare inode and mod time */
+ struct time_rng *fow; /* next pattern */
+} TIME_RNG;
diff --git a/bin/pax/tables.c b/bin/pax/tables.c
new file mode 100644
index 0000000..dd135fe
--- /dev/null
+++ b/bin/pax/tables.c
@@ -0,0 +1,1379 @@
+/* $NetBSD: tables.c,v 1.31 2013/10/18 19:53:34 christos Exp $ */
+
+/*-
+ * Copyright (c) 1992 Keith Muller.
+ * Copyright (c) 1992, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Keith Muller of the University of California, San Diego.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#if HAVE_NBTOOL_CONFIG_H
+#include "nbtool_config.h"
+#endif
+
+#include <sys/cdefs.h>
+#if !defined(lint)
+#if 0
+static char sccsid[] = "@(#)tables.c 8.1 (Berkeley) 5/31/93";
+#else
+__RCSID("$NetBSD: tables.c,v 1.31 2013/10/18 19:53:34 christos Exp $");
+#endif
+#endif /* not lint */
+
+#include <sys/types.h>
+#include <sys/time.h>
+#include <sys/stat.h>
+#include <sys/param.h>
+#include <stdio.h>
+#include <ctype.h>
+#include <fcntl.h>
+#include <paths.h>
+#include <string.h>
+#include <unistd.h>
+#include <errno.h>
+#include <stdlib.h>
+#include "pax.h"
+#include "tables.h"
+#include "extern.h"
+
+/*
+ * Routines for controlling the contents of all the different databases pax
+ * keeps. Tables are dynamically created only when they are needed. The
+ * goal was speed and the ability to work with HUGE archives. The databases
+ * were kept simple, but do have complex rules for when the contents change.
+ * As of this writing, the POSIX library functions were more complex than
+ * needed for this application (pax databases have very short lifetimes and
+ * do not survive after pax is finished). Pax is required to handle very
+ * large archives. These database routines carefully combine memory usage and
+ * temporary file storage in ways which will not significantly impact runtime
+ * performance while allowing the largest possible archives to be handled.
+ * Trying to force the fit to the POSIX database routines was not considered
+ * time well spent.
+ */
+
+static HRDLNK **ltab = NULL; /* hard link table for detecting hard links */
+static FTM **ftab = NULL; /* file time table for updating arch */
+static NAMT **ntab = NULL; /* interactive rename storage table */
+static DEVT **dtab = NULL; /* device/inode mapping tables */
+static ATDIR **atab = NULL; /* file tree directory time reset table */
+#ifdef DIRS_USE_FILE
+static int dirfd = -1; /* storage for setting created dir time/mode */
+static u_long dircnt; /* entries in dir time/mode storage */
+#endif
+static int ffd = -1; /* tmp file for file time table name storage */
+
+static DEVT *chk_dev(dev_t, int);
+
+/*
+ * hard link table routines
+ *
+ * The hard link table tries to detect hard links to files using the device and
+ * inode values. We do this when writing an archive, so we can tell the format
+ * write routine that this file is a hard link to another file. The format
+ * write routine then can store this file in whatever way it wants (as a hard
+ * link if the format supports that like tar, or ignore this info like cpio).
+ * (Actually a field in the format driver table tells us if the format wants
+ * hard link info. if not, we do not waste time looking for them). We also use
+ * the same table when reading an archive. In that situation, this table is
+ * used by the format read routine to detect hard links from stored dev and
+ * inode numbers (like cpio). This will allow pax to create a link when one
+ * can be detected by the archive format.
+ */
+
+/*
+ * lnk_start
+ * Creates the hard link table.
+ * Return:
+ * 0 if created, -1 if failure
+ */
+
+int
+lnk_start(void)
+{
+ if (ltab != NULL)
+ return 0;
+ if ((ltab = (HRDLNK **)calloc(L_TAB_SZ, sizeof(HRDLNK *))) == NULL) {
+ tty_warn(1, "Cannot allocate memory for hard link table");
+ return -1;
+ }
+ return 0;
+}
+
+/*
+ * chk_lnk()
+ * Looks up entry in hard link hash table. If found, it copies the name
+ * of the file it is linked to (we already saw that file) into ln_name.
+ * lnkcnt is decremented and if goes to 1 the node is deleted from the
+ * database. (We have seen all the links to this file). If not found,
+ * we add the file to the database if it has the potential for having
+ * hard links to other files we may process (it has a link count > 1)
+ * Return:
+ * if found returns 1; if not found returns 0; -1 on error
+ */
+
+int
+chk_lnk(ARCHD *arcn)
+{
+ HRDLNK *pt;
+ HRDLNK **ppt;
+ u_int indx;
+
+ if (ltab == NULL)
+ return -1;
+ /*
+ * ignore those nodes that cannot have hard links
+ */
+ if ((arcn->type == PAX_DIR) || (arcn->sb.st_nlink <= 1))
+ return 0;
+
+ /*
+ * hash inode number and look for this file
+ */
+ indx = ((unsigned)arcn->sb.st_ino) % L_TAB_SZ;
+ if ((pt = ltab[indx]) != NULL) {
+ /*
+ * its hash chain is not empty, walk down looking for it
+ */
+ ppt = &(ltab[indx]);
+ while (pt != NULL) {
+ if ((pt->ino == arcn->sb.st_ino) &&
+ (pt->dev == arcn->sb.st_dev))
+ break;
+ ppt = &(pt->fow);
+ pt = pt->fow;
+ }
+
+ if (pt != NULL) {
+ /*
+ * found a link. set the node type and copy in the
+ * name of the file it is to link to. we need to
+ * handle hardlinks to regular files differently than
+ * other links.
+ */
+ arcn->ln_nlen = strlcpy(arcn->ln_name, pt->name,
+ sizeof(arcn->ln_name));
+ if (arcn->type == PAX_REG)
+ arcn->type = PAX_HRG;
+ else
+ arcn->type = PAX_HLK;
+
+ /*
+ * if we have found all the links to this file, remove
+ * it from the database
+ */
+ if (--pt->nlink <= 1) {
+ *ppt = pt->fow;
+ (void)free((char *)pt->name);
+ (void)free((char *)pt);
+ }
+ return 1;
+ }
+ }
+
+ /*
+ * we never saw this file before. It has links so we add it to the
+ * front of this hash chain
+ */
+ if ((pt = (HRDLNK *)malloc(sizeof(HRDLNK))) != NULL) {
+ if ((pt->name = strdup(arcn->name)) != NULL) {
+ pt->dev = arcn->sb.st_dev;
+ pt->ino = arcn->sb.st_ino;
+ pt->nlink = arcn->sb.st_nlink;
+ pt->fow = ltab[indx];
+ ltab[indx] = pt;
+ return 0;
+ }
+ (void)free((char *)pt);
+ }
+
+ tty_warn(1, "Hard link table out of memory");
+ return -1;
+}
+
+/*
+ * purg_lnk
+ * remove reference for a file that we may have added to the data base as
+ * a potential source for hard links. We ended up not using the file, so
+ * we do not want to accidentally point another file at it later on.
+ */
+
+void
+purg_lnk(ARCHD *arcn)
+{
+ HRDLNK *pt;
+ HRDLNK **ppt;
+ u_int indx;
+
+ if (ltab == NULL)
+ return;
+ /*
+ * do not bother to look if it could not be in the database
+ */
+ if ((arcn->sb.st_nlink <= 1) || (arcn->type == PAX_DIR) ||
+ (arcn->type == PAX_HLK) || (arcn->type == PAX_HRG))
+ return;
+
+ /*
+ * find the hash chain for this inode value, if empty return
+ */
+ indx = ((unsigned)arcn->sb.st_ino) % L_TAB_SZ;
+ if ((pt = ltab[indx]) == NULL)
+ return;
+
+ /*
+ * walk down the list looking for the inode/dev pair, unlink and
+ * free if found
+ */
+ ppt = &(ltab[indx]);
+ while (pt != NULL) {
+ if ((pt->ino == arcn->sb.st_ino) &&
+ (pt->dev == arcn->sb.st_dev))
+ break;
+ ppt = &(pt->fow);
+ pt = pt->fow;
+ }
+ if (pt == NULL)
+ return;
+
+ /*
+ * remove and free it
+ */
+ *ppt = pt->fow;
+ (void)free((char *)pt->name);
+ (void)free((char *)pt);
+}
+
+/*
+ * lnk_end()
+ * pull apart a existing link table so we can reuse it. We do this between
+ * read and write phases of append with update. (The format may have
+ * used the link table, and we need to start with a fresh table for the
+ * write phase
+ */
+
+void
+lnk_end(void)
+{
+ int i;
+ HRDLNK *pt;
+ HRDLNK *ppt;
+
+ if (ltab == NULL)
+ return;
+
+ for (i = 0; i < L_TAB_SZ; ++i) {
+ if (ltab[i] == NULL)
+ continue;
+ pt = ltab[i];
+ ltab[i] = NULL;
+
+ /*
+ * free up each entry on this chain
+ */
+ while (pt != NULL) {
+ ppt = pt;
+ pt = ppt->fow;
+ (void)free((char *)ppt->name);
+ (void)free((char *)ppt);
+ }
+ }
+ return;
+}
+
+/*
+ * modification time table routines
+ *
+ * The modification time table keeps track of last modification times for all
+ * files stored in an archive during a write phase when -u is set. We only
+ * add a file to the archive if it is newer than a file with the same name
+ * already stored on the archive (if there is no other file with the same
+ * name on the archive it is added). This applies to writes and appends.
+ * An append with an -u must read the archive and store the modification time
+ * for every file on that archive before starting the write phase. It is clear
+ * that this is one HUGE database. To save memory space, the actual file names
+ * are stored in a scratch file and indexed by an in-memory hash table. The
+ * hash table is indexed by hashing the file path. The nodes in the table store
+ * the length of the filename and the lseek offset within the scratch file
+ * where the actual name is stored. Since there are never any deletions from this
+ * table, fragmentation of the scratch file is never a issue. Lookups seem to
+ * not exhibit any locality at all (files in the database are rarely
+ * looked up more than once...), so caching is just a waste of memory. The
+ * only limitation is the amount of scratch file space available to store the
+ * path names.
+ */
+
+/*
+ * ftime_start()
+ * create the file time hash table and open for read/write the scratch
+ * file. (after created it is unlinked, so when we exit we leave
+ * no witnesses).
+ * Return:
+ * 0 if the table and file was created ok, -1 otherwise
+ */
+
+int
+ftime_start(void)
+{
+ if (ftab != NULL)
+ return 0;
+ if ((ftab = (FTM **)calloc(F_TAB_SZ, sizeof(FTM *))) == NULL) {
+ tty_warn(1, "Cannot allocate memory for file time table");
+ return -1;
+ }
+
+ /*
+ * get random name and create temporary scratch file, unlink name
+ * so it will get removed on exit
+ */
+ memcpy(tempbase, _TFILE_BASE, sizeof(_TFILE_BASE));
+ if ((ffd = mkstemp(tempfile)) == -1) {
+ syswarn(1, errno, "Unable to create temporary file: %s",
+ tempfile);
+ return -1;
+ }
+
+ (void)unlink(tempfile);
+ return 0;
+}
+
+/*
+ * chk_ftime()
+ * looks up entry in file time hash table. If not found, the file is
+ * added to the hash table and the file named stored in the scratch file.
+ * If a file with the same name is found, the file times are compared and
+ * the most recent file time is retained. If the new file was younger (or
+ * was not in the database) the new file is selected for storage.
+ * Return:
+ * 0 if file should be added to the archive, 1 if it should be skipped,
+ * -1 on error
+ */
+
+int
+chk_ftime(ARCHD *arcn)
+{
+ FTM *pt;
+ int namelen;
+ u_int indx;
+ char ckname[PAXPATHLEN+1];
+
+ /*
+ * no info, go ahead and add to archive
+ */
+ if (ftab == NULL)
+ return 0;
+
+ /*
+ * hash the pathname and look up in table
+ */
+ namelen = arcn->nlen;
+ indx = st_hash(arcn->name, namelen, F_TAB_SZ);
+ if ((pt = ftab[indx]) != NULL) {
+ /*
+ * the hash chain is not empty, walk down looking for match
+ * only read up the path names if the lengths match, speeds
+ * up the search a lot
+ */
+ while (pt != NULL) {
+ if (pt->namelen == namelen) {
+ /*
+ * potential match, have to read the name
+ * from the scratch file.
+ */
+ if (lseek(ffd,pt->seek,SEEK_SET) != pt->seek) {
+ syswarn(1, errno,
+ "Failed ftime table seek");
+ return -1;
+ }
+ if (xread(ffd, ckname, namelen) != namelen) {
+ syswarn(1, errno,
+ "Failed ftime table read");
+ return -1;
+ }
+
+ /*
+ * if the names match, we are done
+ */
+ if (!strncmp(ckname, arcn->name, namelen))
+ break;
+ }
+
+ /*
+ * try the next entry on the chain
+ */
+ pt = pt->fow;
+ }
+
+ if (pt != NULL) {
+ /*
+ * found the file, compare the times, save the newer
+ */
+ if (arcn->sb.st_mtime > pt->mtime) {
+ /*
+ * file is newer
+ */
+ pt->mtime = arcn->sb.st_mtime;
+ return 0;
+ }
+ /*
+ * file is older
+ */
+ return 1;
+ }
+ }
+
+ /*
+ * not in table, add it
+ */
+ if ((pt = (FTM *)malloc(sizeof(FTM))) != NULL) {
+ /*
+ * add the name at the end of the scratch file, saving the
+ * offset. add the file to the head of the hash chain
+ */
+ if ((pt->seek = lseek(ffd, (off_t)0, SEEK_END)) >= 0) {
+ if (xwrite(ffd, arcn->name, namelen) == namelen) {
+ pt->mtime = arcn->sb.st_mtime;
+ pt->namelen = namelen;
+ pt->fow = ftab[indx];
+ ftab[indx] = pt;
+ return 0;
+ }
+ syswarn(1, errno, "Failed write to file time table");
+ } else
+ syswarn(1, errno, "Failed seek on file time table");
+ } else
+ tty_warn(1, "File time table ran out of memory");
+
+ if (pt != NULL)
+ (void)free((char *)pt);
+ return -1;
+}
+
+/*
+ * Interactive rename table routines
+ *
+ * The interactive rename table keeps track of the new names that the user
+ * assigns to files from tty input. Since this map is unique for each file
+ * we must store it in case there is a reference to the file later in archive
+ * (a link). Otherwise we will be unable to find the file we know was
+ * extracted. The remapping of these files is stored in a memory based hash
+ * table (it is assumed since input must come from /dev/tty, it is unlikely to
+ * be a very large table).
+ */
+
+/*
+ * name_start()
+ * create the interactive rename table
+ * Return:
+ * 0 if successful, -1 otherwise
+ */
+
+int
+name_start(void)
+{
+ if (ntab != NULL)
+ return 0;
+ if ((ntab = (NAMT **)calloc(N_TAB_SZ, sizeof(NAMT *))) == NULL) {
+ tty_warn(1,
+ "Cannot allocate memory for interactive rename table");
+ return -1;
+ }
+ return 0;
+}
+
+/*
+ * add_name()
+ * add the new name to old name mapping just created by the user.
+ * If an old name mapping is found (there may be duplicate names on an
+ * archive) only the most recent is kept.
+ * Return:
+ * 0 if added, -1 otherwise
+ */
+
+int
+add_name(char *oname, int onamelen, char *nname)
+{
+ NAMT *pt;
+ u_int indx;
+
+ if (ntab == NULL) {
+ /*
+ * should never happen
+ */
+ tty_warn(0, "No interactive rename table, links may fail\n");
+ return 0;
+ }
+
+ /*
+ * look to see if we have already mapped this file, if so we
+ * will update it
+ */
+ indx = st_hash(oname, onamelen, N_TAB_SZ);
+ if ((pt = ntab[indx]) != NULL) {
+ /*
+ * look down the has chain for the file
+ */
+ while ((pt != NULL) && (strcmp(oname, pt->oname) != 0))
+ pt = pt->fow;
+
+ if (pt != NULL) {
+ /*
+ * found an old mapping, replace it with the new one
+ * the user just input (if it is different)
+ */
+ if (strcmp(nname, pt->nname) == 0)
+ return 0;
+
+ (void)free((char *)pt->nname);
+ if ((pt->nname = strdup(nname)) == NULL) {
+ tty_warn(1, "Cannot update rename table");
+ return -1;
+ }
+ return 0;
+ }
+ }
+
+ /*
+ * this is a new mapping, add it to the table
+ */
+ if ((pt = (NAMT *)malloc(sizeof(NAMT))) != NULL) {
+ if ((pt->oname = strdup(oname)) != NULL) {
+ if ((pt->nname = strdup(nname)) != NULL) {
+ pt->fow = ntab[indx];
+ ntab[indx] = pt;
+ return 0;
+ }
+ (void)free((char *)pt->oname);
+ }
+ (void)free((char *)pt);
+ }
+ tty_warn(1, "Interactive rename table out of memory");
+ return -1;
+}
+
+/*
+ * sub_name()
+ * look up a link name to see if it points at a file that has been
+ * remapped by the user. If found, the link is adjusted to contain the
+ * new name (oname is the link to name)
+ */
+
+void
+sub_name(char *oname, int *onamelen, size_t onamesize)
+{
+ NAMT *pt;
+ u_int indx;
+
+ if (ntab == NULL)
+ return;
+ /*
+ * look the name up in the hash table
+ */
+ indx = st_hash(oname, *onamelen, N_TAB_SZ);
+ if ((pt = ntab[indx]) == NULL)
+ return;
+
+ while (pt != NULL) {
+ /*
+ * walk down the hash chain looking for a match
+ */
+ if (strcmp(oname, pt->oname) == 0) {
+ /*
+ * found it, replace it with the new name
+ * and return (we know that oname has enough space)
+ */
+ *onamelen = strlcpy(oname, pt->nname, onamesize);
+ return;
+ }
+ pt = pt->fow;
+ }
+
+ /*
+ * no match, just return
+ */
+ return;
+}
+
+/*
+ * device/inode mapping table routines
+ * (used with formats that store device and inodes fields)
+ *
+ * device/inode mapping tables remap the device field in an archive header. The
+ * device/inode fields are used to determine when files are hard links to each
+ * other. However these values have very little meaning outside of that. This
+ * database is used to solve one of two different problems.
+ *
+ * 1) when files are appended to an archive, while the new files may have hard
+ * links to each other, you cannot determine if they have hard links to any
+ * file already stored on the archive from a prior run of pax. We must assume
+ * that these inode/device pairs are unique only within a SINGLE run of pax
+ * (which adds a set of files to an archive). So we have to make sure the
+ * inode/dev pairs we add each time are always unique. We do this by observing
+ * while the inode field is very dense, the use of the dev field is fairly
+ * sparse. Within each run of pax, we remap any device number of a new archive
+ * member that has a device number used in a prior run and already stored in a
+ * file on the archive. During the read phase of the append, we store the
+ * device numbers used and mark them to not be used by any file during the
+ * write phase. If during write we go to use one of those old device numbers,
+ * we remap it to a new value.
+ *
+ * 2) Often the fields in the archive header used to store these values are
+ * too small to store the entire value. The result is an inode or device value
+ * which can be truncated. This really can foul up an archive. With truncation
+ * we end up creating links between files that are really not links (after
+ * truncation the inodes are the same value). We address that by detecting
+ * truncation and forcing a remap of the device field to split truncated
+ * inodes away from each other. Each truncation creates a pattern of bits that
+ * are removed. We use this pattern of truncated bits to partition the inodes
+ * on a single device to many different devices (each one represented by the
+ * truncated bit pattern). All inodes on the same device that have the same
+ * truncation pattern are mapped to the same new device. Two inodes that
+ * truncate to the same value clearly will always have different truncation
+ * bit patterns, so they will be split from away each other. When we spot
+ * device truncation we remap the device number to a non truncated value.
+ * (for more info see table.h for the data structures involved).
+ */
+
+/*
+ * dev_start()
+ * create the device mapping table
+ * Return:
+ * 0 if successful, -1 otherwise
+ */
+
+int
+dev_start(void)
+{
+ if (dtab != NULL)
+ return 0;
+ if ((dtab = (DEVT **)calloc(D_TAB_SZ, sizeof(DEVT *))) == NULL) {
+ tty_warn(1, "Cannot allocate memory for device mapping table");
+ return -1;
+ }
+ return 0;
+}
+
+/*
+ * add_dev()
+ * add a device number to the table. this will force the device to be
+ * remapped to a new value if it be used during a write phase. This
+ * function is called during the read phase of an append to prohibit the
+ * use of any device number already in the archive.
+ * Return:
+ * 0 if added ok, -1 otherwise
+ */
+
+int
+add_dev(ARCHD *arcn)
+{
+ if (chk_dev(arcn->sb.st_dev, 1) == NULL)
+ return -1;
+ return 0;
+}
+
+/*
+ * chk_dev()
+ * check for a device value in the device table. If not found and the add
+ * flag is set, it is added. This does NOT assign any mapping values, just
+ * adds the device number as one that need to be remapped. If this device
+ * is already mapped, just return with a pointer to that entry.
+ * Return:
+ * pointer to the entry for this device in the device map table. Null
+ * if the add flag is not set and the device is not in the table (it is
+ * not been seen yet). If add is set and the device cannot be added, null
+ * is returned (indicates an error).
+ */
+
+static DEVT *
+chk_dev(dev_t dev, int add)
+{
+ DEVT *pt;
+ u_int indx;
+
+ if (dtab == NULL)
+ return NULL;
+ /*
+ * look to see if this device is already in the table
+ */
+ indx = ((unsigned)dev) % D_TAB_SZ;
+ if ((pt = dtab[indx]) != NULL) {
+ while ((pt != NULL) && (pt->dev != dev))
+ pt = pt->fow;
+
+ /*
+ * found it, return a pointer to it
+ */
+ if (pt != NULL)
+ return pt;
+ }
+
+ /*
+ * not in table, we add it only if told to as this may just be a check
+ * to see if a device number is being used.
+ */
+ if (add == 0)
+ return NULL;
+
+ /*
+ * allocate a node for this device and add it to the front of the hash
+ * chain. Note we do not assign remaps values here, so the pt->list
+ * list must be NULL.
+ */
+ if ((pt = (DEVT *)malloc(sizeof(DEVT))) == NULL) {
+ tty_warn(1, "Device map table out of memory");
+ return NULL;
+ }
+ pt->dev = dev;
+ pt->list = NULL;
+ pt->fow = dtab[indx];
+ dtab[indx] = pt;
+ return pt;
+}
+/*
+ * map_dev()
+ * given an inode and device storage mask (the mask has a 1 for each bit
+ * the archive format is able to store in a header), we check for inode
+ * and device truncation and remap the device as required. Device mapping
+ * can also occur when during the read phase of append a device number was
+ * seen (and was marked as do not use during the write phase). WE ASSUME
+ * that unsigned longs are the same size or bigger than the fields used
+ * for ino_t and dev_t. If not the types will have to be changed.
+ * Return:
+ * 0 if all ok, -1 otherwise.
+ */
+
+int
+map_dev(ARCHD *arcn, u_long dev_mask, u_long ino_mask)
+{
+ DEVT *pt;
+ DLIST *dpt;
+ static dev_t lastdev = 0; /* next device number to try */
+ int trc_ino = 0;
+ int trc_dev = 0;
+ ino_t trunc_bits = 0;
+ ino_t nino;
+
+ if (dtab == NULL)
+ return 0;
+ /*
+ * check for device and inode truncation, and extract the truncated
+ * bit pattern.
+ */
+ if ((arcn->sb.st_dev & (dev_t)dev_mask) != arcn->sb.st_dev)
+ ++trc_dev;
+ if ((nino = arcn->sb.st_ino & (ino_t)ino_mask) != arcn->sb.st_ino) {
+ ++trc_ino;
+ trunc_bits = arcn->sb.st_ino & (ino_t)(~ino_mask);
+ }
+
+ /*
+ * see if this device is already being mapped, look up the device
+ * then find the truncation bit pattern which applies
+ */
+ if ((pt = chk_dev(arcn->sb.st_dev, 0)) != NULL) {
+ /*
+ * this device is already marked to be remapped
+ */
+ for (dpt = pt->list; dpt != NULL; dpt = dpt->fow)
+ if (dpt->trunc_bits == trunc_bits)
+ break;
+
+ if (dpt != NULL) {
+ /*
+ * we are being remapped for this device and pattern
+ * change the device number to be stored and return
+ */
+ arcn->sb.st_dev = dpt->dev;
+ arcn->sb.st_ino = nino;
+ return 0;
+ }
+ } else {
+ /*
+ * this device is not being remapped YET. if we do not have any
+ * form of truncation, we do not need a remap
+ */
+ if (!trc_ino && !trc_dev)
+ return 0;
+
+ /*
+ * we have truncation, have to add this as a device to remap
+ */
+ if ((pt = chk_dev(arcn->sb.st_dev, 1)) == NULL)
+ goto bad;
+
+ /*
+ * if we just have a truncated inode, we have to make sure that
+ * all future inodes that do not truncate (they have the
+ * truncation pattern of all 0's) continue to map to the same
+ * device number. We probably have already written inodes with
+ * this device number to the archive with the truncation
+ * pattern of all 0's. So we add the mapping for all 0's to the
+ * same device number.
+ */
+ if (!trc_dev && (trunc_bits != 0)) {
+ if ((dpt = (DLIST *)malloc(sizeof(DLIST))) == NULL)
+ goto bad;
+ dpt->trunc_bits = 0;
+ dpt->dev = arcn->sb.st_dev;
+ dpt->fow = pt->list;
+ pt->list = dpt;
+ }
+ }
+
+ /*
+ * look for a device number not being used. We must watch for wrap
+ * around on lastdev (so we do not get stuck looking forever!)
+ */
+ while (++lastdev > 0) {
+ if (chk_dev(lastdev, 0) != NULL)
+ continue;
+ /*
+ * found an unused value. If we have reached truncation point
+ * for this format we are hosed, so we give up. Otherwise we
+ * mark it as being used.
+ */
+ if (((lastdev & ((dev_t)dev_mask)) != lastdev) ||
+ (chk_dev(lastdev, 1) == NULL))
+ goto bad;
+ break;
+ }
+
+ if ((lastdev <= 0) || ((dpt = (DLIST *)malloc(sizeof(DLIST))) == NULL))
+ goto bad;
+
+ /*
+ * got a new device number, store it under this truncation pattern.
+ * change the device number this file is being stored with.
+ */
+ dpt->trunc_bits = trunc_bits;
+ dpt->dev = lastdev;
+ dpt->fow = pt->list;
+ pt->list = dpt;
+ arcn->sb.st_dev = lastdev;
+ arcn->sb.st_ino = nino;
+ return 0;
+
+ bad:
+ tty_warn(1,
+ "Unable to fix truncated inode/device field when storing %s",
+ arcn->name);
+ tty_warn(0, "Archive may create improper hard links when extracted");
+ return 0;
+}
+
+/*
+ * directory access/mod time reset table routines (for directories READ by pax)
+ *
+ * The pax -t flag requires that access times of archive files to be the same
+ * as before being read by pax. For regular files, access time is restored after
+ * the file has been copied. This database provides the same functionality for
+ * directories read during file tree traversal. Restoring directory access time
+ * is more complex than files since directories may be read several times until
+ * all the descendants in their subtree are visited by fts. Directory access
+ * and modification times are stored during the fts pre-order visit (done
+ * before any descendants in the subtree is visited) and restored after the
+ * fts post-order visit (after all the descendants have been visited). In the
+ * case of premature exit from a subtree (like from the effects of -n), any
+ * directory entries left in this database are reset during final cleanup
+ * operations of pax. Entries are hashed by inode number for fast lookup.
+ */
+
+/*
+ * atdir_start()
+ * create the directory access time database for directories READ by pax.
+ * Return:
+ * 0 is created ok, -1 otherwise.
+ */
+
+int
+atdir_start(void)
+{
+ if (atab != NULL)
+ return 0;
+ if ((atab = (ATDIR **)calloc(A_TAB_SZ, sizeof(ATDIR *))) == NULL) {
+ tty_warn(1,
+ "Cannot allocate space for directory access time table");
+ return -1;
+ }
+ return 0;
+}
+
+
+/*
+ * atdir_end()
+ * walk through the directory access time table and reset the access time
+ * of any directory who still has an entry left in the database. These
+ * entries are for directories READ by pax
+ */
+
+void
+atdir_end(void)
+{
+ ATDIR *pt;
+ int i;
+
+ if (atab == NULL)
+ return;
+ /*
+ * for each non-empty hash table entry reset all the directories
+ * chained there.
+ */
+ for (i = 0; i < A_TAB_SZ; ++i) {
+ if ((pt = atab[i]) == NULL)
+ continue;
+ /*
+ * remember to force the times, set_ftime() looks at pmtime
+ * and patime, which only applies to things CREATED by pax,
+ * not read by pax. Read time reset is controlled by -t.
+ */
+ for (; pt != NULL; pt = pt->fow)
+ set_ftime(pt->name, pt->mtime, pt->atime, 1, 0);
+ }
+}
+
+/*
+ * add_atdir()
+ * add a directory to the directory access time table. Table is hashed
+ * and chained by inode number. This is for directories READ by pax
+ */
+
+void
+add_atdir(char *fname, dev_t dev, ino_t ino, time_t mtime, time_t atime)
+{
+ ATDIR *pt;
+ u_int indx;
+
+ if (atab == NULL)
+ return;
+
+ /*
+ * make sure this directory is not already in the table, if so just
+ * return (the older entry always has the correct time). The only
+ * way this will happen is when the same subtree can be traversed by
+ * different args to pax and the -n option is aborting fts out of a
+ * subtree before all the post-order visits have been made.
+ */
+ indx = ((unsigned)ino) % A_TAB_SZ;
+ if ((pt = atab[indx]) != NULL) {
+ while (pt != NULL) {
+ if ((pt->ino == ino) && (pt->dev == dev))
+ break;
+ pt = pt->fow;
+ }
+
+ /*
+ * oops, already there. Leave it alone.
+ */
+ if (pt != NULL)
+ return;
+ }
+
+ /*
+ * add it to the front of the hash chain
+ */
+ if ((pt = (ATDIR *)malloc(sizeof(ATDIR))) != NULL) {
+ if ((pt->name = strdup(fname)) != NULL) {
+ pt->dev = dev;
+ pt->ino = ino;
+ pt->mtime = mtime;
+ pt->atime = atime;
+ pt->fow = atab[indx];
+ atab[indx] = pt;
+ return;
+ }
+ (void)free((char *)pt);
+ }
+
+ tty_warn(1, "Directory access time reset table ran out of memory");
+ return;
+}
+
+/*
+ * get_atdir()
+ * look up a directory by inode and device number to obtain the access
+ * and modification time you want to set to. If found, the modification
+ * and access time parameters are set and the entry is removed from the
+ * table (as it is no longer needed). These are for directories READ by
+ * pax
+ * Return:
+ * 0 if found, -1 if not found.
+ */
+
+int
+get_atdir(dev_t dev, ino_t ino, time_t *mtime, time_t *atime)
+{
+ ATDIR *pt;
+ ATDIR **ppt;
+ u_int indx;
+
+ if (atab == NULL)
+ return -1;
+ /*
+ * hash by inode and search the chain for an inode and device match
+ */
+ indx = ((unsigned)ino) % A_TAB_SZ;
+ if ((pt = atab[indx]) == NULL)
+ return -1;
+
+ ppt = &(atab[indx]);
+ while (pt != NULL) {
+ if ((pt->ino == ino) && (pt->dev == dev))
+ break;
+ /*
+ * no match, go to next one
+ */
+ ppt = &(pt->fow);
+ pt = pt->fow;
+ }
+
+ /*
+ * return if we did not find it.
+ */
+ if (pt == NULL)
+ return -1;
+
+ /*
+ * found it. return the times and remove the entry from the table.
+ */
+ *ppt = pt->fow;
+ *mtime = pt->mtime;
+ *atime = pt->atime;
+ (void)free((char *)pt->name);
+ (void)free((char *)pt);
+ return 0;
+}
+
+/*
+ * directory access mode and time storage routines (for directories CREATED
+ * by pax).
+ *
+ * Pax requires that extracted directories, by default, have their access/mod
+ * times and permissions set to the values specified in the archive. During the
+ * actions of extracting (and creating the destination subtree during -rw copy)
+ * directories extracted may be modified after being created. Even worse is
+ * that these directories may have been created with file permissions which
+ * prohibits any descendants of these directories from being extracted. When
+ * directories are created by pax, access rights may be added to permit the
+ * creation of files in their subtree. Every time pax creates a directory, the
+ * times and file permissions specified by the archive are stored. After all
+ * files have been extracted (or copied), these directories have their times
+ * and file modes reset to the stored values. The directory info is restored in
+ * reverse order as entries were added to the data file from root to leaf. To
+ * restore atime properly, we must go backwards. The data file consists of
+ * records with two parts, the file name followed by a DIRDATA trailer. The
+ * fixed sized trailer contains the size of the name plus the off_t location in
+ * the file. To restore we work backwards through the file reading the trailer
+ * then the file name.
+ */
+
+#ifndef DIRS_USE_FILE
+static DIRDATA *dirdata_head;
+#endif
+
+/*
+ * dir_start()
+ * set up the directory time and file mode storage for directories CREATED
+ * by pax.
+ * Return:
+ * 0 if ok, -1 otherwise
+ */
+
+int
+dir_start(void)
+{
+#ifdef DIRS_USE_FILE
+ if (dirfd != -1)
+ return 0;
+
+ /*
+ * unlink the file so it goes away at termination by itself
+ */
+ memcpy(tempbase, _TFILE_BASE, sizeof(_TFILE_BASE));
+ if ((dirfd = mkstemp(tempfile)) >= 0) {
+ (void)unlink(tempfile);
+ return 0;
+ }
+ tty_warn(1, "Unable to create temporary file for directory times: %s",
+ tempfile);
+ return -1;
+#else
+ return (0);
+#endif /* DIRS_USE_FILE */
+}
+
+/*
+ * add_dir()
+ * add the mode and times for a newly CREATED directory
+ * name is name of the directory, psb the stat buffer with the data in it,
+ * frc_mode is a flag that says whether to force the setting of the mode
+ * (ignoring the user set values for preserving file mode). Frc_mode is
+ * for the case where we created a file and found that the resulting
+ * directory was not writable and the user asked for file modes to NOT
+ * be preserved. (we have to preserve what was created by default, so we
+ * have to force the setting at the end. this is stated explicitly in the
+ * pax spec)
+ */
+
+void
+add_dir(char *name, int nlen, struct stat *psb, int frc_mode)
+{
+#ifdef DIRS_USE_FILE
+ DIRDATA dblk;
+#else
+ DIRDATA *dblk;
+#endif
+ char realname[MAXPATHLEN], *rp;
+
+ if (havechd && *name != '/') {
+ if ((rp = realpath(name, realname)) == NULL) {
+ tty_warn(1, "Cannot canonicalize %s", name);
+ return;
+ }
+ name = rp;
+#ifdef DIRS_USE_FILE
+ nlen = strlen(name);
+#endif
+ }
+
+#ifdef DIRS_USE_FILE
+ if (dirfd < 0)
+ return;
+
+ /*
+ * get current position (where file name will start) so we can store it
+ * in the trailer
+ */
+ if ((dblk.npos = lseek(dirfd, 0L, SEEK_CUR)) < 0) {
+ tty_warn(1,
+ "Unable to store mode and times for directory: %s",name);
+ return;
+ }
+
+ /*
+ * write the file name followed by the trailer
+ */
+ dblk.nlen = nlen + 1;
+ dblk.mode = psb->st_mode & 0xffff;
+ dblk.mtime = psb->st_mtime;
+ dblk.atime = psb->st_atime;
+#if HAVE_STRUCT_STAT_ST_FLAGS
+ dblk.fflags = psb->st_flags;
+#else
+ dblk.fflags = 0;
+#endif
+ dblk.frc_mode = frc_mode;
+ if ((xwrite(dirfd, name, dblk.nlen) == dblk.nlen) &&
+ (xwrite(dirfd, (char *)&dblk, sizeof(dblk)) == sizeof(dblk))) {
+ ++dircnt;
+ return;
+ }
+
+ tty_warn(1,
+ "Unable to store mode and times for created directory: %s",name);
+ return;
+#else
+
+ if ((dblk = malloc(sizeof(*dblk))) == NULL ||
+ (dblk->name = strdup(name)) == NULL) {
+ tty_warn(1,
+ "Unable to store mode and times for directory: %s",name);
+ if (dblk != NULL)
+ free(dblk);
+ return;
+ }
+
+ dblk->mode = psb->st_mode & 0xffff;
+ dblk->mtime = psb->st_mtime;
+ dblk->atime = psb->st_atime;
+#if HAVE_STRUCT_STAT_ST_FLAGS
+ dblk->fflags = psb->st_flags;
+#else
+ dblk->fflags = 0;
+#endif
+ dblk->frc_mode = frc_mode;
+
+ dblk->next = dirdata_head;
+ dirdata_head = dblk;
+ return;
+#endif /* DIRS_USE_FILE */
+}
+
+/*
+ * proc_dir()
+ * process all file modes and times stored for directories CREATED
+ * by pax
+ */
+
+void
+proc_dir(void)
+{
+#ifdef DIRS_USE_FILE
+ char name[PAXPATHLEN+1];
+ DIRDATA dblk;
+ u_long cnt;
+
+ if (dirfd < 0)
+ return;
+ /*
+ * read backwards through the file and process each directory
+ */
+ for (cnt = 0; cnt < dircnt; ++cnt) {
+ /*
+ * read the trailer, then the file name, if this fails
+ * just give up.
+ */
+ if (lseek(dirfd, -((off_t)sizeof(dblk)), SEEK_CUR) < 0)
+ break;
+ if (xread(dirfd,(char *)&dblk, sizeof(dblk)) != sizeof(dblk))
+ break;
+ if (lseek(dirfd, dblk.npos, SEEK_SET) < 0)
+ break;
+ if (xread(dirfd, name, dblk.nlen) != dblk.nlen)
+ break;
+ if (lseek(dirfd, dblk.npos, SEEK_SET) < 0)
+ break;
+
+ /*
+ * frc_mode set, make sure we set the file modes even if
+ * the user didn't ask for it (see file_subs.c for more info)
+ */
+ if (pmode || dblk.frc_mode)
+ set_pmode(name, dblk.mode);
+ if (patime || pmtime)
+ set_ftime(name, dblk.mtime, dblk.atime, 0, 0);
+ if (pfflags)
+ set_chflags(name, dblk.fflags);
+ }
+
+ (void)close(dirfd);
+ dirfd = -1;
+ if (cnt != dircnt)
+ tty_warn(1,
+ "Unable to set mode and times for created directories");
+ return;
+#else
+ DIRDATA *dblk;
+
+ for (dblk = dirdata_head; dblk != NULL; dblk = dirdata_head) {
+ dirdata_head = dblk->next;
+
+ /*
+ * frc_mode set, make sure we set the file modes even if
+ * the user didn't ask for it (see file_subs.c for more info)
+ */
+ if (pmode || dblk->frc_mode)
+ set_pmode(dblk->name, dblk->mode);
+ if (patime || pmtime)
+ set_ftime(dblk->name, dblk->mtime, dblk->atime, 0, 0);
+ if (pfflags)
+ set_chflags(dblk->name, dblk->fflags);
+
+ free(dblk->name);
+ free(dblk);
+ }
+#endif /* DIRS_USE_FILE */
+}
+
+/*
+ * database independent routines
+ */
+
+/*
+ * st_hash()
+ * hashes filenames to a u_int for hashing into a table. Looks at the tail
+ * end of file, as this provides far better distribution than any other
+ * part of the name. For performance reasons we only care about the last
+ * MAXKEYLEN chars (should be at LEAST large enough to pick off the file
+ * name). Was tested on 500,000 name file tree traversal from the root
+ * and gave almost a perfectly uniform distribution of keys when used with
+ * prime sized tables (MAXKEYLEN was 128 in test). Hashes (sizeof int)
+ * chars at a time and pads with 0 for last addition.
+ * Return:
+ * the hash value of the string MOD (%) the table size.
+ */
+
+u_int
+st_hash(char *name, int len, int tabsz)
+{
+ char *pt;
+ char *dest;
+ char *end;
+ int i;
+ u_int key = 0;
+ int steps;
+ int res;
+ u_int val;
+
+ /*
+ * only look at the tail up to MAXKEYLEN, we do not need to waste
+ * time here (remember these are pathnames, the tail is what will
+ * spread out the keys)
+ */
+ if (len > MAXKEYLEN) {
+ pt = &(name[len - MAXKEYLEN]);
+ len = MAXKEYLEN;
+ } else
+ pt = name;
+
+ /*
+ * calculate the number of u_int size steps in the string and if
+ * there is a runt to deal with
+ */
+ steps = len/sizeof(u_int);
+ res = len % sizeof(u_int);
+
+ /*
+ * add up the value of the string in unsigned integer sized pieces
+ * too bad we cannot have unsigned int aligned strings, then we
+ * could avoid the expensive copy.
+ */
+ for (i = 0; i < steps; ++i) {
+ end = pt + sizeof(u_int);
+ dest = (char *)&val;
+ while (pt < end)
+ *dest++ = *pt++;
+ key += val;
+ }
+
+ /*
+ * add in the runt padded with zero to the right
+ */
+ if (res) {
+ val = 0;
+ end = pt + res;
+ dest = (char *)&val;
+ while (pt < end)
+ *dest++ = *pt++;
+ key += val;
+ }
+
+ /*
+ * return the result mod the table size
+ */
+ return key % tabsz;
+}
diff --git a/bin/pax/tables.h b/bin/pax/tables.h
new file mode 100644
index 0000000..1038589
--- /dev/null
+++ b/bin/pax/tables.h
@@ -0,0 +1,176 @@
+/* $NetBSD: tables.h,v 1.10 2007/04/29 20:23:34 msaitoh Exp $ */
+
+/*-
+ * Copyright (c) 1992 Keith Muller.
+ * Copyright (c) 1992, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Keith Muller of the University of California, San Diego.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * @(#)tables.h 8.1 (Berkeley) 5/31/93
+ */
+
+/*
+ * data structures and constants used by the different databases kept by pax
+ */
+
+/*
+ * Hash Table Sizes MUST BE PRIME, if set too small performance suffers.
+ * Probably safe to expect 500000 inodes per tape. Assuming good key
+ * distribution (inodes) chains of under 50 long (worse case) is ok.
+ */
+#define L_TAB_SZ 2503 /* hard link hash table size */
+#define F_TAB_SZ 50503 /* file time hash table size */
+#define N_TAB_SZ 541 /* interactive rename hash table */
+#define D_TAB_SZ 317 /* unique device mapping table */
+#define A_TAB_SZ 317 /* ftree dir access time reset table */
+#define MAXKEYLEN 64 /* max number of chars for hash */
+
+/*
+ * file hard link structure (hashed by dev/ino and chained) used to find the
+ * hard links in a file system or with some archive formats (cpio)
+ */
+typedef struct hrdlnk {
+ char *name; /* name of first file seen with this ino/dev */
+ dev_t dev; /* files device number */
+ ino_t ino; /* files inode number */
+ u_long nlink; /* expected link count */
+ struct hrdlnk *fow;
+} HRDLNK;
+
+/*
+ * Archive write update file time table (the -u, -C flag), hashed by filename.
+ * Filenames are stored in a scratch file at seek offset into the file. The
+ * file time (mod time) and the file name length (for a quick check) are
+ * stored in a hash table node. We were forced to use a scratch file because
+ * with -u, the mtime for every node in the archive must always be available
+ * to compare against (and this data can get REALLY large with big archives).
+ * By being careful to read only when we have a good chance of a match, the
+ * performance loss is not measurable (and the size of the archive we can
+ * handle is greatly increased).
+ */
+typedef struct ftm {
+ int namelen; /* file name length */
+ time_t mtime; /* files last modification time */
+ off_t seek; /* location in scratch file */
+ struct ftm *fow;
+} FTM;
+
+/*
+ * Interactive rename table (-i flag), hashed by orig filename.
+ * We assume this will not be a large table as this mapping data can only be
+ * obtained through interactive input by the user. Nobody is going to type in
+ * changes for 500000 files? We use chaining to resolve collisions.
+ */
+
+typedef struct namt {
+ char *oname; /* old name */
+ char *nname; /* new name typed in by the user */
+ struct namt *fow;
+} NAMT;
+
+/*
+ * Unique device mapping tables. Some protocols (e.g. cpio) require that the
+ * <c_dev,c_ino> pair will uniquely identify a file in an archive unless they
+ * are links to the same file. Appending to archives can break this. For those
+ * protocols that have this requirement we map c_dev to a unique value not seen
+ * in the archive when we append. We also try to handle inode truncation with
+ * this table. (When the inode field in the archive header are too small, we
+ * remap the dev on writes to remove accidental collisions).
+ *
+ * The list is hashed by device number using chain collision resolution. Off of
+ * each DEVT are linked the various remaps for this device based on those bits
+ * in the inode which were truncated. For example if we are just remapping to
+ * avoid a device number during an update append, off the DEVT we would have
+ * only a single DLIST that has a truncation id of 0 (no inode bits were
+ * stripped for this device so far). When we spot inode truncation we create
+ * a new mapping based on the set of bits in the inode which were stripped off.
+ * so if the top four bits of the inode are stripped and they have a pattern of
+ * 0110...... (where . are those bits not truncated) we would have a mapping
+ * assigned for all inodes that has the same 0110.... pattern (with this dev
+ * number of course). This keeps the mapping sparse and should be able to store
+ * close to the limit of files which can be represented by the optimal
+ * combination of dev and inode bits, and without creating a fouled up archive.
+ * Note we also remap truncated devs in the same way (an exercise for the
+ * dedicated reader; always wanted to say that...:)
+ */
+
+typedef struct devt {
+ dev_t dev; /* the orig device number we now have to map */
+ struct devt *fow; /* new device map list */
+ struct dlist *list; /* map list based on inode truncation bits */
+} DEVT;
+
+typedef struct dlist {
+ ino_t trunc_bits; /* truncation pattern for a specific map */
+ dev_t dev; /* the new device id we use */
+ struct dlist *fow;
+} DLIST;
+
+/*
+ * ftree directory access time reset table. When we are done with a
+ * subtree we reset the access and mod time of the directory when the tflag is
+ * set. Not really explicitly specified in the pax spec, but easy and fast to
+ * do (and this may have even been intended in the spec, it is not clear).
+ * table is hashed by inode with chaining.
+ */
+
+typedef struct atdir {
+ char *name; /* name of directory to reset */
+ dev_t dev; /* dev and inode for fast lookup */
+ ino_t ino;
+ time_t mtime; /* access and mod time to reset to */
+ time_t atime;
+ struct atdir *fow;
+} ATDIR;
+
+/*
+ * created directory time and mode storage entry. After pax is finished during
+ * extraction or copy, we must reset directory access modes and times that
+ * may have been modified after creation (they no longer have the specified
+ * times and/or modes). We must reset time in the reverse order of creation,
+ * because entries are added from the top of the file tree to the bottom.
+ * We MUST reset times from leaf to root (it will not work the other
+ * direction). Entries are recorded into a spool file to make reverse
+ * reading faster.
+ */
+
+typedef struct dirdata {
+#ifdef DIRS_USE_FILE
+ int nlen; /* length of the directory name (includes \0) */
+ off_t npos; /* position in file where this dir name starts */
+#else
+ char *name; /* file name */
+ struct dirdata *next;
+#endif
+ mode_t mode; /* file mode to restore */
+ time_t mtime; /* mtime to set */
+ time_t atime; /* atime to set */
+ long fflags; /* file flags to set */
+ int frc_mode; /* do we force mode settings? */
+} DIRDATA;
diff --git a/bin/pax/tar.1 b/bin/pax/tar.1
new file mode 100644
index 0000000..f98a138
--- /dev/null
+++ b/bin/pax/tar.1
@@ -0,0 +1,372 @@
+.\" $NetBSD: tar.1,v 1.37 2017/07/03 21:33:23 wiz Exp $
+.\"
+.\" Copyright (c) 1996 SigmaSoft, Th. Lockert
+.\" All rights reserved.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\" notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must reproduce the above copyright
+.\" notice, this list of conditions and the following disclaimer in the
+.\" documentation and/or other materials provided with the distribution.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+.\" IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+.\" OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+.\" IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+.\" INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+.\" NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+.\" DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+.\" THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+.\" (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+.\" THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+.\"
+.\" OpenBSD: tar.1,v 1.28 2000/11/09 23:58:56 aaron Exp
+.\"
+.Dd December 19, 2015
+.Dt TAR 1
+.Os
+.Sh NAME
+.Nm tar
+.Nd tape archiver
+.Sh SYNOPSIS
+.Nm tar
+.Sm off
+.Oo \&- Oc {crtux} Op Fl 014578befHhJjklmOoPpqSvwXZz
+.Sm on
+.Op Ar archive
+.Op Ar blocksize
+.\" XXX how to do this right?
+.Op Fl C Ar directory
+.Op Fl s Ar replstr
+.Op Fl T Ar file
+.Op Ar file ...
+.Sh DESCRIPTION
+The
+.Nm
+command creates, adds files to, or extracts files from an
+archive file in
+.Dq tar
+format.
+A tar archive is often stored on a magnetic tape, but can be
+stored equally well on a floppy, CD-ROM, or in a regular disk file.
+.Pp
+One of the following flags must be present:
+.Bl -tag -width Ar
+.It Fl c , Fl Fl create
+Create new archive, or overwrite an existing archive,
+adding the specified files to it.
+.It Fl r , Fl Fl append
+Append the named new files to existing archive.
+Note that this will only work on media on which an end-of-file mark
+can be overwritten.
+.It Fl t , Fl Fl list
+List contents of archive.
+If any files are named on the
+command line, only those files will be listed.
+.It Fl u , Fl Fl update
+Alias for
+.Fl r .
+.It Fl x , Fl Fl extract , Fl Fl get
+Extract files from archive.
+If any files are named on the
+command line, only those files will be extracted from the
+archive.
+If more than one copy of a file exists in the
+archive, later copies will overwrite earlier copies during
+extraction.
+The file mode and modification time are preserved
+if possible.
+The file mode is subject to modification by the
+.Xr umask 2 .
+.El
+.Pp
+In addition to the flags mentioned above, any of the following
+flags may be used:
+.Bl -tag -width Ar
+.It Fl b Ar "blocking factor" , Fl Fl block-size Ar "blocking factor"
+Set blocking factor to use for the archive.
+.Nm
+uses 512 byte blocks.
+The default is 20, the maximum is 126.
+Archives with a blocking factor larger 63 violate the
+.Tn POSIX
+standard and will not be portable to all systems.
+.It Fl e
+Stop after first error.
+.It Fl f Ar archive , Fl Fl file Ar archive
+Filename where the archive is stored.
+Defaults to
+.Pa /dev/rst0 .
+If the archive is of the form:
+.Ar [[user@]host:]file
+then the archive will be processed using
+.Xr rmt 8 .
+.It Fl h , Fl Fl dereference
+Follow symbolic links as if they were normal files
+or directories.
+.It Fl J, Fl Fl xz
+Compress/decompress archive using
+.Xr xz 1 .
+.It Fl j, Fl Fl bzip2, Fl Fl bunzip2
+Use
+.Xr bzip2 1
+for compression of the archive.
+This option is a GNU extension.
+.It Fl k , Fl Fl keep-old-files
+Keep existing files; don't overwrite them from archive.
+.It Fl l , Fl Fl one-file-system
+Do not descend across mount points.
+.\" should be '-X'
+.It Fl m , Fl Fl modification-time
+Do not preserve modification time.
+.It Fl O
+When creating and appending to an archive, write old-style (non-POSIX) archives.
+When extracting from an archive, extract to standard output.
+.It Fl o , Fl Fl portability , Fl Fl old-archive
+Don't write directory information that the older (V7) style
+.Nm
+is unable to decode.
+This implies the
+.Fl O
+flag.
+.It Fl p , Fl Fl preserve-permissions , Fl Fl preserve
+Preserve user and group ID as well as file mode regardless of
+the current
+.Xr umask 2 .
+The setuid and setgid bits are only preserved if the user is
+the superuser.
+Only meaningful in conjunction with the
+.Fl x
+flag.
+.It Fl q , Fl Fl fast-read
+Select the first archive member that matches each
+.Ar pattern
+operand.
+No more than one archive member is matched for each
+.Ar pattern .
+When members of type directory are matched, the file hierarchy rooted at that
+directory is also matched.
+.It Fl S , Fl Fl sparse
+This flag has no effect as
+.Nm
+always generates sparse files.
+.It Fl s Ar replstr
+Modify the file or archive member names specified by the
+.Ar pattern
+or
+.Ar file
+operands according to the substitution expression
+.Ar replstr ,
+using the syntax of the
+.Xr ed 1
+utility regular expressions.
+The format of these regular expressions are:
+.Dl /old/new/[gps]
+As in
+.Xr ed 1 ,
+.Cm old
+is a basic regular expression and
+.Cm new
+can contain an ampersand (&), \en (where n is a digit) back-references,
+or subexpression matching.
+The
+.Cm old
+string may also contain
+.Aq Dv newline
+characters.
+Any non-null character can be used as a delimiter (/ is shown here).
+Multiple
+.Fl s
+expressions can be specified.
+The expressions are applied in the order they are specified on the
+command line, terminating with the first successful substitution.
+The optional trailing
+.Cm g
+continues to apply the substitution expression to the pathname substring
+which starts with the first character following the end of the last successful
+substitution.
+The first unsuccessful substitution stops the operation of the
+.Cm g
+option.
+The optional trailing
+.Cm p
+will cause the final result of a successful substitution to be written to
+.Dv standard error
+in the following format:
+.Dl <original pathname> >> <new pathname>
+File or archive member names that substitute to the empty string
+are not selected and will be skipped.
+The substitutions are applied by default to the destination hard and symbolic
+links.
+The optional trailing
+.Cm s
+prevents the substitutions from being performed on symbolic link destinations.
+.It Fl v
+Verbose operation mode.
+.It Fl w , Fl Fl interactive , Fl Fl confirmation
+Interactively rename files.
+This option causes
+.Nm
+to prompt the user for the filename to use when storing or
+extracting files in an archive.
+.It Fl z , Fl Fl gzip , Fl Fl gunzip
+Compress/decompress archive using
+.Xr gzip 1 .
+.It Fl B , Fl Fl read-full-blocks
+Reassemble small reads into full blocks (For reading from 4.2BSD pipes).
+.It Fl C Ar directory , Fl Fl directory Ar directory
+This is a positional argument which sets the working directory for the
+following files.
+When extracting, files will be extracted into
+the specified directory; when creating, the specified files will be matched
+from the directory.
+This argument and its parameter may also appear in a file list specified by
+.Fl T .
+.It Fl H
+Only follow symlinks given on command line.
+.Pp
+Note SysVr3/i386 picked up ISC/SCO UNIX compatibility which implemented
+.Dq Fl F Ar file
+which was defined as obtaining a list of command line switches and files
+on which to operate from the specified file,
+but SunOS-5 uses
+.Dq Fl I Ar file
+because they use
+.Sq Fl F
+to mean something else.
+We might someday provide SunOS-5 compatibility
+but it makes little sense to confuse things with ISC/SCO compatibility.
+.\".It Fl L
+.\"Do not follow any symlinks (do the opposite of
+.\".Fl h ).
+.It Fl P , Fl Fl absolute-paths
+Do not strip leading slashes
+.Pq Sq /
+from pathnames.
+The default is to strip leading slashes.
+.It Fl T Ar file , Fl Fl files-from Ar file
+Read the names of files to archive or extract from the given file, one
+per line.
+A line may also specify the positional argument
+.Dq Fl C Ar directory .
+.It Fl X Ar file , Fl Fl exclude-from Ar file
+Exclude files matching the shell glob patterns listed in the given file.
+.\" exclude should be '-E' and '-X' should be one-file-system
+.Pp
+Note that it would be more standard to use this option to mean ``do not
+cross filesystem mount points.''
+.It Fl Z , Fl Fl compress , Fl Fl uncompress
+Compress archive using compress.
+.It Fl Fl strict
+Do not enable GNU tar extensions such as long filenames and long link names.
+.It Fl Fl atime-preserve
+Preserve file access times.
+.It Fl Fl chroot
+.Fn chroot
+to the current directory before extracting files.
+Use with
+.Fl x
+and
+.Fl h
+to make absolute symlinks relative to the current directory.
+.It Fl Fl unlink
+Ignored, only accepted for compatibility with other
+.Nm
+implementations.
+.Nm
+always unlinks files before creating them.
+.It Fl Fl use-compress-program Ar program
+Use the named program as the program to decompress the input.
+.It Fl Fl force-local
+Do not interpret filenames that contain a
+.Sq \&:
+as remote files.
+.It Fl Fl insecure
+Normally
+.Nm
+ignores filenames that contain
+.Dq ..
+as a path component.
+With this option, files that contain
+.Dq ..
+can be processed.
+.It Fl Fl no-recursion
+Cause files of type directory being copied or archived, or archive members of
+type directory being extracted, to match only the directory file or archive
+member and not the file hierarchy rooted at the directory.
+.It Fl Fl timestamp Ar timestamp
+Store all modification times in the archive with the
+.Ar timestamp
+given instead of the actual modification time of the individual archive member
+so that repeatable builds are possible.
+The
+.Ar timestamp
+can be a
+.Pa pathname ,
+where the timestamps are derived from that file, a parseable date for
+.Xr parsedate 3
+(this option is not yet available in the tools build), or an integer value
+interpreted as the number of seconds from the Epoch.
+.El
+.Pp
+The options
+.Op Fl 014578
+can be used to select one of the compiled-in backup devices,
+.Pa /dev/rstN .
+.Sh FILES
+.Bl -tag -width "/dev/rst0"
+.It Pa /dev/rst0
+default archive name
+.El
+.Sh DIAGNOSTICS
+.Nm
+will exit with one of the following values:
+.Bl -tag -width 2n
+.It 0
+All files were processed successfully.
+.It 1
+An error occurred.
+.El
+.Pp
+Whenever
+.Nm
+cannot create a file or a link when extracting an archive or cannot
+find a file while writing an archive, or cannot preserve the user
+ID, group ID, file mode, or access and modification times when the
+.Fl p
+option is specified, a diagnostic message is written to standard
+error and a non-zero exit value will be returned, but processing
+will continue.
+In the case where
+.Nm
+cannot create a link to a file,
+.Nm
+will not create a second copy of the file.
+.Pp
+If the extraction of a file from an archive is prematurely terminated
+by a signal or error,
+.Nm
+may have only partially extracted the file the user wanted.
+Additionally, the file modes of extracted files and directories may
+have incorrect file bits, and the modification and access times may
+be wrong.
+.Pp
+If the creation of an archive is prematurely terminated by a signal
+or error,
+.Nm
+may have only partially created the archive which may violate the
+specific archive format specification.
+.Sh SEE ALSO
+.Xr cpio 1 ,
+.Xr pax 1
+.Sh HISTORY
+A
+.Nm
+command first appeared in
+.At v7 .
+.Sh AUTHORS
+.An Keith Muller
+at the University of California, San Diego.
diff --git a/bin/pax/tar.c b/bin/pax/tar.c
new file mode 100644
index 0000000..ded2ad4
--- /dev/null
+++ b/bin/pax/tar.c
@@ -0,0 +1,1430 @@
+/* $NetBSD: tar.c,v 1.74 2018/11/30 00:53:11 christos Exp $ */
+
+/*-
+ * Copyright (c) 1992 Keith Muller.
+ * Copyright (c) 1992, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Keith Muller of the University of California, San Diego.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#if HAVE_NBTOOL_CONFIG_H
+#include "nbtool_config.h"
+#endif
+
+#include <sys/cdefs.h>
+#if !defined(lint)
+#if 0
+static char sccsid[] = "@(#)tar.c 8.2 (Berkeley) 4/18/94";
+#else
+__RCSID("$NetBSD: tar.c,v 1.74 2018/11/30 00:53:11 christos Exp $");
+#endif
+#endif /* not lint */
+
+#include <sys/types.h>
+#include <sys/time.h>
+#include <sys/stat.h>
+#include <sys/param.h>
+
+#include <ctype.h>
+#include <errno.h>
+#include <grp.h>
+#include <pwd.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "pax.h"
+#include "extern.h"
+#include "tar.h"
+
+extern struct stat tst;
+
+/*
+ * Routines for reading, writing and header identify of various versions of tar
+ */
+
+static int expandname(char *, size_t, char **, size_t *, const char *, size_t);
+static void longlink(ARCHD *, int);
+static uint32_t tar_chksm(char *, int);
+static char *name_split(char *, int);
+static int u32_oct(uintmax_t, char *, int, int);
+static int umax_oct(uintmax_t, char *, int, int);
+static int tar_gnutar_exclude_one(const char *, size_t);
+static int check_sum(char *, size_t, char *, size_t, int);
+
+/*
+ * Routines common to all versions of tar
+ */
+
+static int tar_nodir; /* do not write dirs under old tar */
+int is_gnutar; /* behave like gnu tar; enable gnu
+ * extensions and skip end-of-volume
+ * checks
+ */
+static int seen_gnu_warning; /* Have we warned yet? */
+static char *gnu_hack_string; /* ././@LongLink hackery */
+static int gnu_hack_len; /* len of gnu_hack_string */
+char *gnu_name_string; /* ././@LongLink hackery name */
+char *gnu_link_string; /* ././@LongLink hackery link */
+size_t gnu_name_length; /* ././@LongLink hackery name */
+size_t gnu_link_length; /* ././@LongLink hackery link */
+static int gnu_short_trailer; /* gnu short trailer */
+
+static const char LONG_LINK[] = "././@LongLink";
+
+#ifdef _PAX_
+char DEV_0[] = "/dev/rst0";
+char DEV_1[] = "/dev/rst1";
+char DEV_4[] = "/dev/rst4";
+char DEV_5[] = "/dev/rst5";
+char DEV_7[] = "/dev/rst7";
+char DEV_8[] = "/dev/rst8";
+#endif
+
+static int
+check_sum(char *hd, size_t hdlen, char *bl, size_t bllen, int quiet)
+{
+ uint32_t hdck, blck;
+
+ hdck = asc_u32(hd, hdlen, OCT);
+ blck = tar_chksm(bl, bllen);
+
+ if (hdck != blck) {
+ if (!quiet)
+ tty_warn(0, "Header checksum %o does not match %o",
+ hdck, blck);
+ return -1;
+ }
+ return 0;
+}
+
+
+/*
+ * tar_endwr()
+ * add the tar trailer of two null blocks
+ * Return:
+ * 0 if ok, -1 otherwise (what wr_skip returns)
+ */
+
+int
+tar_endwr(void)
+{
+ return wr_skip((off_t)(NULLCNT * BLKMULT));
+}
+
+/*
+ * tar_endrd()
+ * no cleanup needed here, just return size of trailer (for append)
+ * Return:
+ * size of trailer BLKMULT
+ */
+
+off_t
+tar_endrd(void)
+{
+ return (off_t)((gnu_short_trailer ? 1 : NULLCNT) * BLKMULT);
+}
+
+/*
+ * tar_trail()
+ * Called to determine if a header block is a valid trailer. We are passed
+ * the block, the in_sync flag (which tells us we are in resync mode;
+ * looking for a valid header), and cnt (which starts at zero) which is
+ * used to count the number of empty blocks we have seen so far.
+ * Return:
+ * 0 if a valid trailer, -1 if not a valid trailer, or 1 if the block
+ * could never contain a header.
+ */
+
+int
+tar_trail(char *buf, int in_resync, int *cnt)
+{
+ int i;
+
+ gnu_short_trailer = 0;
+ /*
+ * look for all zero, trailer is two consecutive blocks of zero
+ */
+ for (i = 0; i < BLKMULT; ++i) {
+ if (buf[i] != '\0')
+ break;
+ }
+
+ /*
+ * if not all zero it is not a trailer, but MIGHT be a header.
+ */
+ if (i != BLKMULT)
+ return -1;
+
+ /*
+ * When given a zero block, we must be careful!
+ * If we are not in resync mode, check for the trailer. Have to watch
+ * out that we do not mis-identify file data as the trailer, so we do
+ * NOT try to id a trailer during resync mode. During resync mode we
+ * might as well throw this block out since a valid header can NEVER be
+ * a block of all 0 (we must have a valid file name).
+ */
+ if (!in_resync) {
+ ++*cnt;
+ /*
+ * old GNU tar (up through 1.13) only writes one block of
+ * trailers, so we pretend we got another
+ */
+ if (is_gnutar) {
+ gnu_short_trailer = 1;
+ ++*cnt;
+ }
+ if (*cnt >= NULLCNT)
+ return 0;
+ }
+ return 1;
+}
+
+/*
+ * u32_oct()
+ * convert an uintmax_t to an octal string. many oddball field
+ * termination characters are used by the various versions of tar in the
+ * different fields. term selects which kind to use. str is '0' padded
+ * at the front to len. we are unable to use only one format as many old
+ * tar readers are very cranky about this.
+ * Return:
+ * 0 if the number fit into the string, -1 otherwise
+ */
+
+static int
+u32_oct(uintmax_t val, char *str, int len, int term)
+{
+ char *pt;
+ uint64_t p;
+
+ p = val & TOP_HALF;
+ if (p && p != TOP_HALF)
+ return -1;
+
+ val &= BOTTOM_HALF;
+
+ /*
+ * term selects the appropriate character(s) for the end of the string
+ */
+ pt = str + len - 1;
+ switch(term) {
+ case 3:
+ *pt-- = '\0';
+ break;
+ case 2:
+ *pt-- = ' ';
+ *pt-- = '\0';
+ break;
+ case 1:
+ *pt-- = ' ';
+ break;
+ case 0:
+ default:
+ *pt-- = '\0';
+ *pt-- = ' ';
+ break;
+ }
+
+ /*
+ * convert and blank pad if there is space
+ */
+ while (pt >= str) {
+ *pt-- = '0' + (char)(val & 0x7);
+ if ((val = val >> 3) == 0)
+ break;
+ }
+
+ while (pt >= str)
+ *pt-- = '0';
+ if (val != 0)
+ return -1;
+ return 0;
+}
+
+/*
+ * umax_oct()
+ * convert an unsigned long long to an octal string. one of many oddball
+ * field termination characters are used by the various versions of tar
+ * in the different fields. term selects which kind to use. str is '0'
+ * padded at the front to len. we are unable to use only one format as
+ * many old tar readers are very cranky about this.
+ * Return:
+ * 0 if the number fit into the string, -1 otherwise
+ */
+
+static int
+umax_oct(uintmax_t val, char *str, int len, int term)
+{
+ char *pt;
+
+ /*
+ * term selects the appropriate character(s) for the end of the string
+ */
+ pt = str + len - 1;
+ switch(term) {
+ case 3:
+ *pt-- = '\0';
+ break;
+ case 2:
+ *pt-- = ' ';
+ *pt-- = '\0';
+ break;
+ case 1:
+ *pt-- = ' ';
+ break;
+ case 0:
+ default:
+ *pt-- = '\0';
+ *pt-- = ' ';
+ break;
+ }
+
+ /*
+ * convert and blank pad if there is space
+ */
+ while (pt >= str) {
+ *pt-- = '0' + (char)(val & 0x7);
+ if ((val = val >> 3) == 0)
+ break;
+ }
+
+ while (pt >= str)
+ *pt-- = '0';
+ if (val != 0)
+ return -1;
+ return 0;
+}
+
+/*
+ * tar_chksm()
+ * calculate the checksum for a tar block counting the checksum field as
+ * all blanks (BLNKSUM is that value pre-calculated, the sum of 8 blanks).
+ * NOTE: we use len to short circuit summing 0's on write since we ALWAYS
+ * pad headers with 0.
+ * Return:
+ * unsigned long checksum
+ */
+
+static uint32_t
+tar_chksm(char *blk, int len)
+{
+ char *stop;
+ char *pt;
+ uint32_t chksm = BLNKSUM; /* initial value is checksum field sum */
+
+ /*
+ * add the part of the block before the checksum field
+ */
+ pt = blk;
+ stop = blk + CHK_OFFSET;
+ while (pt < stop)
+ chksm += (uint32_t)(*pt++ & 0xff);
+ /*
+ * move past the checksum field and keep going, spec counts the
+ * checksum field as the sum of 8 blanks (which is pre-computed as
+ * BLNKSUM).
+ * ASSUMED: len is greater than CHK_OFFSET. (len is where our 0 padding
+ * starts, no point in summing zero's)
+ */
+ pt += CHK_LEN;
+ stop = blk + len;
+ while (pt < stop)
+ chksm += (uint32_t)(*pt++ & 0xff);
+ return chksm;
+}
+
+/*
+ * Routines for old BSD style tar (also made portable to sysV tar)
+ */
+
+/*
+ * tar_id()
+ * determine if a block given to us is a valid tar header (and not a USTAR
+ * header). We have to be on the lookout for those pesky blocks of all
+ * zero's.
+ * Return:
+ * 0 if a tar header, -1 otherwise
+ */
+
+int
+tar_id(char *blk, int size)
+{
+ HD_TAR *hd;
+ HD_USTAR *uhd;
+ static int is_ustar = -1;
+
+ if (size < BLKMULT)
+ return -1;
+ hd = (HD_TAR *)blk;
+ uhd = (HD_USTAR *)blk;
+
+ /*
+ * check for block of zero's first, a simple and fast test, then make
+ * sure this is not a ustar header by looking for the ustar magic
+ * cookie. We should use TMAGLEN, but some USTAR archive programs are
+ * wrong and create archives missing the \0. Last we check the
+ * checksum. If this is ok we have to assume it is a valid header.
+ */
+ if (hd->name[0] == '\0')
+ return -1;
+ if (strncmp(uhd->magic, TMAGIC, TMAGLEN - 1) == 0) {
+ if (is_ustar == -1) {
+ is_ustar = 1;
+ return -1;
+ } else
+ tty_warn(0,
+ "Busted tar archive: has both ustar and old tar "
+ "records");
+ } else
+ is_ustar = 0;
+ return check_sum(hd->chksum, sizeof(hd->chksum), blk, BLKMULT, 1);
+}
+
+/*
+ * tar_opt()
+ * handle tar format specific -o options
+ * Return:
+ * 0 if ok -1 otherwise
+ */
+
+int
+tar_opt(void)
+{
+ OPLIST *opt;
+
+ while ((opt = opt_next()) != NULL) {
+ if (strcmp(opt->name, TAR_OPTION) ||
+ strcmp(opt->value, TAR_NODIR)) {
+ tty_warn(1,
+ "Unknown tar format -o option/value pair %s=%s",
+ opt->name, opt->value);
+ tty_warn(1,
+ "%s=%s is the only supported tar format option",
+ TAR_OPTION, TAR_NODIR);
+ return -1;
+ }
+
+ /*
+ * we only support one option, and only when writing
+ */
+ if ((act != APPND) && (act != ARCHIVE)) {
+ tty_warn(1, "%s=%s is only supported when writing.",
+ opt->name, opt->value);
+ return -1;
+ }
+ tar_nodir = 1;
+ }
+ return 0;
+}
+
+
+/*
+ * tar_rd()
+ * extract the values out of block already determined to be a tar header.
+ * store the values in the ARCHD parameter.
+ * Return:
+ * 0
+ */
+
+int
+tar_rd(ARCHD *arcn, char *buf)
+{
+ HD_TAR *hd;
+ char *pt;
+
+ /*
+ * we only get proper sized buffers passed to us
+ */
+ if (tar_id(buf, BLKMULT) < 0)
+ return -1;
+ memset(arcn, 0, sizeof(*arcn));
+ arcn->org_name = arcn->name;
+ arcn->pat = NULL;
+ arcn->sb.st_nlink = 1;
+
+ /*
+ * copy out the name and values in the stat buffer
+ */
+ hd = (HD_TAR *)buf;
+ if (hd->linkflag != LONGLINKTYPE && hd->linkflag != LONGNAMETYPE) {
+ arcn->nlen = expandname(arcn->name, sizeof(arcn->name),
+ &gnu_name_string, &gnu_name_length, hd->name,
+ sizeof(hd->name));
+ arcn->ln_nlen = expandname(arcn->ln_name, sizeof(arcn->ln_name),
+ &gnu_link_string, &gnu_link_length, hd->linkname,
+ sizeof(hd->linkname));
+ }
+ arcn->sb.st_mode = (mode_t)(asc_u32(hd->mode,sizeof(hd->mode),OCT) &
+ 0xfff);
+ arcn->sb.st_uid = (uid_t)asc_u32(hd->uid, sizeof(hd->uid), OCT);
+ arcn->sb.st_gid = (gid_t)asc_u32(hd->gid, sizeof(hd->gid), OCT);
+ arcn->sb.st_size = (off_t)ASC_OFFT(hd->size, sizeof(hd->size), OCT);
+ if (arcn->sb.st_size == -1)
+ return -1;
+ arcn->sb.st_mtime = (time_t)(int32_t)asc_u32(hd->mtime, sizeof(hd->mtime), OCT);
+ arcn->sb.st_ctime = arcn->sb.st_atime = arcn->sb.st_mtime;
+
+ /*
+ * have to look at the last character, it may be a '/' and that is used
+ * to encode this as a directory
+ */
+ pt = &(arcn->name[arcn->nlen - 1]);
+ arcn->pad = 0;
+ arcn->skip = 0;
+ switch(hd->linkflag) {
+ case SYMTYPE:
+ /*
+ * symbolic link, need to get the link name and set the type in
+ * the st_mode so -v printing will look correct.
+ */
+ arcn->type = PAX_SLK;
+ arcn->sb.st_mode |= S_IFLNK;
+ break;
+ case LNKTYPE:
+ /*
+ * hard link, need to get the link name, set the type in the
+ * st_mode and st_nlink so -v printing will look better.
+ */
+ arcn->type = PAX_HLK;
+ arcn->sb.st_nlink = 2;
+
+ /*
+ * no idea of what type this thing really points at, but
+ * we set something for printing only.
+ */
+ arcn->sb.st_mode |= S_IFREG;
+ break;
+ case LONGLINKTYPE:
+ case LONGNAMETYPE:
+ /*
+ * GNU long link/file; we tag these here and let the
+ * pax internals deal with it -- too ugly otherwise.
+ */
+ if (hd->linkflag != LONGLINKTYPE)
+ arcn->type = PAX_GLF;
+ else
+ arcn->type = PAX_GLL;
+ arcn->pad = TAR_PAD(arcn->sb.st_size);
+ arcn->skip = arcn->sb.st_size;
+ break;
+ case AREGTYPE:
+ case REGTYPE:
+ case DIRTYPE: /* see below */
+ default:
+ /*
+ * If we have a trailing / this is a directory and NOT a file.
+ * Note: V7 tar doesn't actually have DIRTYPE, but it was
+ * reported that V7 archives using USTAR directories do exist.
+ */
+ if (*pt == '/' || hd->linkflag == DIRTYPE) {
+ /*
+ * it is a directory, set the mode for -v printing
+ */
+ arcn->type = PAX_DIR;
+ arcn->sb.st_mode |= S_IFDIR;
+ arcn->sb.st_nlink = 2;
+ } else {
+ /*
+ * have a file that will be followed by data. Set the
+ * skip value to the size field and calculate the size
+ * of the padding.
+ */
+ arcn->type = PAX_REG;
+ arcn->sb.st_mode |= S_IFREG;
+ arcn->pad = TAR_PAD(arcn->sb.st_size);
+ arcn->skip = arcn->sb.st_size;
+ }
+ break;
+ }
+
+ /*
+ * strip off any trailing slash.
+ */
+ if (*pt == '/') {
+ *pt = '\0';
+ --arcn->nlen;
+ }
+ return 0;
+}
+
+/*
+ * tar_wr()
+ * write a tar header for the file specified in the ARCHD to the archive.
+ * Have to check for file types that cannot be stored and file names that
+ * are too long. Be careful of the term (last arg) to u32_oct, each field
+ * of tar has it own spec for the termination character(s).
+ * ASSUMED: space after header in header block is zero filled
+ * Return:
+ * 0 if file has data to be written after the header, 1 if file has NO
+ * data to write after the header, -1 if archive write failed
+ */
+
+int
+tar_wr(ARCHD *arcn)
+{
+ HD_TAR *hd;
+ int len;
+ uintmax_t mtime;
+ char hdblk[sizeof(HD_TAR)];
+
+ /*
+ * check for those file system types which tar cannot store
+ */
+ switch(arcn->type) {
+ case PAX_DIR:
+ /*
+ * user asked that dirs not be written to the archive
+ */
+ if (tar_nodir)
+ return 1;
+ break;
+ case PAX_CHR:
+ tty_warn(1, "Tar cannot archive a character device %s",
+ arcn->org_name);
+ return 1;
+ case PAX_BLK:
+ tty_warn(1,
+ "Tar cannot archive a block device %s", arcn->org_name);
+ return 1;
+ case PAX_SCK:
+ tty_warn(1, "Tar cannot archive a socket %s", arcn->org_name);
+ return 1;
+ case PAX_FIF:
+ tty_warn(1, "Tar cannot archive a fifo %s", arcn->org_name);
+ return 1;
+ case PAX_SLK:
+ case PAX_HLK:
+ case PAX_HRG:
+ if (arcn->ln_nlen > (int)sizeof(hd->linkname)) {
+ tty_warn(1,"Link name too long for tar %s",
+ arcn->ln_name);
+ return 1;
+ }
+ break;
+ case PAX_REG:
+ case PAX_CTG:
+ default:
+ break;
+ }
+
+ /*
+ * check file name len, remember extra char for dirs (the / at the end)
+ */
+ len = arcn->nlen;
+ if (arcn->type == PAX_DIR)
+ ++len;
+ if (len >= (int)sizeof(hd->name)) {
+ tty_warn(1, "File name too long for tar %s", arcn->name);
+ return 1;
+ }
+
+ /*
+ * copy the data out of the ARCHD into the tar header based on the type
+ * of the file. Remember many tar readers want the unused fields to be
+ * padded with zero. We set the linkflag field (type), the linkname
+ * (or zero if not used),the size, and set the padding (if any) to be
+ * added after the file data (0 for all other types, as they only have
+ * a header)
+ */
+ memset(hdblk, 0, sizeof(hdblk));
+ hd = (HD_TAR *)hdblk;
+ strlcpy(hd->name, arcn->name, sizeof(hd->name));
+ arcn->pad = 0;
+
+ if (arcn->type == PAX_DIR) {
+ /*
+ * directories are the same as files, except have a filename
+ * that ends with a /, we add the slash here. No data follows,
+ * dirs, so no pad.
+ */
+ hd->linkflag = AREGTYPE;
+ hd->name[len-1] = '/';
+ if (u32_oct((uintmax_t)0L, hd->size, sizeof(hd->size), 1))
+ goto out;
+ } else if (arcn->type == PAX_SLK) {
+ /*
+ * no data follows this file, so no pad
+ */
+ hd->linkflag = SYMTYPE;
+ strlcpy(hd->linkname, arcn->ln_name, sizeof(hd->linkname));
+ if (u32_oct((uintmax_t)0L, hd->size, sizeof(hd->size), 1))
+ goto out;
+ } else if ((arcn->type == PAX_HLK) || (arcn->type == PAX_HRG)) {
+ /*
+ * no data follows this file, so no pad
+ */
+ hd->linkflag = LNKTYPE;
+ strlcpy(hd->linkname, arcn->ln_name, sizeof(hd->linkname));
+ if (u32_oct((uintmax_t)0L, hd->size, sizeof(hd->size), 1))
+ goto out;
+ } else {
+ /*
+ * data follows this file, so set the pad
+ */
+ hd->linkflag = AREGTYPE;
+ if (OFFT_OCT(arcn->sb.st_size, hd->size, sizeof(hd->size), 1)) {
+ tty_warn(1,"File is too large for tar %s",
+ arcn->org_name);
+ return 1;
+ }
+ arcn->pad = TAR_PAD(arcn->sb.st_size);
+ }
+
+ /*
+ * copy those fields that are independent of the type
+ */
+ mtime = tst.st_ino ? tst.st_mtime : arcn->sb.st_mtime;
+ if (u32_oct((uintmax_t)arcn->sb.st_mode, hd->mode, sizeof(hd->mode), 0) ||
+ u32_oct((uintmax_t)arcn->sb.st_uid, hd->uid, sizeof(hd->uid), 0) ||
+ u32_oct((uintmax_t)arcn->sb.st_gid, hd->gid, sizeof(hd->gid), 0) ||
+ u32_oct(mtime, hd->mtime, sizeof(hd->mtime), 1))
+ goto out;
+
+ /*
+ * calculate and add the checksum, then write the header. A return of
+ * 0 tells the caller to now write the file data, 1 says no data needs
+ * to be written
+ */
+ if (u32_oct(tar_chksm(hdblk, sizeof(HD_TAR)), hd->chksum,
+ sizeof(hd->chksum), 3))
+ goto out; /* XXX Something's wrong here
+ * because a zero-byte file can
+ * cause this to be done and
+ * yet the resulting warning
+ * seems incorrect */
+
+ if (wr_rdbuf(hdblk, sizeof(HD_TAR)) < 0)
+ return -1;
+ if (wr_skip((off_t)(BLKMULT - sizeof(HD_TAR))) < 0)
+ return -1;
+ if ((arcn->type == PAX_CTG) || (arcn->type == PAX_REG))
+ return 0;
+ return 1;
+
+ out:
+ /*
+ * header field is out of range
+ */
+ tty_warn(1, "Tar header field is too small for %s", arcn->org_name);
+ return 1;
+}
+
+/*
+ * Routines for POSIX ustar
+ */
+
+/*
+ * ustar_strd()
+ * initialization for ustar read
+ * Return:
+ * 0 if ok, -1 otherwise
+ */
+
+int
+ustar_strd(void)
+{
+ return 0;
+}
+
+/*
+ * ustar_stwr()
+ * initialization for ustar write
+ * Return:
+ * 0 if ok, -1 otherwise
+ */
+
+int
+ustar_stwr(void)
+{
+ return 0;
+}
+
+/*
+ * ustar_id()
+ * determine if a block given to us is a valid ustar header. We have to
+ * be on the lookout for those pesky blocks of all zero's
+ * Return:
+ * 0 if a ustar header, -1 otherwise
+ */
+
+int
+ustar_id(char *blk, int size)
+{
+ HD_USTAR *hd;
+
+ if (size < BLKMULT)
+ return -1;
+ hd = (HD_USTAR *)blk;
+
+ /*
+ * check for block of zero's first, a simple and fast test then check
+ * ustar magic cookie. We should use TMAGLEN, but some USTAR archive
+ * programs are fouled up and create archives missing the \0. Last we
+ * check the checksum. If ok we have to assume it is a valid header.
+ */
+ if (hd->name[0] == '\0')
+ return -1;
+ if (strncmp(hd->magic, TMAGIC, TMAGLEN - 1) != 0)
+ return -1;
+ /* This is GNU tar */
+ if (strncmp(hd->magic, "ustar ", 8) == 0 && !is_gnutar &&
+ !seen_gnu_warning) {
+ seen_gnu_warning = 1;
+ tty_warn(0,
+ "Trying to read GNU tar archive with GNU extensions and end-of-volume checks off");
+ }
+ return check_sum(hd->chksum, sizeof(hd->chksum), blk, BLKMULT, 0);
+}
+
+/*
+ * ustar_rd()
+ * extract the values out of block already determined to be a ustar header.
+ * store the values in the ARCHD parameter.
+ * Return:
+ * 0
+ */
+
+int
+ustar_rd(ARCHD *arcn, char *buf)
+{
+ HD_USTAR *hd;
+ char *dest;
+ int cnt;
+ dev_t devmajor;
+ dev_t devminor;
+
+ /*
+ * we only get proper sized buffers
+ */
+ if (ustar_id(buf, BLKMULT) < 0)
+ return -1;
+
+ memset(arcn, 0, sizeof(*arcn));
+ arcn->org_name = arcn->name;
+ arcn->pat = NULL;
+ arcn->sb.st_nlink = 1;
+ hd = (HD_USTAR *)buf;
+
+ /*
+ * see if the filename is split into two parts. if, so joint the parts.
+ * we copy the prefix first and add a / between the prefix and name.
+ */
+ dest = arcn->name;
+ if (*(hd->prefix) != '\0') {
+ cnt = strlcpy(arcn->name, hd->prefix, sizeof(arcn->name));
+ dest += cnt;
+ *dest++ = '/';
+ cnt++;
+ } else {
+ cnt = 0;
+ }
+
+ if (hd->typeflag != LONGLINKTYPE && hd->typeflag != LONGNAMETYPE) {
+ arcn->nlen = expandname(dest, sizeof(arcn->name) - cnt,
+ &gnu_name_string, &gnu_name_length, hd->name,
+ sizeof(hd->name)) + cnt;
+ arcn->ln_nlen = expandname(arcn->ln_name,
+ sizeof(arcn->ln_name), &gnu_link_string, &gnu_link_length,
+ hd->linkname, sizeof(hd->linkname));
+ }
+
+ /*
+ * follow the spec to the letter. we should only have mode bits, strip
+ * off all other crud we may be passed.
+ */
+ arcn->sb.st_mode = (mode_t)(asc_u32(hd->mode, sizeof(hd->mode), OCT) &
+ 0xfff);
+ arcn->sb.st_size = (off_t)ASC_OFFT(hd->size, sizeof(hd->size), OCT);
+ if (arcn->sb.st_size == -1)
+ return -1;
+ arcn->sb.st_mtime = (time_t)(int32_t)asc_u32(hd->mtime, sizeof(hd->mtime), OCT);
+ arcn->sb.st_ctime = arcn->sb.st_atime = arcn->sb.st_mtime;
+
+ /*
+ * If we can find the ascii names for gname and uname in the password
+ * and group files we will use the uid's and gid they bind. Otherwise
+ * we use the uid and gid values stored in the header. (This is what
+ * the posix spec wants).
+ */
+ hd->gname[sizeof(hd->gname) - 1] = '\0';
+ if (gid_from_group(hd->gname, &(arcn->sb.st_gid)) < 0)
+ arcn->sb.st_gid = (gid_t)asc_u32(hd->gid, sizeof(hd->gid), OCT);
+ hd->uname[sizeof(hd->uname) - 1] = '\0';
+ if (uid_from_user(hd->uname, &(arcn->sb.st_uid)) < 0)
+ arcn->sb.st_uid = (uid_t)asc_u32(hd->uid, sizeof(hd->uid), OCT);
+
+ /*
+ * set the defaults, these may be changed depending on the file type
+ */
+ arcn->pad = 0;
+ arcn->skip = 0;
+ arcn->sb.st_rdev = (dev_t)0;
+
+ /*
+ * set the mode and PAX type according to the typeflag in the header
+ */
+ switch(hd->typeflag) {
+ case FIFOTYPE:
+ arcn->type = PAX_FIF;
+ arcn->sb.st_mode |= S_IFIFO;
+ break;
+ case DIRTYPE:
+ arcn->type = PAX_DIR;
+ arcn->sb.st_mode |= S_IFDIR;
+ arcn->sb.st_nlink = 2;
+
+ /*
+ * Some programs that create ustar archives append a '/'
+ * to the pathname for directories. This clearly violates
+ * ustar specs, but we will silently strip it off anyway.
+ */
+ if (arcn->name[arcn->nlen - 1] == '/')
+ arcn->name[--arcn->nlen] = '\0';
+ break;
+ case BLKTYPE:
+ case CHRTYPE:
+ /*
+ * this type requires the rdev field to be set.
+ */
+ if (hd->typeflag == BLKTYPE) {
+ arcn->type = PAX_BLK;
+ arcn->sb.st_mode |= S_IFBLK;
+ } else {
+ arcn->type = PAX_CHR;
+ arcn->sb.st_mode |= S_IFCHR;
+ }
+ devmajor = (dev_t)asc_u32(hd->devmajor,sizeof(hd->devmajor),OCT);
+ devminor = (dev_t)asc_u32(hd->devminor,sizeof(hd->devminor),OCT);
+ arcn->sb.st_rdev = TODEV(devmajor, devminor);
+ break;
+ case SYMTYPE:
+ case LNKTYPE:
+ if (hd->typeflag == SYMTYPE) {
+ arcn->type = PAX_SLK;
+ arcn->sb.st_mode |= S_IFLNK;
+ } else {
+ arcn->type = PAX_HLK;
+ /*
+ * so printing looks better
+ */
+ arcn->sb.st_mode |= S_IFREG;
+ arcn->sb.st_nlink = 2;
+ }
+ break;
+ case LONGLINKTYPE:
+ case LONGNAMETYPE:
+ if (is_gnutar) {
+ /*
+ * GNU long link/file; we tag these here and let the
+ * pax internals deal with it -- too ugly otherwise.
+ */
+ if (hd->typeflag != LONGLINKTYPE)
+ arcn->type = PAX_GLF;
+ else
+ arcn->type = PAX_GLL;
+ arcn->pad = TAR_PAD(arcn->sb.st_size);
+ arcn->skip = arcn->sb.st_size;
+ } else {
+ tty_warn(1, "GNU Long %s found in posix ustar archive.",
+ hd->typeflag == LONGLINKTYPE ? "Link" : "File");
+ }
+ break;
+ case FILEXTYPE:
+ case GLOBXTYPE:
+ tty_warn(0, "%s extended headers posix ustar archive."
+ " Extracting as plain files. Following files might be"
+ " in the wrong directory or have wrong attributes.",
+ hd->typeflag == FILEXTYPE ? "File" : "Global");
+ /*FALLTHROUGH*/
+ case CONTTYPE:
+ case AREGTYPE:
+ case REGTYPE:
+ default:
+ /*
+ * these types have file data that follows. Set the skip and
+ * pad fields.
+ */
+ arcn->type = PAX_REG;
+ arcn->pad = TAR_PAD(arcn->sb.st_size);
+ arcn->skip = arcn->sb.st_size;
+ arcn->sb.st_mode |= S_IFREG;
+ break;
+ }
+ return 0;
+}
+
+static int
+expandname(char *buf, size_t len, char **gnu_name, size_t *gnu_length,
+ const char *name, size_t nlen)
+{
+ if (*gnu_name) {
+ len = strlcpy(buf, *gnu_name, len);
+ free(*gnu_name);
+ *gnu_name = NULL;
+ *gnu_length = 0;
+ } else {
+ if (len > ++nlen)
+ len = nlen;
+ len = strlcpy(buf, name, len);
+ }
+ return len;
+}
+
+static void
+longlink(ARCHD *arcn, int type)
+{
+ ARCHD larc;
+
+ (void)memset(&larc, 0, sizeof(larc));
+
+ larc.type = type;
+ larc.nlen = strlcpy(larc.name, LONG_LINK, sizeof(larc.name));
+
+ switch (type) {
+ case PAX_GLL:
+ gnu_hack_string = arcn->ln_name;
+ gnu_hack_len = arcn->ln_nlen + 1;
+ break;
+ case PAX_GLF:
+ gnu_hack_string = arcn->name;
+ gnu_hack_len = arcn->nlen + 1;
+ break;
+ default:
+ errx(1, "Invalid type in GNU longlink %d", type);
+ }
+
+ /*
+ * We need a longlink now.
+ */
+ ustar_wr(&larc);
+}
+
+/*
+ * ustar_wr()
+ * write a ustar header for the file specified in the ARCHD to the archive
+ * Have to check for file types that cannot be stored and file names that
+ * are too long. Be careful of the term (last arg) to u32_oct, we only use
+ * '\0' for the termination character (this is different than picky tar)
+ * ASSUMED: space after header in header block is zero filled
+ * Return:
+ * 0 if file has data to be written after the header, 1 if file has NO
+ * data to write after the header, -1 if archive write failed
+ */
+
+static int
+size_err(const char *what, ARCHD *arcn)
+{
+ /*
+ * header field is out of range
+ */
+ tty_warn(1, "Ustar %s header field is too small for %s",
+ what, arcn->org_name);
+ return 1;
+}
+
+int
+ustar_wr(ARCHD *arcn)
+{
+ HD_USTAR *hd;
+ char *pt;
+ uintmax_t mtime;
+ char hdblk[sizeof(HD_USTAR)];
+ const char *user, *group;
+
+ switch (arcn->type) {
+ case PAX_SCK:
+ /*
+ * check for those file system types ustar cannot store
+ */
+ if (!is_gnutar)
+ tty_warn(1, "Ustar cannot archive a socket %s",
+ arcn->org_name);
+ return 1;
+
+ case PAX_SLK:
+ case PAX_HLK:
+ case PAX_HRG:
+ /*
+ * check the length of the linkname
+ */
+ if (arcn->ln_nlen >= (int)sizeof(hd->linkname)) {
+ if (is_gnutar) {
+ longlink(arcn, PAX_GLL);
+ } else {
+ tty_warn(1, "Link name too long for ustar %s",
+ arcn->ln_name);
+ return 1;
+ }
+ }
+ break;
+ default:
+ break;
+ }
+
+ /*
+ * split the path name into prefix and name fields (if needed). if
+ * pt != arcn->name, the name has to be split
+ */
+ if ((pt = name_split(arcn->name, arcn->nlen)) == NULL) {
+ if (is_gnutar) {
+ longlink(arcn, PAX_GLF);
+ pt = arcn->name;
+ } else {
+ tty_warn(1, "File name too long for ustar %s",
+ arcn->name);
+ return 1;
+ }
+ }
+
+ /*
+ * zero out the header so we don't have to worry about zero fill below
+ */
+ memset(hdblk, 0, sizeof(hdblk));
+ hd = (HD_USTAR *)hdblk;
+ arcn->pad = 0L;
+
+ /*
+ * split the name, or zero out the prefix
+ */
+ if (pt != arcn->name) {
+ /*
+ * name was split, pt points at the / where the split is to
+ * occur, we remove the / and copy the first part to the prefix
+ */
+ *pt = '\0';
+ strlcpy(hd->prefix, arcn->name, sizeof(hd->prefix));
+ *pt++ = '/';
+ }
+
+ /*
+ * copy the name part. this may be the whole path or the part after
+ * the prefix
+ */
+ strlcpy(hd->name, pt, sizeof(hd->name));
+
+ /*
+ * set the fields in the header that are type dependent
+ */
+ switch(arcn->type) {
+ case PAX_DIR:
+ hd->typeflag = DIRTYPE;
+ if (u32_oct((uintmax_t)0L, hd->size, sizeof(hd->size), 3))
+ return size_err("DIRTYPE", arcn);
+ break;
+ case PAX_CHR:
+ case PAX_BLK:
+ if (arcn->type == PAX_CHR)
+ hd->typeflag = CHRTYPE;
+ else
+ hd->typeflag = BLKTYPE;
+ if (u32_oct((uintmax_t)MAJOR(arcn->sb.st_rdev), hd->devmajor,
+ sizeof(hd->devmajor), 3) ||
+ u32_oct((uintmax_t)MINOR(arcn->sb.st_rdev), hd->devminor,
+ sizeof(hd->devminor), 3) ||
+ u32_oct((uintmax_t)0L, hd->size, sizeof(hd->size), 3))
+ return size_err("DEVTYPE", arcn);
+ break;
+ case PAX_FIF:
+ hd->typeflag = FIFOTYPE;
+ if (u32_oct((uintmax_t)0L, hd->size, sizeof(hd->size), 3))
+ return size_err("FIFOTYPE", arcn);
+ break;
+ case PAX_GLL:
+ case PAX_SLK:
+ case PAX_HLK:
+ case PAX_HRG:
+ if (arcn->type == PAX_SLK)
+ hd->typeflag = SYMTYPE;
+ else if (arcn->type == PAX_GLL)
+ hd->typeflag = LONGLINKTYPE;
+ else
+ hd->typeflag = LNKTYPE;
+ strlcpy(hd->linkname, arcn->ln_name, sizeof(hd->linkname));
+ if (u32_oct((uintmax_t)gnu_hack_len, hd->size,
+ sizeof(hd->size), 3))
+ return size_err("LINKTYPE", arcn);
+ break;
+ case PAX_GLF:
+ case PAX_REG:
+ case PAX_CTG:
+ default:
+ /*
+ * file data with this type, set the padding
+ */
+ if (arcn->type == PAX_GLF) {
+ hd->typeflag = LONGNAMETYPE;
+ arcn->pad = TAR_PAD(gnu_hack_len);
+ if (OFFT_OCT((uint32_t)gnu_hack_len, hd->size,
+ sizeof(hd->size), 3)) {
+ tty_warn(1,"File is too long for ustar %s",
+ arcn->org_name);
+ return 1;
+ }
+ } else {
+ if (arcn->type == PAX_CTG)
+ hd->typeflag = CONTTYPE;
+ else
+ hd->typeflag = REGTYPE;
+ arcn->pad = TAR_PAD(arcn->sb.st_size);
+ if (OFFT_OCT(arcn->sb.st_size, hd->size,
+ sizeof(hd->size), 3)) {
+ tty_warn(1,"File is too long for ustar %s",
+ arcn->org_name);
+ return 1;
+ }
+ }
+ break;
+ }
+
+ strncpy(hd->magic, TMAGIC, TMAGLEN);
+ if (is_gnutar)
+ hd->magic[TMAGLEN - 1] = hd->version[0] = ' ';
+ else
+ strncpy(hd->version, TVERSION, TVERSLEN);
+
+ /*
+ * set the remaining fields. Some versions want all 16 bits of mode
+ * we better humor them (they really do not meet spec though)....
+ */
+ if (u32_oct((uintmax_t)arcn->sb.st_mode, hd->mode, sizeof(hd->mode), 3))
+ return size_err("MODE", arcn);
+ if (u32_oct((uintmax_t)arcn->sb.st_uid, hd->uid, sizeof(hd->uid), 3))
+ return size_err("UID", arcn);
+ if (u32_oct((uintmax_t)arcn->sb.st_gid, hd->gid, sizeof(hd->gid), 3))
+ return size_err("GID", arcn);
+ mtime = tst.st_ino ? tst.st_mtime : arcn->sb.st_mtime;
+ if (u32_oct(mtime, hd->mtime, sizeof(hd->mtime), 3))
+ return size_err("MTIME", arcn);
+ user = user_from_uid(arcn->sb.st_uid, 1);
+ group = group_from_gid(arcn->sb.st_gid, 1);
+ strncpy(hd->uname, user ? user : "", sizeof(hd->uname));
+ strncpy(hd->gname, group ? group : "", sizeof(hd->gname));
+
+ /*
+ * calculate and store the checksum write the header to the archive
+ * return 0 tells the caller to now write the file data, 1 says no data
+ * needs to be written
+ */
+ if (u32_oct(tar_chksm(hdblk, sizeof(HD_USTAR)), hd->chksum,
+ sizeof(hd->chksum), 3))
+ return size_err("CHKSUM", arcn);
+ if (wr_rdbuf(hdblk, sizeof(HD_USTAR)) < 0)
+ return -1;
+ if (wr_skip((off_t)(BLKMULT - sizeof(HD_USTAR))) < 0)
+ return -1;
+ if (gnu_hack_string) {
+ int res = wr_rdbuf(gnu_hack_string, gnu_hack_len);
+ int pad = gnu_hack_len;
+ gnu_hack_string = NULL;
+ gnu_hack_len = 0;
+ if (res < 0)
+ return -1;
+ if (wr_skip((off_t)(BLKMULT - (pad % BLKMULT))) < 0)
+ return -1;
+ }
+ if ((arcn->type == PAX_CTG) || (arcn->type == PAX_REG))
+ return 0;
+ return 1;
+}
+
+/*
+ * name_split()
+ * see if the name has to be split for storage in a ustar header. We try
+ * to fit the entire name in the name field without splitting if we can.
+ * The split point is always at a /
+ * Return
+ * character pointer to split point (always the / that is to be removed
+ * if the split is not needed, the points is set to the start of the file
+ * name (it would violate the spec to split there). A NULL is returned if
+ * the file name is too long
+ */
+
+static char *
+name_split(char *name, int len)
+{
+ char *start;
+
+ /*
+ * check to see if the file name is small enough to fit in the name
+ * field. if so just return a pointer to the name.
+ */
+ if (len < TNMSZ)
+ return name;
+ /*
+ * GNU tar does not honor the prefix+name mode if the magic
+ * is not "ustar\0". So in GNU tar compatibility mode, we don't
+ * split the filename into prefix+name because we are setting
+ * the magic to "ustar " as GNU tar does. This of course will
+ * end up creating a LongLink record in cases where it does not
+ * really need do, but we are behaving like GNU tar after all.
+ */
+ if (is_gnutar || len > (TPFSZ + TNMSZ))
+ return NULL;
+
+ /*
+ * we start looking at the biggest sized piece that fits in the name
+ * field. We walk forward looking for a slash to split at. The idea is
+ * to find the biggest piece to fit in the name field (or the smallest
+ * prefix we can find) (the -1 is correct the biggest piece would
+ * include the slash between the two parts that gets thrown away)
+ */
+ start = name + len - TNMSZ;
+ while ((*start != '\0') && (*start != '/'))
+ ++start;
+
+ /*
+ * if we hit the end of the string, this name cannot be split, so we
+ * cannot store this file.
+ */
+ if (*start == '\0')
+ return NULL;
+ len = start - name;
+
+ /*
+ * NOTE: /str where the length of str == TNMSZ cannot be stored under
+ * the p1003.1-1990 spec for ustar. We could force a prefix of / and
+ * the file would then expand on extract to //str. The len == 0 below
+ * makes this special case follow the spec to the letter.
+ */
+ if ((len >= TPFSZ) || (len == 0))
+ return NULL;
+
+ /*
+ * ok have a split point, return it to the caller
+ */
+ return start;
+}
+
+/*
+ * convert a glob into a RE, and add it to the list. we convert to
+ * four different RE's (because we're using BRE's and can't use |
+ * alternation :-() with this padding:
+ * .*\/ and $
+ * .*\/ and \/.*
+ * ^ and $
+ * ^ and \/.*
+ */
+static int
+tar_gnutar_exclude_one(const char *line, size_t len)
+{
+ /* 2 * buffer len + nul */
+ char sbuf[MAXPATHLEN * 2 + 1];
+ /* + / + // + .*""/\/ + \/.* */
+ char rabuf[MAXPATHLEN * 2 + 1 + 1 + 2 + 4 + 4];
+ size_t i;
+ int j = 0;
+
+ if (line[len - 1] == '\n')
+ len--;
+ for (i = 0; i < len; i++) {
+ /*
+ * convert glob to regexp, escaping everything
+ */
+ if (line[i] == '*')
+ sbuf[j++] = '.';
+ else if (line[i] == '?') {
+ sbuf[j++] = '.';
+ continue;
+ } else if (!isalnum((unsigned char)line[i]) &&
+ !isblank((unsigned char)line[i]))
+ sbuf[j++] = '\\';
+ sbuf[j++] = line[i];
+ }
+ sbuf[j] = '\0';
+ /* don't need the .*\/ ones if we start with /, i guess */
+ if (line[0] != '/') {
+ (void)snprintf(rabuf, sizeof rabuf, "/.*\\/%s$//", sbuf);
+ if (rep_add(rabuf) < 0)
+ return (-1);
+ (void)snprintf(rabuf, sizeof rabuf, "/.*\\/%s\\/.*//", sbuf);
+ if (rep_add(rabuf) < 0)
+ return (-1);
+ }
+
+ (void)snprintf(rabuf, sizeof rabuf, "/^%s$//", sbuf);
+ if (rep_add(rabuf) < 0)
+ return (-1);
+ (void)snprintf(rabuf, sizeof rabuf, "/^%s\\/.*//", sbuf);
+ if (rep_add(rabuf) < 0)
+ return (-1);
+
+ return (0);
+}
+
+/*
+ * deal with GNU tar -X/--exclude-from & --exclude switchs. basically,
+ * we go through each line of the file, building a string from the "glob"
+ * lines in the file into RE lines, of the form `/^RE$//', which we pass
+ * to rep_add(), which will add a empty replacement (exclusion), for the
+ * named files.
+ */
+int
+tar_gnutar_minus_minus_exclude(const char *path)
+{
+ size_t len = strlen(path);
+
+ if (len > MAXPATHLEN)
+ tty_warn(0, "pathname too long: %s", path);
+
+ return (tar_gnutar_exclude_one(path, len));
+}
+
+int
+tar_gnutar_X_compat(const char *path)
+{
+ char *line;
+ FILE *fp;
+ int lineno = 0;
+ size_t len;
+
+ if (path[0] == '-' && path[1] == '\0')
+ fp = stdin;
+ else {
+ fp = fopen(path, "r");
+ if (fp == NULL) {
+ tty_warn(1, "cannot open %s: %s", path,
+ strerror(errno));
+ return -1;
+ }
+ }
+
+ while ((line = fgetln(fp, &len))) {
+ lineno++;
+ if (len > MAXPATHLEN) {
+ tty_warn(0, "pathname too long, line %d of %s",
+ lineno, path);
+ }
+ if (tar_gnutar_exclude_one(line, len))
+ return -1;
+ }
+ if (fp != stdin)
+ fclose(fp);
+ return 0;
+}
diff --git a/bin/pax/tar.h b/bin/pax/tar.h
new file mode 100644
index 0000000..ae7f6ce
--- /dev/null
+++ b/bin/pax/tar.h
@@ -0,0 +1,154 @@
+/* $NetBSD: tar.h,v 1.10 2013/01/24 17:43:44 christos Exp $ */
+
+/*-
+ * Copyright (c) 1992 Keith Muller.
+ * Copyright (c) 1992, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Keith Muller of the University of California, San Diego.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * @(#)tar.h 8.2 (Berkeley) 4/18/94
+ */
+
+/*
+ * defines and data structures common to all tar formats
+ */
+#define CHK_LEN 8 /* length of checksum field */
+#define TNMSZ 100 /* size of name field */
+#ifdef _PAX_
+#define NULLCNT 2 /* number of null blocks in trailer */
+#define CHK_OFFSET 148 /* start of chksum field */
+#define BLNKSUM 256L /* sum of checksum field using ' ' */
+#endif /* _PAX_ */
+
+/*
+ * Values used in typeflag field in all tar formats
+ * (only REGTYPE, LNKTYPE and SYMTYPE are used in old bsd tar headers)
+ */
+#define REGTYPE '0' /* Regular File */
+#define AREGTYPE '\0' /* Regular File */
+#define LNKTYPE '1' /* Link */
+#define SYMTYPE '2' /* Symlink */
+#define CHRTYPE '3' /* Character Special File */
+#define BLKTYPE '4' /* Block Special File */
+#define DIRTYPE '5' /* Directory */
+#define FIFOTYPE '6' /* FIFO */
+#define CONTTYPE '7' /* high perf file */
+#define GLOBXTYPE 'g' /* global extended header */
+#define FILEXTYPE 'x' /* file extended header */
+
+/*
+ * GNU tar compatibility;
+ */
+#define LONGLINKTYPE 'K' /* Long Symlink */
+#define LONGNAMETYPE 'L' /* Long File */
+
+/*
+ * Mode field encoding of the different file types - values in octal
+ */
+#define TSUID 04000 /* Set UID on execution */
+#define TSGID 02000 /* Set GID on execution */
+#define TSVTX 01000 /* Reserved */
+#define TUREAD 00400 /* Read by owner */
+#define TUWRITE 00200 /* Write by owner */
+#define TUEXEC 00100 /* Execute/Search by owner */
+#define TGREAD 00040 /* Read by group */
+#define TGWRITE 00020 /* Write by group */
+#define TGEXEC 00010 /* Execute/Search by group */
+#define TOREAD 00004 /* Read by other */
+#define TOWRITE 00002 /* Write by other */
+#define TOEXEC 00001 /* Execute/Search by other */
+
+#ifdef _PAX_
+/*
+ * Pad with a bit mask, much faster than doing a mod but only works on powers
+ * of 2. Macro below is for block of 512 bytes.
+ */
+#define TAR_PAD(x) ((512 - ((x) & 511)) & 511)
+#endif /* _PAX_ */
+
+/*
+ * structure of an old tar header as it appeared in BSD releases
+ */
+typedef struct {
+ char name[TNMSZ]; /* name of entry */
+ char mode[8]; /* mode */
+ char uid[8]; /* uid */
+ char gid[8]; /* gid */
+ char size[12]; /* size */
+ char mtime[12]; /* modification time */
+ char chksum[CHK_LEN]; /* checksum */
+ char linkflag; /* norm, hard, or sym. */
+ char linkname[TNMSZ]; /* linked to name */
+} HD_TAR;
+
+#ifdef _PAX_
+/*
+ * -o options for BSD tar to not write directories to the archive
+ */
+#define TAR_NODIR "nodir"
+#define TAR_OPTION "write_opt"
+
+/*
+ * default device names
+ */
+extern char DEV_0[];
+extern char DEV_1[];
+extern char DEV_4[];
+extern char DEV_5[];
+extern char DEV_7[];
+extern char DEV_8[];
+#endif /* _PAX_ */
+
+/*
+ * Data Interchange Format - Extended tar header format - POSIX 1003.1-1990
+ */
+#define TPFSZ 155
+#define TMAGIC "ustar" /* ustar and a null */
+#define TMAGLEN 6
+#define TVERSION "00" /* 00 and no null */
+#define TVERSLEN 2
+
+typedef struct {
+ char name[TNMSZ]; /* name of entry */
+ char mode[8]; /* mode */
+ char uid[8]; /* uid */
+ char gid[8]; /* gid */
+ char size[12]; /* size */
+ char mtime[12]; /* modification time */
+ char chksum[CHK_LEN]; /* checksum */
+ char typeflag; /* type of file. */
+ char linkname[TNMSZ]; /* linked to name */
+ char magic[TMAGLEN]; /* magic cookie */
+ char version[TVERSLEN]; /* version */
+ char uname[32]; /* ascii owner name */
+ char gname[32]; /* ascii group name */
+ char devmajor[8]; /* major device number */
+ char devminor[8]; /* minor device number */
+ char prefix[TPFSZ]; /* linked to name */
+} HD_USTAR;
diff --git a/bin/pax/tty_subs.c b/bin/pax/tty_subs.c
new file mode 100644
index 0000000..5956877
--- /dev/null
+++ b/bin/pax/tty_subs.c
@@ -0,0 +1,200 @@
+/* $NetBSD: tty_subs.c,v 1.19 2007/04/23 18:40:22 christos Exp $ */
+
+/*-
+ * Copyright (c) 1992 Keith Muller.
+ * Copyright (c) 1992, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Keith Muller of the University of California, San Diego.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#if HAVE_NBTOOL_CONFIG_H
+#include "nbtool_config.h"
+#endif
+
+#include <sys/cdefs.h>
+#if !defined(lint)
+#if 0
+static char sccsid[] = "@(#)tty_subs.c 8.2 (Berkeley) 4/18/94";
+#else
+__RCSID("$NetBSD: tty_subs.c,v 1.19 2007/04/23 18:40:22 christos Exp $");
+#endif
+#endif /* not lint */
+
+#include <sys/types.h>
+#include <sys/time.h>
+#include <sys/stat.h>
+#include <sys/param.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <ctype.h>
+#include <errno.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <string.h>
+#include "pax.h"
+#include "extern.h"
+#include <stdarg.h>
+
+/*
+ * routines that deal with I/O to and from the user
+ */
+
+#define DEVTTY "/dev/tty" /* device for interactive i/o */
+static FILE *ttyoutf = NULL; /* output pointing at control tty */
+static FILE *ttyinf = NULL; /* input pointing at control tty */
+
+/*
+ * tty_init()
+ * Try to open the controlling terminal (if any) for this process. If the
+ * open fails, future ops that require user input will get an EOF.
+ */
+
+int
+tty_init(void)
+{
+ int ttyfd;
+
+ if ((ttyfd = open(DEVTTY, O_RDWR)) >= 0) {
+ if ((ttyoutf = fdopen(ttyfd, "w")) != NULL) {
+ if ((ttyinf = fdopen(ttyfd, "r")) != NULL)
+ return 0;
+ (void)fclose(ttyoutf);
+ }
+ (void)close(ttyfd);
+ }
+
+ if (iflag) {
+ tty_warn(1, "Fatal error, cannot open %s", DEVTTY);
+ return -1;
+ }
+ return 0;
+}
+
+/*
+ * tty_prnt()
+ * print a message using the specified format to the controlling tty
+ * if there is no controlling terminal, just return.
+ */
+
+void
+tty_prnt(const char *fmt, ...)
+{
+ va_list ap;
+ if (ttyoutf == NULL)
+ return;
+ va_start(ap, fmt);
+ (void)vfprintf(ttyoutf, fmt, ap);
+ va_end(ap);
+ (void)fflush(ttyoutf);
+}
+
+/*
+ * tty_read()
+ * read a string from the controlling terminal if it is open into the
+ * supplied buffer
+ * Return:
+ * 0 if data was read, -1 otherwise.
+ */
+
+int
+tty_read(char *str, int len)
+{
+ char *pt;
+
+ if ((--len <= 0) || (ttyinf == NULL) || (fgets(str,len,ttyinf) == NULL))
+ return -1;
+ *(str + len) = '\0';
+
+ /*
+ * strip off that trailing newline
+ */
+ if ((pt = strchr(str, '\n')) != NULL)
+ *pt = '\0';
+ return 0;
+}
+
+/*
+ * tty_warn()
+ * write a warning message to stderr. if "set" the exit value of pax
+ * will be non-zero.
+ */
+
+void
+tty_warn(int set, const char *fmt, ...)
+{
+ va_list ap;
+ va_start(ap, fmt);
+ if (set)
+ exit_val = 1;
+ /*
+ * when vflag we better ship out an extra \n to get this message on a
+ * line by itself
+ */
+ if ((Vflag || vflag) && vfpart) {
+ (void)fputc('\n', stderr);
+ vfpart = 0;
+ }
+ (void)fprintf(stderr, "%s: ", argv0);
+ (void)vfprintf(stderr, fmt, ap);
+ va_end(ap);
+ (void)fputc('\n', stderr);
+}
+
+/*
+ * syswarn()
+ * write a warning message to stderr. if "set" the exit value of pax
+ * will be non-zero.
+ */
+
+void
+syswarn(int set, int errnum, const char *fmt, ...)
+{
+ va_list ap;
+ va_start(ap, fmt);
+ if (set)
+ exit_val = 1;
+ /*
+ * when vflag we better ship out an extra \n to get this message on a
+ * line by itself
+ */
+ if ((Vflag || vflag) && vfpart) {
+ (void)fputc('\n', stdout);
+ vfpart = 0;
+ }
+ (void)fprintf(stderr, "%s: ", argv0);
+ (void)vfprintf(stderr, fmt, ap);
+ va_end(ap);
+
+ /*
+ * format and print the errno
+ */
+ if (errnum > 0)
+ (void)fprintf(stderr, " (%s)", strerror(errnum));
+ (void)fputc('\n', stderr);
+}
diff --git a/bin/ps/extern.h b/bin/ps/extern.h
new file mode 100644
index 0000000..6ec96ca
--- /dev/null
+++ b/bin/ps/extern.h
@@ -0,0 +1,99 @@
+/* $NetBSD: extern.h,v 1.39 2017/12/09 14:56:54 kamil Exp $ */
+
+/*-
+ * Copyright (c) 1991, 1993, 1994
+ * The Regents of the University of California. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * @(#)extern.h 8.3 (Berkeley) 4/2/94
+ */
+
+/*
+ * We expect to be included by ps.h, which will already have
+ * defined the types we use.
+ */
+
+extern double log_ccpu;
+extern int eval, fscale, mempages, nlistread, maxslp, uspace;
+extern int sumrusage, termwidth, totwidth;
+extern int needenv, needcomm, commandonly;
+extern uid_t myuid;
+extern kvm_t *kd;
+extern VAR var[];
+extern VARLIST displaylist;
+extern VARLIST sortlist;
+
+void command(struct pinfo *, VARENT *, enum mode);
+void cpuid(struct pinfo *, VARENT *, enum mode);
+void cputime(struct pinfo *, VARENT *, enum mode);
+void donlist(void);
+void donlist_sysctl(void);
+void fmt_puts(char *, int *);
+void fmt_putc(int, int *);
+void elapsed(struct pinfo *, VARENT *, enum mode);
+double getpcpu(const struct kinfo_proc2 *);
+double getpmem(const struct kinfo_proc2 *);
+void gname(struct pinfo *, VARENT *, enum mode);
+void groups(struct pinfo *, VARENT *, enum mode);
+void groupnames(struct pinfo *, VARENT *, enum mode);
+void lcputime(struct pinfo *, VARENT *, enum mode);
+void logname(struct pinfo *, VARENT *, enum mode);
+void longtname(struct pinfo *, VARENT *, enum mode);
+void lname(struct pinfo *, VARENT *, enum mode);
+void lstarted(struct pinfo *, VARENT *, enum mode);
+void lstate(struct pinfo *, VARENT *, enum mode);
+void maxrss(struct pinfo *, VARENT *, enum mode);
+void nlisterr(struct nlist *);
+void p_rssize(struct pinfo *, VARENT *, enum mode);
+void pagein(struct pinfo *, VARENT *, enum mode);
+void parsefmt(const char *);
+void parsefmt_insert(const char *, VARENT **);
+void parsesort(const char *);
+VARENT * varlist_find(VARLIST *, const char *);
+void emul(struct pinfo *, VARENT *, enum mode);
+void pcpu(struct pinfo *, VARENT *, enum mode);
+void pmem(struct pinfo *, VARENT *, enum mode);
+void pnice(struct pinfo *, VARENT *, enum mode);
+void pri(struct pinfo *, VARENT *, enum mode);
+void printheader(void);
+void putimeval(struct pinfo *, VARENT *, enum mode);
+void pvar(struct pinfo *, VARENT *, enum mode);
+void rgname(struct pinfo *, VARENT *, enum mode);
+void rssize(struct pinfo *, VARENT *, enum mode);
+void runame(struct pinfo *, VARENT *, enum mode);
+void showkey(void);
+void started(struct pinfo *, VARENT *, enum mode);
+void state(struct pinfo *, VARENT *, enum mode);
+void svgname(struct pinfo *, VARENT *, enum mode);
+void svuname(struct pinfo *, VARENT *, enum mode);
+void tdev(struct pinfo *, VARENT *, enum mode);
+void tname(struct pinfo *, VARENT *, enum mode);
+void tsize(struct pinfo *, VARENT *, enum mode);
+void ucomm(struct pinfo *, VARENT *, enum mode);
+void usrname(struct pinfo *, VARENT *, enum mode);
+void uvar(struct pinfo *, VARENT *, enum mode);
+void vsize(struct pinfo *, VARENT *, enum mode);
+void wchan(struct pinfo *, VARENT *, enum mode);
diff --git a/bin/ps/fmt.c b/bin/ps/fmt.c
new file mode 100644
index 0000000..489a0ec
--- /dev/null
+++ b/bin/ps/fmt.c
@@ -0,0 +1,60 @@
+/* $NetBSD: fmt.c,v 1.21 2007/12/12 22:55:43 lukem Exp $ */
+
+#include <sys/cdefs.h>
+__RCSID("$NetBSD: fmt.c,v 1.21 2007/12/12 22:55:43 lukem Exp $");
+
+#include <kvm.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <vis.h>
+#include <sys/time.h>
+#include <sys/resource.h>
+#include "ps.h"
+
+void
+fmt_puts(char *s, int *leftp)
+{
+ static char *v = 0;
+ static int maxlen = 0;
+ char *nv;
+ int len, nlen;
+
+ if (*leftp == 0)
+ return;
+ len = strlen(s) * 4 + 1;
+ if (len > maxlen) {
+ if (maxlen == 0)
+ nlen = getpagesize();
+ else
+ nlen = maxlen;
+ while (len > nlen)
+ nlen *= 2;
+ nv = realloc(v, nlen);
+ if (nv == 0)
+ return;
+ v = nv;
+ maxlen = nlen;
+ }
+ len = strvis(v, s, VIS_TAB | VIS_NL | VIS_CSTYLE);
+ if (*leftp != -1) {
+ if (len > *leftp) {
+ v[*leftp] = '\0';
+ *leftp = 0;
+ } else
+ *leftp -= len;
+ }
+ (void)printf("%s", v);
+}
+
+void
+fmt_putc(int c, int *leftp)
+{
+
+ if (*leftp == 0)
+ return;
+ if (*leftp != -1)
+ *leftp -= 1;
+ putchar(c);
+}
diff --git a/bin/ps/keyword.c b/bin/ps/keyword.c
new file mode 100644
index 0000000..f7504ca
--- /dev/null
+++ b/bin/ps/keyword.c
@@ -0,0 +1,415 @@
+/* $NetBSD: keyword.c,v 1.56 2018/04/11 18:52:05 christos Exp $ */
+
+/*-
+ * Copyright (c) 1990, 1993, 1994
+ * The Regents of the University of California. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+#ifndef lint
+#if 0
+static char sccsid[] = "@(#)keyword.c 8.5 (Berkeley) 4/2/94";
+#else
+__RCSID("$NetBSD: keyword.c,v 1.56 2018/04/11 18:52:05 christos Exp $");
+#endif
+#endif /* not lint */
+
+#include <sys/param.h>
+#include <sys/time.h>
+#include <sys/lwp.h>
+#include <sys/proc.h>
+#include <sys/resource.h>
+#include <sys/sysctl.h>
+#include <sys/ucred.h>
+
+#include <err.h>
+#include <errno.h>
+#include <kvm.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <signal.h>
+
+#include "ps.h"
+
+static VAR *findvar(const char *);
+static int vcmp(const void *, const void *);
+
+#if 0 /* kernel doesn't calculate these */
+ PUVAR("idrss", "IDRSS", 0, p_uru_idrss, UINT64, PRIu64),
+ PUVAR("isrss", "ISRSS", 0, p_uru_isrss, UINT64, PRId64),
+ PUVAR("ixrss", "IXRSS", 0, p_uru_ixrss, UINT64, PRId64),
+ PUVAR("maxrss", "MAXRSS", 0, p_uru_maxrss, UINT64, PRIu64),
+#endif
+
+/* Compute offset in common structures. */
+#define POFF(x) offsetof(struct kinfo_proc2, x)
+#define LOFF(x) offsetof(struct kinfo_lwp, x)
+
+#define UIDFMT "u"
+#define UID(n1, n2, of) \
+ { .name = n1, .header = n2, .flag = 0, .oproc = pvar, \
+ .off = POFF(of), .type = UINT32, .fmt = UIDFMT }
+#define GID(n1, n2, off) UID(n1, n2, off)
+
+#define PIDFMT "d"
+#define PID(n1, n2, of) \
+ { .name = n1, .header = n2, .flag = 0, .oproc = pvar, \
+ .off = POFF(of), .type = INT32, .fmt = PIDFMT }
+
+#define LVAR(n1, n2, fl, of, ty, fm) \
+ { .name = n1, .header = n2, .flag = (fl) | LWP, .oproc = pvar, \
+ .off = LOFF(of), .type = ty, .fmt = fm }
+#define PVAR(n1, n2, fl, of, ty, fm) \
+ { .name = n1, .header = n2, .flag = (fl) | 0, .oproc = pvar, \
+ .off = POFF(of), .type = ty, .fmt = fm }
+#define PUVAR(n1, n2, fl, of, ty, fm) \
+ { .name = n1, .header = n2, .flag = (fl) | UAREA, .oproc = pvar, \
+ .off = POFF(of), .type = ty, .fmt = fm }
+#define VAR3(n1, n2, fl) \
+ { .name = n1, .header = n2, .flag = fl }
+#define VAR4(n1, n2, fl, op) \
+ { .name = n1, .header = n2, .flag = fl, .oproc = op, }
+#define VAR6(n1, n2, fl, op, of, ty) \
+ { .name = n1, .header = n2, .flag = fl, .oproc = op, \
+ .off = of, .type = ty }
+
+/* NB: table must be sorted, in vi use:
+ * :/^VAR/,/end_sort/! sort -t\" +1
+ * breaking long lines just makes the sort harder
+ *
+ * We support all the fields required by P1003.1-2004 (SUSv3), with
+ * the correct default headers, except for the "tty" field, where the
+ * standard says the header should be "TT", but we have "TTY".
+ */
+VAR var[] = {
+ VAR6("%cpu", "%CPU", 0, pcpu, 0, PCPU),
+ VAR6("%mem", "%MEM", 0, pmem, POFF(p_vm_rssize), INT32),
+ PVAR("acflag", "ACFLG", 0, p_acflag, USHORT, "x"),
+ VAR3("acflg", "acflag", ALIAS),
+ VAR3("args", "command", ALIAS),
+ VAR3("blocked", "sigmask", ALIAS),
+ VAR3("caught", "sigcatch", ALIAS),
+ VAR4("comm", "COMMAND", COMM|ARGV0|LJUST, command),
+ VAR4("command", "COMMAND", COMM|LJUST, command),
+ PVAR("cpu", "CPU", 0, p_estcpu, UINT, "u"),
+ VAR4("cpuid", "CPUID", LWP, cpuid),
+ VAR3("cputime", "time", ALIAS),
+ VAR6("ctime", "CTIME", 0, putimeval, POFF(p_uctime_sec), TIMEVAL),
+ GID("egid", "EGID", p_gid),
+ VAR4("egroup", "EGROUP", LJUST, gname),
+ VAR4("emul", "EMUL", LJUST, emul),
+ VAR6("etime", "ELAPSED", 0, elapsed, POFF(p_ustart_sec), TIMEVAL),
+ UID("euid", "EUID", p_uid),
+ VAR4("euser", "EUSER", LJUST, usrname),
+ PVAR("f", "F", 0, p_flag, INT, "x"),
+ VAR3("flags", "f", ALIAS),
+ GID("gid", "GID", p_gid),
+ VAR4("group", "GROUP", LJUST, gname),
+ VAR4("groupnames", "GROUPNAMES", LJUST, groupnames),
+ VAR4("groups", "GROUPS", LJUST, groups),
+ /* holdcnt: unused, left for compat. */
+ LVAR("holdcnt", "HOLDCNT", 0, l_holdcnt, INT, "d"),
+ VAR3("ignored", "sigignore", ALIAS),
+ PUVAR("inblk", "INBLK", 0, p_uru_inblock, UINT64, PRIu64),
+ VAR3("inblock", "inblk", ALIAS),
+ PVAR("jobc", "JOBC", 0, p_jobc, SHORT, "d"),
+ PVAR("ktrace", "KTRACE", 0, p_traceflag, INT, "x"),
+/*XXX*/ PVAR("ktracep", "KTRACEP", 0, p_tracep, KPTR, PRIx64),
+ LVAR("laddr", "LADDR", 0, l_laddr, KPTR, PRIx64),
+ LVAR("lid", "LID", 0, l_lid, INT32, "d"),
+ VAR4("lim", "LIM", 0, maxrss),
+ VAR4("lname", "LNAME", LJUST|LWP, lname),
+ VAR4("login", "LOGIN", LJUST, logname),
+ VAR3("logname", "login", ALIAS),
+ VAR6("lstart", "STARTED", LJUST, lstarted, POFF(p_ustart_sec), UINT32),
+ VAR4("lstate", "STAT", LJUST|LWP, lstate),
+ VAR6("ltime", "LTIME", LWP, lcputime, 0, CPUTIME),
+ PUVAR("majflt", "MAJFLT", 0, p_uru_majflt, UINT64, PRIu64),
+ PUVAR("minflt", "MINFLT", 0, p_uru_minflt, UINT64, PRIu64),
+ PUVAR("msgrcv", "MSGRCV", 0, p_uru_msgrcv, UINT64, PRIu64),
+ PUVAR("msgsnd", "MSGSND", 0, p_uru_msgsnd, UINT64, PRIu64),
+ VAR3("ni", "nice", ALIAS),
+ VAR6("nice", "NI", 0, pnice, POFF(p_nice), UCHAR),
+ PUVAR("nivcsw", "NIVCSW", 0, p_uru_nivcsw, UINT64, PRIu64),
+ PVAR("nlwp", "NLWP", 0, p_nlwps, UINT64, PRId64),
+ VAR3("nsignals", "nsigs", ALIAS),
+ PUVAR("nsigs", "NSIGS", 0, p_uru_nsignals, UINT64, PRIu64),
+ /* nswap: unused, left for compat. */
+ PUVAR("nswap", "NSWAP", 0, p_uru_nswap, UINT64, PRIu64),
+ PUVAR("nvcsw", "NVCSW", 0, p_uru_nvcsw, UINT64, PRIu64),
+/*XXX*/ LVAR("nwchan", "WCHAN", 0, l_wchan, KPTR, PRIx64),
+ PUVAR("oublk", "OUBLK", 0, p_uru_oublock, UINT64, PRIu64),
+ VAR3("oublock", "oublk", ALIAS),
+/*XXX*/ PVAR("p_ru", "P_RU", 0, p_ru, KPTR, PRIx64),
+/*XXX*/ PVAR("paddr", "PADDR", 0, p_paddr, KPTR, PRIx64),
+ PUVAR("pagein", "PAGEIN", 0, p_uru_majflt, UINT64, PRIu64),
+ VAR3("pcpu", "%cpu", ALIAS),
+ VAR3("pending", "sig", ALIAS),
+ PID("pgid", "PGID", p__pgid),
+ PID("pid", "PID", p_pid),
+ VAR3("pmem", "%mem", ALIAS),
+ PID("ppid", "PPID", p_ppid),
+ VAR4("pri", "PRI", LWP, pri),
+ LVAR("re", "RE", INF127, l_swtime, UINT, "u"),
+ GID("rgid", "RGID", p_rgid),
+ VAR4("rgroup", "RGROUP", LJUST, rgname),
+/*XXX*/ LVAR("rlink", "RLINK", 0, l_back, KPTR, PRIx64),
+ PVAR("rlwp", "RLWP", 0, p_nrlwps, UINT64, PRId64),
+ VAR6("rss", "RSS", 0, p_rssize, POFF(p_vm_rssize), INT32),
+ VAR3("rssize", "rsz", ALIAS),
+ VAR6("rsz", "RSZ", 0, rssize, POFF(p_vm_rssize), INT32),
+ UID("ruid", "RUID", p_ruid),
+ VAR4("ruser", "RUSER", LJUST, runame),
+ PVAR("sess", "SESS", 0, p_sess, KPTR24, PRIx64),
+ PID("sid", "SID", p_sid),
+ PVAR("sig", "PENDING", 0, p_siglist, SIGLIST, "s"),
+ PVAR("sigcatch", "CAUGHT", 0, p_sigcatch, SIGLIST, "s"),
+ PVAR("sigignore", "IGNORED", 0, p_sigignore, SIGLIST, "s"),
+ PVAR("sigmask", "BLOCKED", 0, p_sigmask, SIGLIST, "s"),
+ LVAR("sl", "SL", INF127, l_slptime, UINT, "u"),
+ VAR6("start", "STARTED", 0, started, POFF(p_ustart_sec), UINT32),
+ VAR3("stat", "state", ALIAS),
+ VAR4("state", "STAT", LJUST, state),
+ VAR6("stime", "STIME", 0, putimeval, POFF(p_ustime_sec), TIMEVAL),
+ GID("svgid", "SVGID", p_svgid),
+ VAR4("svgroup", "SVGROUP", LJUST, svgname),
+ UID("svuid", "SVUID", p_svuid),
+ VAR4("svuser", "SVUSER", LJUST, svuname),
+ /* "tdev" is UINT32, but we do this for sorting purposes */
+ VAR6("tdev", "TDEV", 0, tdev, POFF(p_tdev), INT32),
+ VAR6("time", "TIME", 0, cputime, 0, CPUTIME),
+ PID("tpgid", "TPGID", p_tpgid),
+ PVAR("tsess", "TSESS", 0, p_tsess, KPTR, PRIx64),
+ VAR6("tsiz", "TSIZ", 0, tsize, POFF(p_vm_tsize), INT32),
+ VAR6("tt", "TTY", LJUST, tname, POFF(p_tdev), INT32),
+ VAR6("tty", "TTY", LJUST, longtname, POFF(p_tdev), INT32),
+ LVAR("uaddr", "UADDR", 0, l_addr, KPTR, PRIx64),
+ VAR4("ucomm", "UCOMM", LJUST, ucomm),
+ UID("uid", "UID", p_uid),
+ LVAR("upr", "UPR", 0, l_usrpri, UCHAR, "u"),
+ VAR4("user", "USER", LJUST, usrname),
+ VAR3("usrpri", "upr", ALIAS),
+ VAR6("utime", "UTIME", 0, putimeval, POFF(p_uutime_sec), TIMEVAL),
+ VAR3("vsize", "vsz", ALIAS),
+ VAR6("vsz", "VSZ", 0, vsize, 0, VSIZE),
+ VAR4("wchan", "WCHAN", LJUST|LWP, wchan),
+ PVAR("xstat", "XSTAT", 0, p_xstat, USHORT, "x"),
+/* "zzzz" end_sort */
+ { .name = "" },
+};
+
+void
+showkey(void)
+{
+ VAR *v;
+ int i;
+ const char *p;
+ const char *sep;
+
+ i = 0;
+ sep = "";
+ for (v = var; *(p = v->name); ++v) {
+ int len = strlen(p);
+ if (termwidth && (i += len + 1) > termwidth) {
+ i = len;
+ sep = "\n";
+ }
+ (void)printf("%s%s", sep, p);
+ sep = " ";
+ }
+ (void)printf("\n");
+}
+
+/*
+ * Parse the string pp, and insert or append entries to the list
+ * referenced by listptr. If pos in non-null and *pos is non-null, then
+ * *pos specifies where to insert (instead of appending). If pos is
+ * non-null, then a new value is returned through *pos referring to the
+ * last item inserted.
+ */
+static void
+parsevarlist(const char *pp, struct varlist *listptr, struct varent **pos)
+{
+ char *p, *sp, *equalsp;
+
+ /* dup to avoid zapping arguments. We will free sp later. */
+ p = sp = strdup(pp);
+
+ /*
+ * Everything after the first '=' is part of a custom header.
+ * Temporarily replace it with '\0' to simplify other code.
+ */
+ equalsp = strchr(p, '=');
+ if (equalsp)
+ *equalsp = '\0';
+
+#define FMTSEP " \t,\n"
+ while (p && *p) {
+ char *cp;
+ VAR *v;
+ struct varent *vent;
+
+ /*
+ * skip separators before the first keyword, and
+ * look for the separator after the keyword.
+ */
+ for (cp = p; *cp != '\0'; cp++) {
+ p = strpbrk(cp, FMTSEP);
+ if (p != cp)
+ break;
+ }
+ if (*cp == '\0')
+ break;
+ /*
+ * Now cp points to the start of a keyword,
+ * and p is NULL or points past the end of the keyword.
+ *
+ * Terminate the keyword with '\0', or reinstate the
+ * '=' that was removed earlier, if appropriate.
+ */
+ if (p) {
+ *p = '\0';
+ p++;
+ } else if (equalsp) {
+ *equalsp = '=';
+ }
+
+ /*
+ * If findvar() likes the keyword or keyword=header,
+ * add it to our list. If findvar() doesn't like it,
+ * it will print a warning, so we ignore it.
+ */
+ if ((v = findvar(cp)) == NULL)
+ continue;
+ if ((vent = malloc(sizeof(struct varent))) == NULL)
+ err(EXIT_FAILURE, NULL);
+ vent->var = v;
+ if (pos && *pos)
+ SIMPLEQ_INSERT_AFTER(listptr, *pos, vent, next);
+ else {
+ SIMPLEQ_INSERT_TAIL(listptr, vent, next);
+ }
+ if (pos)
+ *pos = vent;
+ }
+ free(sp);
+ if (SIMPLEQ_EMPTY(listptr))
+ errx(EXIT_FAILURE, "no valid keywords");
+}
+
+void
+parsefmt(const char *p)
+{
+
+ parsevarlist(p, &displaylist, NULL);
+}
+
+void
+parsefmt_insert(const char *p, struct varent **pos)
+{
+
+ parsevarlist(p, &displaylist, pos);
+}
+
+void
+parsesort(const char *p)
+{
+
+ parsevarlist(p, &sortlist, NULL);
+}
+
+/* Search through a list for an entry with a specified name. */
+struct varent *
+varlist_find(struct varlist *list, const char *name)
+{
+ struct varent *vent;
+
+ SIMPLEQ_FOREACH(vent, list, next) {
+ if (strcmp(vent->var->name, name) == 0)
+ break;
+ }
+ return vent;
+}
+
+static VAR *
+findvar(const char *p)
+{
+ VAR *v;
+ char *hp;
+
+ hp = strchr(p, '=');
+ if (hp)
+ *hp++ = '\0';
+
+ v = bsearch(p, var, sizeof(var)/sizeof(VAR) - 1, sizeof(VAR), vcmp);
+ if (v && v->flag & ALIAS)
+ v = findvar(v->header);
+ if (!v) {
+ warnx("%s: keyword not found", p);
+ eval = 1;
+ return NULL;
+ }
+
+ if (v && hp) {
+ /*
+ * Override the header.
+ *
+ * We need to copy the entry first, and override the
+ * header in the copy, because the same field might be
+ * used multiple times with different headers. We also
+ * need to strdup the header.
+ */
+ struct var *newvar;
+ char *newheader;
+
+ if ((newvar = malloc(sizeof(struct var))) == NULL)
+ err(EXIT_FAILURE, NULL);
+ if ((newheader = strdup(hp)) == NULL)
+ err(EXIT_FAILURE, NULL);
+ memcpy(newvar, v, sizeof(struct var));
+ newvar->header = newheader;
+
+ /*
+ * According to P1003.1-2004, if the header text is null,
+ * such as -o user=, the field width will be at least as
+ * wide as the default header text.
+ */
+ if (*hp == '\0')
+ newvar->width = strlen(v->header);
+
+ v = newvar;
+ }
+ return v;
+}
+
+static int
+vcmp(const void *a, const void *b)
+{
+ return strcmp(a, ((const VAR *)b)->name);
+}
diff --git a/bin/ps/nlist.c b/bin/ps/nlist.c
new file mode 100644
index 0000000..42471ea
--- /dev/null
+++ b/bin/ps/nlist.c
@@ -0,0 +1,234 @@
+/* $NetBSD: nlist.c,v 1.27 2016/11/28 08:19:23 rin Exp $ */
+
+/*
+ * Copyright (c) 2000 The NetBSD Foundation, Inc.
+ * All rights reserved.
+ *
+ * This code is derived from software contributed to The NetBSD Foundation
+ * by Simon Burge.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
+ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/*
+ * Copyright (c) 1990, 1993, 1994
+ * The Regents of the University of California. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+#ifndef lint
+#if 0
+static char sccsid[] = "@(#)nlist.c 8.4 (Berkeley) 4/2/94";
+#else
+__RCSID("$NetBSD: nlist.c,v 1.27 2016/11/28 08:19:23 rin Exp $");
+#endif
+#endif /* not lint */
+
+#include <sys/param.h>
+#include <sys/time.h>
+#include <sys/lwp.h>
+#include <sys/proc.h>
+#include <sys/resource.h>
+#include <sys/sysctl.h>
+
+#include <err.h>
+#include <errno.h>
+#include <kvm.h>
+#include <math.h>
+#include <nlist.h>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "ps.h"
+
+struct nlist psnl[] = {
+ { .n_name = "_fscale" },
+#define X_FSCALE 0
+ { .n_name = "_ccpu" },
+#define X_CCPU 1
+ { .n_name = "_physmem" },
+#define X_PHYSMEM 2
+ { .n_name = "_maxslp" },
+#define X_MAXSLP 3
+ { .n_name = NULL }
+};
+
+double log_ccpu; /* log of kernel _ccpu variable */
+int nlistread; /* if nlist already read. */
+int mempages; /* number of pages of phys. memory */
+int fscale; /* kernel _fscale variable */
+int maxslp; /* kernel _maxslp variable */
+int uspace; /* kernel USPACE value */
+
+/* XXX Hopefully reasonable default */
+#define MEMPAGES 0
+#ifndef FSCALE
+#define FSCALE (1 << 8)
+#endif
+#define LOG_CCPU (-1.0 / 20.0)
+#ifndef MAXSLP
+#define MAXSLP 20
+#endif
+#ifndef USPACE
+#define USPACE (getpagesize())
+#endif
+
+#define kread(x, v) \
+ kvm_read(kd, psnl[x].n_value, (char *)&v, sizeof v) != sizeof(v)
+
+void
+donlist(void)
+{
+ fixpt_t xccpu;
+
+ nlistread = 1;
+
+ if (kvm_nlist(kd, psnl)) {
+ nlisterr(psnl);
+ eval = 1;
+ fscale = FSCALE;
+ mempages = MEMPAGES;
+ log_ccpu = LOG_CCPU;
+ maxslp = MAXSLP;
+ return;
+ }
+
+ if (kread(X_FSCALE, fscale)) {
+ warnx("fscale: %s", kvm_geterr(kd));
+ eval = 1;
+ fscale = FSCALE;
+ }
+
+ if (kread(X_PHYSMEM, mempages)) {
+ warnx("avail_start: %s", kvm_geterr(kd));
+ eval = 1;
+ mempages = MEMPAGES;
+ }
+
+ if (kread(X_CCPU, xccpu)) {
+ warnx("ccpu: %s", kvm_geterr(kd));
+ eval = 1;
+ log_ccpu = LOG_CCPU;
+ } else
+ log_ccpu = log((double)xccpu / fscale);
+
+ if (kread(X_MAXSLP, maxslp)) {
+ warnx("maxslp: %s", kvm_geterr(kd));
+ eval = 1;
+ maxslp = MAXSLP;
+ }
+}
+
+void
+donlist_sysctl(void)
+{
+ int mib[2];
+ size_t size;
+ fixpt_t xccpu;
+ uint64_t memsize;
+
+ nlistread = 1;
+
+ mib[0] = CTL_KERN;
+ mib[1] = KERN_FSCALE;
+ size = sizeof(fscale);
+ if (sysctl(mib, 2, &fscale, &size, NULL, 0)) {
+ warn("fscale");
+ eval = 1;
+ fscale = FSCALE;
+ }
+
+ mib[0] = CTL_HW;
+ mib[1] = HW_PHYSMEM64;
+ size = sizeof(memsize);
+ if (sysctl(mib, 2, &memsize, &size, NULL, 0)) {
+ warn("avail_start");
+ eval = 1;
+ mempages = MEMPAGES;
+ } else
+ mempages = memsize / getpagesize();
+
+ mib[0] = CTL_KERN;
+ mib[1] = KERN_CCPU;
+ size = sizeof(xccpu);
+ if (sysctl(mib, 2, &xccpu, &size, NULL, 0)) {
+ warn("ccpu");
+ eval = 1;
+ log_ccpu = LOG_CCPU;
+ } else
+ log_ccpu = log((double)xccpu / fscale);
+
+ mib[0] = CTL_VM;
+ mib[1] = VM_MAXSLP;
+ size = sizeof(maxslp);
+ if (sysctl(mib, 2, &maxslp, &size, NULL, 0)) {
+ warn("maxslp");
+ eval = 1;
+ maxslp = MAXSLP;
+ }
+
+ mib[0] = CTL_VM;
+ mib[1] = VM_USPACE;
+ size = sizeof(uspace);
+ if (sysctl(mib, 2, &uspace, &size, NULL, 0)) {
+ warn("uspace");
+ eval = 1;
+ uspace = USPACE;
+ }
+}
+
+void
+nlisterr(struct nlist nl[])
+{
+ int i;
+
+ (void)fprintf(stderr, "ps: nlist: can't find following symbols:");
+ for (i = 0; nl[i].n_name != NULL; i++)
+ if (nl[i].n_value == 0)
+ (void)fprintf(stderr, " %s", nl[i].n_name);
+ (void)fprintf(stderr, "\n");
+}
diff --git a/bin/ps/print.c b/bin/ps/print.c
new file mode 100644
index 0000000..e725ef0
--- /dev/null
+++ b/bin/ps/print.c
@@ -0,0 +1,1413 @@
+/* $NetBSD: print.c,v 1.130 2018/09/19 15:20:39 maxv Exp $ */
+
+/*
+ * Copyright (c) 2000, 2007 The NetBSD Foundation, Inc.
+ * All rights reserved.
+ *
+ * This code is derived from software contributed to The NetBSD Foundation
+ * by Simon Burge.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
+ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/*
+ * Copyright (c) 1990, 1993, 1994
+ * The Regents of the University of California. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+#ifndef lint
+#if 0
+static char sccsid[] = "@(#)print.c 8.6 (Berkeley) 4/16/94";
+#else
+__RCSID("$NetBSD: print.c,v 1.130 2018/09/19 15:20:39 maxv Exp $");
+#endif
+#endif /* not lint */
+
+#include <sys/param.h>
+#include <sys/time.h>
+#include <sys/resource.h>
+#include <sys/lwp.h>
+#include <sys/proc.h>
+#include <sys/stat.h>
+#include <sys/ucred.h>
+#include <sys/sysctl.h>
+
+#include <err.h>
+#include <grp.h>
+#include <kvm.h>
+#include <math.h>
+#include <nlist.h>
+#include <pwd.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <tzfile.h>
+#include <unistd.h>
+
+#include "ps.h"
+
+static char *cmdpart(char *);
+static void printval(void *, VAR *, enum mode);
+static int titlecmp(char *, char **);
+
+static void doubleprintorsetwidth(VAR *, double, int, enum mode);
+static void intprintorsetwidth(VAR *, int, enum mode);
+static void strprintorsetwidth(VAR *, const char *, enum mode);
+
+static time_t now;
+
+#define min(a,b) ((a) <= (b) ? (a) : (b))
+
+static int
+iwidth(u_int64_t v)
+{
+ u_int64_t nlim, lim;
+ int w = 1;
+
+ for (lim = 10; v >= lim; lim = nlim) {
+ nlim = lim * 10;
+ w++;
+ if (nlim < lim)
+ break;
+ }
+ return w;
+}
+
+static char *
+cmdpart(char *arg0)
+{
+ char *cp;
+
+ return ((cp = strrchr(arg0, '/')) != NULL ? cp + 1 : arg0);
+}
+
+void
+printheader(void)
+{
+ int len;
+ VAR *v;
+ struct varent *vent;
+ static int firsttime = 1;
+ static int noheader = 0;
+
+ /*
+ * If all the columns have user-specified null headers,
+ * don't print the blank header line at all.
+ */
+ if (firsttime) {
+ SIMPLEQ_FOREACH(vent, &displaylist, next) {
+ if (vent->var->header[0])
+ break;
+ }
+ if (vent == NULL) {
+ noheader = 1;
+ firsttime = 0;
+ }
+
+ }
+ if (noheader)
+ return;
+
+ SIMPLEQ_FOREACH(vent, &displaylist, next) {
+ v = vent->var;
+ if (firsttime) {
+ len = strlen(v->header);
+ if (len > v->width)
+ v->width = len;
+ totwidth += v->width + 1; /* +1 for space */
+ }
+ if (v->flag & LJUST) {
+ if (SIMPLEQ_NEXT(vent, next) == NULL) /* last one */
+ (void)printf("%s", v->header);
+ else
+ (void)printf("%-*s", v->width,
+ v->header);
+ } else
+ (void)printf("%*s", v->width, v->header);
+ if (SIMPLEQ_NEXT(vent, next) != NULL)
+ (void)putchar(' ');
+ }
+ (void)putchar('\n');
+ if (firsttime) {
+ firsttime = 0;
+ totwidth--; /* take off last space */
+ }
+}
+
+/*
+ * Return 1 if the command name in the argument vector (u-area) does
+ * not match the command name (p_comm)
+ */
+static int
+titlecmp(char *name, char **argv)
+{
+ char *title;
+ int namelen;
+
+
+ /* no argument vector == no match; system processes/threads do that */
+ if (argv == 0 || argv[0] == 0)
+ return (1);
+
+ title = cmdpart(argv[0]);
+
+ /* the basename matches */
+ if (!strcmp(name, title))
+ return (0);
+
+ /* handle login shells, by skipping the leading - */
+ if (title[0] == '-' && !strcmp(name, title + 1))
+ return (0);
+
+ namelen = strlen(name);
+
+ /* handle daemons that report activity as daemonname: activity */
+ if (argv[1] == 0 &&
+ !strncmp(name, title, namelen) &&
+ title[namelen + 0] == ':' &&
+ title[namelen + 1] == ' ')
+ return (0);
+
+ return (1);
+}
+
+static void
+doubleprintorsetwidth(VAR *v, double val, int prec, enum mode mode)
+{
+ int fmtlen;
+
+ if (mode == WIDTHMODE) {
+ if (val < 0.0 && val < v->longestnd) {
+ fmtlen = (int)log10(-val) + prec + 2;
+ v->longestnd = val;
+ if (fmtlen > v->width)
+ v->width = fmtlen;
+ } else if (val > 0.0 && val > v->longestpd) {
+ fmtlen = (int)log10(val) + prec + 1;
+ v->longestpd = val;
+ if (fmtlen > v->width)
+ v->width = fmtlen;
+ }
+ } else {
+ (void)printf("%*.*f", v->width, prec, val);
+ }
+}
+
+static void
+intprintorsetwidth(VAR *v, int val, enum mode mode)
+{
+ int fmtlen;
+
+ if (mode == WIDTHMODE) {
+ if (val < 0 && val < v->longestn) {
+ v->longestn = val;
+ fmtlen = iwidth(-val) + 1;
+ if (fmtlen > v->width)
+ v->width = fmtlen;
+ } else if (val > 0 && val > v->longestp) {
+ v->longestp = val;
+ fmtlen = iwidth(val);
+ if (fmtlen > v->width)
+ v->width = fmtlen;
+ }
+ } else
+ (void)printf("%*d", v->width, val);
+}
+
+static void
+strprintorsetwidth(VAR *v, const char *str, enum mode mode)
+{
+ int len;
+
+ if (mode == WIDTHMODE) {
+ len = strlen(str);
+ if (len > v->width)
+ v->width = len;
+ } else {
+ if (v->flag & LJUST)
+ (void)printf("%-*.*s", v->width, v->width, str);
+ else
+ (void)printf("%*.*s", v->width, v->width, str);
+ }
+}
+
+void
+command(struct pinfo *pi, VARENT *ve, enum mode mode)
+{
+ struct kinfo_proc2 *ki = pi->ki;
+ VAR *v;
+ int left;
+ char **argv, **p, *name;
+
+ if (mode == WIDTHMODE)
+ return;
+
+ v = ve->var;
+ if (SIMPLEQ_NEXT(ve, next) != NULL || termwidth != UNLIMITED) {
+ if (SIMPLEQ_NEXT(ve, next) == NULL) {
+ left = termwidth - (totwidth - v->width);
+ if (left < 1) /* already wrapped, just use std width */
+ left = v->width;
+ } else
+ left = v->width;
+ } else
+ left = -1;
+ if (needenv && kd) {
+ argv = kvm_getenvv2(kd, ki, termwidth);
+ if ((p = argv) != NULL) {
+ while (*p) {
+ fmt_puts(*p, &left);
+ p++;
+ fmt_putc(' ', &left);
+ }
+ }
+ }
+ if (needcomm) {
+ if (pi->prefix)
+ (void)fmt_puts(pi->prefix, &left);
+ name = ki->p_comm;
+ if (!commandonly) {
+ argv = kvm_getargv2(kd, ki, termwidth);
+ if ((p = argv) != NULL) {
+ while (*p) {
+ fmt_puts(*p, &left);
+ p++;
+ fmt_putc(' ', &left);
+ if (v->flag & ARGV0)
+ break;
+ }
+ if (!(v->flag & ARGV0) &&
+ titlecmp(name, argv)) {
+ /*
+ * append the real command name within
+ * parentheses, if the command name
+ * does not match the one in the
+ * argument vector
+ */
+ fmt_putc('(', &left);
+ fmt_puts(name, &left);
+ fmt_putc(')', &left);
+ }
+ } else {
+ /*
+ * Commands that don't set an argv vector
+ * are printed with square brackets if they
+ * are system commands. Otherwise they are
+ * printed within parentheses.
+ */
+ if (ki->p_flag & P_SYSTEM) {
+ fmt_putc('[', &left);
+ fmt_puts(name, &left);
+ fmt_putc(']', &left);
+ } else {
+ fmt_putc('(', &left);
+ fmt_puts(name, &left);
+ fmt_putc(')', &left);
+ }
+ }
+ } else {
+ fmt_puts(name, &left);
+ }
+ }
+ if (SIMPLEQ_NEXT(ve, next) != NULL && left > 0)
+ (void)printf("%*s", left, "");
+}
+
+void
+groups(struct pinfo *pi, VARENT *ve, enum mode mode)
+{
+ struct kinfo_proc2 *ki = pi->ki;
+ VAR *v;
+ int left, i;
+ char buf[16], *p;
+
+ if (mode == WIDTHMODE)
+ return;
+
+ v = ve->var;
+ if (SIMPLEQ_NEXT(ve, next) != NULL || termwidth != UNLIMITED) {
+ if (SIMPLEQ_NEXT(ve, next) == NULL) {
+ left = termwidth - (totwidth - v->width);
+ if (left < 1) /* already wrapped, just use std width */
+ left = v->width;
+ } else
+ left = v->width;
+ } else
+ left = -1;
+
+ if (ki->p_ngroups == 0)
+ fmt_putc('-', &left);
+
+ for (i = 0; i < ki->p_ngroups; i++) {
+ (void)snprintf(buf, sizeof(buf), "%d", ki->p_groups[i]);
+ if (i)
+ fmt_putc(' ', &left);
+ for (p = &buf[0]; *p; p++)
+ fmt_putc(*p, &left);
+ }
+
+ if (SIMPLEQ_NEXT(ve, next) != NULL && left > 0)
+ (void)printf("%*s", left, "");
+}
+
+void
+groupnames(struct pinfo *pi, VARENT *ve, enum mode mode)
+{
+ struct kinfo_proc2 *ki = pi->ki;
+ VAR *v;
+ int left, i;
+ const char *p;
+
+ if (mode == WIDTHMODE)
+ return;
+
+ v = ve->var;
+ if (SIMPLEQ_NEXT(ve, next) != NULL || termwidth != UNLIMITED) {
+ if (SIMPLEQ_NEXT(ve, next) == NULL) {
+ left = termwidth - (totwidth - v->width);
+ if (left < 1) /* already wrapped, just use std width */
+ left = v->width;
+ } else
+ left = v->width;
+ } else
+ left = -1;
+
+ if (ki->p_ngroups == 0)
+ fmt_putc('-', &left);
+
+ for (i = 0; i < ki->p_ngroups; i++) {
+ if (i)
+ fmt_putc(' ', &left);
+ for (p = group_from_gid(ki->p_groups[i], 0); *p; p++)
+ fmt_putc(*p, &left);
+ }
+
+ if (SIMPLEQ_NEXT(ve, next) != NULL && left > 0)
+ (void)printf("%*s", left, "");
+}
+
+void
+ucomm(struct pinfo *pi, VARENT *ve, enum mode mode)
+{
+ struct kinfo_proc2 *k = pi->ki;
+ char buf[MAXPATHLEN], *p;
+ VAR *v;
+
+ v = ve->var;
+ if (pi->prefix)
+ snprintf(p = buf, sizeof(buf), "%s%s", pi->prefix, k->p_comm);
+ else
+ p = k->p_comm;
+ strprintorsetwidth(v, p, mode);
+}
+
+void
+emul(struct pinfo *pi, VARENT *ve, enum mode mode)
+{
+ struct kinfo_proc2 *k = pi->ki;
+ VAR *v;
+
+ v = ve->var;
+ strprintorsetwidth(v, k->p_ename, mode);
+}
+
+void
+logname(struct pinfo *pi, VARENT *ve, enum mode mode)
+{
+ struct kinfo_proc2 *k = pi->ki;
+ VAR *v;
+
+ v = ve->var;
+ strprintorsetwidth(v, k->p_login, mode);
+}
+
+void
+state(struct pinfo *pi, VARENT *ve, enum mode mode)
+{
+ struct kinfo_proc2 *k = pi->ki;
+ int flag, is_zombie;
+ char *cp;
+ VAR *v;
+ char buf[16];
+
+ is_zombie = 0;
+ v = ve->var;
+ flag = k->p_flag;
+ cp = buf;
+
+ /*
+ * NOTE: There are historical letters, which are no longer used:
+ *
+ * - W: indicated that process is swapped out.
+ * - L: indicated non-zero l_holdcnt (i.e. that process was
+ * prevented from swapping-out.
+ *
+ * These letters should not be used for new states to avoid
+ * conflicts with old applications which might depend on them.
+ */
+ switch (k->p_stat) {
+
+ case LSSTOP:
+ *cp = 'T';
+ break;
+
+ case LSSLEEP:
+ if (flag & L_SINTR) /* interruptable (long) */
+ *cp = (int)k->p_slptime >= maxslp ? 'I' : 'S';
+ else
+ *cp = 'D';
+ break;
+
+ case LSRUN:
+ case LSIDL:
+ *cp = 'R';
+ break;
+
+ case LSONPROC:
+ *cp = 'O';
+ break;
+
+ case LSZOMB:
+ *cp = 'Z';
+ is_zombie = 1;
+ break;
+
+ case LSSUSPENDED:
+ *cp = 'U';
+ break;
+
+ default:
+ *cp = '?';
+ }
+ cp++;
+ if (k->p_nice < NZERO)
+ *cp++ = '<';
+ else if (k->p_nice > NZERO)
+ *cp++ = 'N';
+ if (flag & P_TRACED)
+ *cp++ = 'X';
+ if (flag & P_WEXIT && !is_zombie)
+ *cp++ = 'E';
+ if (flag & P_PPWAIT)
+ *cp++ = 'V';
+ if (flag & P_SYSTEM)
+ *cp++ = 'K';
+ if (k->p_eflag & EPROC_SLEADER)
+ *cp++ = 's';
+ if (flag & P_SA)
+ *cp++ = 'a';
+ else if (k->p_nlwps > 1)
+ *cp++ = 'l';
+ if ((flag & P_CONTROLT) && k->p__pgid == k->p_tpgid)
+ *cp++ = '+';
+ *cp = '\0';
+ strprintorsetwidth(v, buf, mode);
+}
+
+void
+lstate(struct pinfo *pi, VARENT *ve, enum mode mode)
+{
+ struct kinfo_lwp *k = pi->li;
+ int flag;
+ char *cp;
+ VAR *v;
+ char buf[16];
+
+ v = ve->var;
+ flag = k->l_flag;
+ cp = buf;
+
+ switch (k->l_stat) {
+
+ case LSSTOP:
+ *cp = 'T';
+ break;
+
+ case LSSLEEP:
+ if (flag & L_SINTR) /* interruptible (long) */
+ *cp = (int)k->l_slptime >= maxslp ? 'I' : 'S';
+ else
+ *cp = 'D';
+ break;
+
+ case LSRUN:
+ case LSIDL:
+ *cp = 'R';
+ break;
+
+ case LSONPROC:
+ *cp = 'O';
+ break;
+
+ case LSZOMB:
+ case LSDEAD:
+ *cp = 'Z';
+ break;
+
+ case LSSUSPENDED:
+ *cp = 'U';
+ break;
+
+ default:
+ *cp = '?';
+ }
+ cp++;
+ if (flag & L_SYSTEM)
+ *cp++ = 'K';
+ if (flag & L_SA)
+ *cp++ = 'a';
+ if (flag & L_DETACHED)
+ *cp++ = '-';
+ *cp = '\0';
+ strprintorsetwidth(v, buf, mode);
+}
+
+void
+pnice(struct pinfo *pi, VARENT *ve, enum mode mode)
+{
+ struct kinfo_proc2 *k = pi->ki;
+ VAR *v;
+
+ v = ve->var;
+ intprintorsetwidth(v, k->p_nice - NZERO, mode);
+}
+
+void
+pri(struct pinfo *pi, VARENT *ve, enum mode mode)
+{
+ struct kinfo_lwp *l = pi->li;
+ VAR *v;
+
+ v = ve->var;
+ intprintorsetwidth(v, l->l_priority, mode);
+}
+
+void
+usrname(struct pinfo *pi, VARENT *ve, enum mode mode)
+{
+ struct kinfo_proc2 *k = pi->ki;
+ VAR *v;
+
+ v = ve->var;
+ strprintorsetwidth(v, user_from_uid(k->p_uid, 0), mode);
+}
+
+void
+runame(struct pinfo *pi, VARENT *ve, enum mode mode)
+{
+ struct kinfo_proc2 *k = pi->ki;
+ VAR *v;
+
+ v = ve->var;
+ strprintorsetwidth(v, user_from_uid(k->p_ruid, 0), mode);
+}
+
+void
+svuname(struct pinfo *pi, VARENT *ve, enum mode mode)
+{
+ struct kinfo_proc2 *k = pi->ki;
+ VAR *v;
+
+ v = ve->var;
+ strprintorsetwidth(v, user_from_uid(k->p_svuid, 0), mode);
+}
+
+void
+gname(struct pinfo *pi, VARENT *ve, enum mode mode)
+{
+ struct kinfo_proc2 *k = pi->ki;
+ VAR *v;
+
+ v = ve->var;
+ strprintorsetwidth(v, group_from_gid(k->p_gid, 0), mode);
+}
+
+void
+rgname(struct pinfo *pi, VARENT *ve, enum mode mode)
+{
+ struct kinfo_proc2 *k = pi->ki;
+ VAR *v;
+
+ v = ve->var;
+ strprintorsetwidth(v, group_from_gid(k->p_rgid, 0), mode);
+}
+
+void
+svgname(struct pinfo *pi, VARENT *ve, enum mode mode)
+{
+ struct kinfo_proc2 *k = pi->ki;
+ VAR *v;
+
+ v = ve->var;
+ strprintorsetwidth(v, group_from_gid(k->p_svgid, 0), mode);
+}
+
+void
+tdev(struct pinfo *pi, VARENT *ve, enum mode mode)
+{
+ struct kinfo_proc2 *k = pi->ki;
+ VAR *v;
+ dev_t dev;
+ char buff[16];
+
+ v = ve->var;
+ dev = k->p_tdev;
+ if (dev == NODEV) {
+ if (mode == PRINTMODE)
+ (void)printf("%*s", v->width, "?");
+ else
+ if (v->width < 2)
+ v->width = 2;
+ } else {
+ (void)snprintf(buff, sizeof(buff),
+ "%lld/%lld", (long long)major(dev), (long long)minor(dev));
+ strprintorsetwidth(v, buff, mode);
+ }
+}
+
+void
+tname(struct pinfo *pi, VARENT *ve, enum mode mode)
+{
+ struct kinfo_proc2 *k = pi->ki;
+ VAR *v;
+ dev_t dev;
+ const char *ttname;
+ int noctty;
+
+ v = ve->var;
+ dev = k->p_tdev;
+ if (dev == NODEV || (ttname = devname(dev, S_IFCHR)) == NULL) {
+ if (mode == PRINTMODE)
+ (void)printf("%-*s", v->width, "?");
+ else
+ if (v->width < 2)
+ v->width = 2;
+ } else {
+ noctty = !(k->p_eflag & EPROC_CTTY) ? 1 : 0;
+ if (mode == WIDTHMODE) {
+ int fmtlen;
+
+ fmtlen = strlen(ttname) + noctty;
+ if (v->width < fmtlen)
+ v->width = fmtlen;
+ } else {
+ if (noctty)
+ (void)printf("%-*s-", v->width - 1, ttname);
+ else
+ (void)printf("%-*s", v->width, ttname);
+ }
+ }
+}
+
+void
+longtname(struct pinfo *pi, VARENT *ve, enum mode mode)
+{
+ struct kinfo_proc2 *k = pi->ki;
+ VAR *v;
+ dev_t dev;
+ const char *ttname;
+
+ v = ve->var;
+ dev = k->p_tdev;
+ if (dev == NODEV || (ttname = devname(dev, S_IFCHR)) == NULL) {
+ if (mode == PRINTMODE)
+ (void)printf("%-*s", v->width, "?");
+ else
+ if (v->width < 2)
+ v->width = 2;
+ } else {
+ strprintorsetwidth(v, ttname, mode);
+ }
+}
+
+void
+started(struct pinfo *pi, VARENT *ve, enum mode mode)
+{
+ struct kinfo_proc2 *k = pi->ki;
+ VAR *v;
+ time_t startt;
+ struct tm *tp;
+ char buf[100], *cp;
+
+ v = ve->var;
+ if (!k->p_uvalid) {
+ if (mode == PRINTMODE)
+ (void)printf("%*s", v->width, "-");
+ return;
+ }
+
+ startt = k->p_ustart_sec;
+ tp = localtime(&startt);
+ if (now == 0)
+ (void)time(&now);
+ if (now - k->p_ustart_sec < SECSPERDAY)
+ /* I *hate* SCCS... */
+ (void)strftime(buf, sizeof(buf) - 1, "%l:%" "M%p", tp);
+ else if (now - k->p_ustart_sec < DAYSPERWEEK * SECSPERDAY)
+ /* I *hate* SCCS... */
+ (void)strftime(buf, sizeof(buf) - 1, "%a%" "I%p", tp);
+ else
+ (void)strftime(buf, sizeof(buf) - 1, "%e%b%y", tp);
+ /* %e and %l can start with a space. */
+ cp = buf;
+ if (*cp == ' ')
+ cp++;
+ strprintorsetwidth(v, cp, mode);
+}
+
+void
+lstarted(struct pinfo *pi, VARENT *ve, enum mode mode)
+{
+ struct kinfo_proc2 *k = pi->ki;
+ VAR *v;
+ time_t startt;
+ char buf[100];
+
+ v = ve->var;
+ if (!k->p_uvalid) {
+ /*
+ * Minimum width is less than header - we don't
+ * need to check it every time.
+ */
+ if (mode == PRINTMODE)
+ (void)printf("%*s", v->width, "-");
+ return;
+ }
+ startt = k->p_ustart_sec;
+
+ /* assume all times are the same length */
+ if (mode != WIDTHMODE || v->width == 0) {
+ (void)strftime(buf, sizeof(buf) -1, "%c",
+ localtime(&startt));
+ strprintorsetwidth(v, buf, mode);
+ }
+}
+
+void
+elapsed(struct pinfo *pi, VARENT *ve, enum mode mode)
+{
+ struct kinfo_proc2 *k = pi->ki;
+ VAR *v;
+ int32_t origseconds, secs, mins, hours, days;
+ int fmtlen, printed_something;
+
+ v = ve->var;
+ if (k->p_uvalid == 0) {
+ origseconds = 0;
+ } else {
+ if (now == 0)
+ (void)time(&now);
+ origseconds = now - k->p_ustart_sec;
+ if (origseconds < 0) {
+ /*
+ * Don't try to be fancy if the machine's
+ * clock has been rewound to before the
+ * process "started".
+ */
+ origseconds = 0;
+ }
+ }
+
+ secs = origseconds;
+ mins = secs / SECSPERMIN;
+ secs %= SECSPERMIN;
+ hours = mins / MINSPERHOUR;
+ mins %= MINSPERHOUR;
+ days = hours / HOURSPERDAY;
+ hours %= HOURSPERDAY;
+
+ if (mode == WIDTHMODE) {
+ if (origseconds == 0)
+ /* non-zero so fmtlen is calculated at least once */
+ origseconds = 1;
+
+ if (origseconds > v->longestp) {
+ v->longestp = origseconds;
+
+ if (days > 0) {
+ /* +9 for "-hh:mm:ss" */
+ fmtlen = iwidth(days) + 9;
+ } else if (hours > 0) {
+ /* +6 for "mm:ss" */
+ fmtlen = iwidth(hours) + 6;
+ } else {
+ /* +3 for ":ss" */
+ fmtlen = iwidth(mins) + 3;
+ }
+
+ if (fmtlen > v->width)
+ v->width = fmtlen;
+ }
+ } else {
+ printed_something = 0;
+ fmtlen = v->width;
+
+ if (days > 0) {
+ (void)printf("%*d", fmtlen - 9, days);
+ printed_something = 1;
+ } else if (fmtlen > 9) {
+ (void)printf("%*s", fmtlen - 9, "");
+ }
+ if (fmtlen > 9)
+ fmtlen = 9;
+
+ if (printed_something) {
+ (void)printf("-%.*d", fmtlen - 7, hours);
+ printed_something = 1;
+ } else if (hours > 0) {
+ (void)printf("%*d", fmtlen - 6, hours);
+ printed_something = 1;
+ } else if (fmtlen > 6) {
+ (void)printf("%*s", fmtlen - 6, "");
+ }
+ if (fmtlen > 6)
+ fmtlen = 6;
+
+ /* Don't need to set fmtlen or printed_something any more... */
+ if (printed_something) {
+ (void)printf(":%.*d", fmtlen - 4, mins);
+ } else if (mins > 0) {
+ (void)printf("%*d", fmtlen - 3, mins);
+ } else if (fmtlen > 3) {
+ (void)printf("%*s", fmtlen - 3, "0");
+ }
+
+ (void)printf(":%.2d", secs);
+ }
+}
+
+void
+wchan(struct pinfo *pi, VARENT *ve, enum mode mode)
+{
+ struct kinfo_lwp *l = pi->li;
+ VAR *v;
+
+ v = ve->var;
+ if (l->l_wmesg[0]) {
+ strprintorsetwidth(v, l->l_wmesg, mode);
+ v->width = min(v->width, KI_WMESGLEN);
+ } else {
+ if (mode == PRINTMODE)
+ (void)printf("%-*s", v->width, "-");
+ }
+}
+
+#define pgtok(a) (((a)*(size_t)getpagesize())/1024)
+
+void
+vsize(struct pinfo *pi, VARENT *ve, enum mode mode)
+{
+ struct kinfo_proc2 *k = pi->ki;
+ VAR *v;
+
+ v = ve->var;
+ intprintorsetwidth(v, pgtok(k->p_vm_msize), mode);
+}
+
+void
+rssize(struct pinfo *pi, VARENT *ve, enum mode mode)
+{
+ struct kinfo_proc2 *k = pi->ki;
+ VAR *v;
+
+ v = ve->var;
+ /* XXX don't have info about shared */
+ intprintorsetwidth(v, pgtok(k->p_vm_rssize), mode);
+}
+
+void
+p_rssize(struct pinfo *pi, VARENT *ve, enum mode mode) /* doesn't account for text */
+{
+ struct kinfo_proc2 *k = pi->ki;
+ VAR *v;
+
+ v = ve->var;
+ intprintorsetwidth(v, pgtok(k->p_vm_rssize), mode);
+}
+
+void
+cpuid(struct pinfo *pi, VARENT *ve, enum mode mode)
+{
+ struct kinfo_lwp *l = pi->li;
+ VAR *v;
+
+ v = ve->var;
+ intprintorsetwidth(v, l->l_cpuid, mode);
+}
+
+static void
+cputime1(int32_t secs, int32_t psecs, VAR *v, enum mode mode)
+{
+ int fmtlen;
+
+ /*
+ * round and scale to 100's
+ */
+ psecs = (psecs + 5000) / 10000;
+ secs += psecs / 100;
+ psecs = psecs % 100;
+
+ if (mode == WIDTHMODE) {
+ /*
+ * Ugg, this is the only field where a value of 0 is longer
+ * than the column title.
+ * Use SECSPERMIN, because secs is divided by that when
+ * passed to iwidth().
+ */
+ if (secs == 0)
+ secs = SECSPERMIN;
+
+ if (secs > v->longestp) {
+ v->longestp = secs;
+ /* "+6" for the ":%02ld.%02ld" in the printf() below */
+ fmtlen = iwidth(secs / SECSPERMIN) + 6;
+ if (fmtlen > v->width)
+ v->width = fmtlen;
+ }
+ } else {
+ (void)printf("%*ld:%02ld.%02ld", v->width - 6,
+ (long)(secs / SECSPERMIN), (long)(secs % SECSPERMIN),
+ (long)psecs);
+ }
+}
+
+void
+cputime(struct pinfo *pi, VARENT *ve, enum mode mode)
+{
+ struct kinfo_proc2 *k = pi->ki;
+ VAR *v;
+ int32_t secs;
+ int32_t psecs; /* "parts" of a second. first micro, then centi */
+
+ v = ve->var;
+
+ /*
+ * This counts time spent handling interrupts. We could
+ * fix this, but it is not 100% trivial (and interrupt
+ * time fractions only work on the sparc anyway). XXX
+ */
+ secs = k->p_rtime_sec;
+ psecs = k->p_rtime_usec;
+ if (sumrusage) {
+ secs += k->p_uctime_sec;
+ psecs += k->p_uctime_usec;
+ }
+
+ cputime1(secs, psecs, v, mode);
+}
+
+void
+lcputime(struct pinfo *pi, VARENT *ve, enum mode mode)
+{
+ struct kinfo_lwp *l = pi->li;
+ VAR *v;
+ int32_t secs;
+ int32_t psecs; /* "parts" of a second. first micro, then centi */
+
+ v = ve->var;
+
+ secs = l->l_rtime_sec;
+ psecs = l->l_rtime_usec;
+
+ cputime1(secs, psecs, v, mode);
+}
+
+void
+pcpu(struct pinfo *pi, VARENT *ve, enum mode mode)
+{
+ VAR *v;
+ double dbl;
+
+ v = ve->var;
+ dbl = pi->pcpu;
+ doubleprintorsetwidth(v, dbl, (dbl >= 99.95) ? 0 : 1, mode);
+}
+
+double
+getpmem(const struct kinfo_proc2 *k)
+{
+ double fracmem;
+ int szptudot;
+
+ if (!nlistread)
+ donlist();
+
+ /* XXX want pmap ptpages, segtab, etc. (per architecture) */
+ szptudot = uspace/getpagesize();
+ /* XXX don't have info about shared */
+ fracmem = ((float)k->p_vm_rssize + szptudot)/mempages;
+ return (100.0 * fracmem);
+}
+
+void
+pmem(struct pinfo *pi, VARENT *ve, enum mode mode)
+{
+ struct kinfo_proc2 *k = pi->ki;
+ VAR *v;
+
+ v = ve->var;
+ doubleprintorsetwidth(v, getpmem(k), 1, mode);
+}
+
+void
+pagein(struct pinfo *pi, VARENT *ve, enum mode mode)
+{
+ struct kinfo_proc2 *k = pi->ki;
+ VAR *v;
+
+ v = ve->var;
+ intprintorsetwidth(v, k->p_uvalid ? k->p_uru_majflt : 0, mode);
+}
+
+void
+maxrss(struct pinfo *pi, VARENT *ve, enum mode mode)
+{
+ VAR *v;
+
+ v = ve->var;
+ /* No need to check width! */
+ if (mode == PRINTMODE)
+ (void)printf("%*s", v->width, "-");
+}
+
+void
+tsize(struct pinfo *pi, VARENT *ve, enum mode mode)
+{
+ struct kinfo_proc2 *k = pi->ki;
+ VAR *v;
+
+ v = ve->var;
+ intprintorsetwidth(v, pgtok(k->p_vm_tsize), mode);
+}
+
+/*
+ * Generic output routines. Print fields from various prototype
+ * structures.
+ */
+static void
+printval(void *bp, VAR *v, enum mode mode)
+{
+ static char ofmt[32] = "%";
+ int width, vok, fmtlen;
+ const char *fcp;
+ char *cp;
+ int64_t val;
+ u_int64_t uval;
+
+ val = 0; /* XXXGCC -Wuninitialized [hpcarm] */
+ uval = 0; /* XXXGCC -Wuninitialized [hpcarm] */
+
+ /*
+ * Note that the "INF127" check is nonsensical for types
+ * that are or can be signed.
+ */
+#define GET(type) (*(type *)bp)
+#define CHK_INF127(n) (((n) > 127) && (v->flag & INF127) ? 127 : (n))
+
+#define VSIGN 1
+#define VUNSIGN 2
+#define VPTR 3
+
+ if (mode == WIDTHMODE) {
+ vok = 0;
+ switch (v->type) {
+ case CHAR:
+ val = GET(char);
+ vok = VSIGN;
+ break;
+ case UCHAR:
+ uval = CHK_INF127(GET(u_char));
+ vok = VUNSIGN;
+ break;
+ case SHORT:
+ val = GET(short);
+ vok = VSIGN;
+ break;
+ case USHORT:
+ uval = CHK_INF127(GET(u_short));
+ vok = VUNSIGN;
+ break;
+ case INT32:
+ val = GET(int32_t);
+ vok = VSIGN;
+ break;
+ case INT:
+ val = GET(int);
+ vok = VSIGN;
+ break;
+ case UINT:
+ case UINT32:
+ uval = CHK_INF127(GET(u_int));
+ vok = VUNSIGN;
+ break;
+ case LONG:
+ val = GET(long);
+ vok = VSIGN;
+ break;
+ case ULONG:
+ uval = CHK_INF127(GET(u_long));
+ vok = VUNSIGN;
+ break;
+ case KPTR:
+ uval = GET(u_int64_t);
+ vok = VPTR;
+ break;
+ case KPTR24:
+ uval = GET(u_int64_t);
+ uval &= 0xffffff;
+ vok = VPTR;
+ break;
+ case INT64:
+ val = GET(int64_t);
+ vok = VSIGN;
+ break;
+ case UINT64:
+ uval = CHK_INF127(GET(u_int64_t));
+ vok = VUNSIGN;
+ break;
+
+ case SIGLIST:
+ default:
+ /* nothing... */;
+ }
+ switch (vok) {
+ case VSIGN:
+ if (val < 0 && val < v->longestn) {
+ v->longestn = val;
+ fmtlen = iwidth(-val) + 1;
+ if (fmtlen > v->width)
+ v->width = fmtlen;
+ } else if (val > 0 && val > v->longestp) {
+ v->longestp = val;
+ fmtlen = iwidth(val);
+ if (fmtlen > v->width)
+ v->width = fmtlen;
+ }
+ return;
+ case VUNSIGN:
+ if (uval > v->longestu) {
+ v->longestu = uval;
+ v->width = iwidth(uval);
+ }
+ return;
+ case VPTR:
+ fmtlen = 0;
+ while (uval > 0) {
+ uval >>= 4;
+ fmtlen++;
+ }
+ if (fmtlen > v->width)
+ v->width = fmtlen;
+ return;
+ }
+ }
+
+ width = v->width;
+ cp = ofmt + 1;
+ fcp = v->fmt;
+ if (v->flag & LJUST)
+ *cp++ = '-';
+ *cp++ = '*';
+ while ((*cp++ = *fcp++) != '\0')
+ continue;
+
+ switch (v->type) {
+ case CHAR:
+ (void)printf(ofmt, width, GET(char));
+ return;
+ case UCHAR:
+ (void)printf(ofmt, width, CHK_INF127(GET(u_char)));
+ return;
+ case SHORT:
+ (void)printf(ofmt, width, GET(short));
+ return;
+ case USHORT:
+ (void)printf(ofmt, width, CHK_INF127(GET(u_short)));
+ return;
+ case INT:
+ (void)printf(ofmt, width, GET(int));
+ return;
+ case UINT:
+ (void)printf(ofmt, width, CHK_INF127(GET(u_int)));
+ return;
+ case LONG:
+ (void)printf(ofmt, width, GET(long));
+ return;
+ case ULONG:
+ (void)printf(ofmt, width, CHK_INF127(GET(u_long)));
+ return;
+ case KPTR:
+ (void)printf(ofmt, width, GET(u_int64_t));
+ return;
+ case KPTR24:
+ (void)printf(ofmt, width, GET(u_int64_t) & 0xffffff);
+ return;
+ case INT32:
+ (void)printf(ofmt, width, GET(int32_t));
+ return;
+ case UINT32:
+ (void)printf(ofmt, width, CHK_INF127(GET(u_int32_t)));
+ return;
+ case SIGLIST:
+ {
+ sigset_t *s = (sigset_t *)(void *)bp;
+ size_t i;
+#define SIGSETSIZE (sizeof(s->__bits) / sizeof(s->__bits[0]))
+ char buf[SIGSETSIZE * 8 + 1];
+
+ for (i = 0; i < SIGSETSIZE; i++)
+ (void)snprintf(&buf[i * 8], 9, "%.8x",
+ s->__bits[(SIGSETSIZE - 1) - i]);
+
+ /* Skip leading zeroes */
+ for (i = 0; buf[i] == '0'; i++)
+ continue;
+
+ if (buf[i] == '\0')
+ i--;
+ strprintorsetwidth(v, buf + i, mode);
+#undef SIGSETSIZE
+ }
+ return;
+ case INT64:
+ (void)printf(ofmt, width, GET(int64_t));
+ return;
+ case UINT64:
+ (void)printf(ofmt, width, CHK_INF127(GET(u_int64_t)));
+ return;
+ default:
+ errx(EXIT_FAILURE, "unknown type %d", v->type);
+ }
+#undef GET
+#undef CHK_INF127
+}
+
+void
+pvar(struct pinfo *pi, VARENT *ve, enum mode mode)
+{
+ VAR *v = ve->var;
+ char *b = (v->flag & LWP) ? (char *)pi->li : (char *)pi->ki;
+
+ if ((v->flag & UAREA) && !pi->ki->p_uvalid) {
+ if (mode == PRINTMODE)
+ (void)printf("%*s", v->width, "-");
+ return;
+ }
+
+ (void)printval(b + v->off, v, mode);
+}
+
+void
+putimeval(struct pinfo *pi, VARENT *ve, enum mode mode)
+{
+ VAR *v = ve->var;
+ struct kinfo_proc2 *k = pi->ki;
+ char *b = (v->flag & LWP) ? (char *)pi->li : (char *)pi->ki;
+ ulong secs = *(uint32_t *)(b + v->off);
+ ulong usec = *(uint32_t *)(b + v->off + sizeof (uint32_t));
+ int fmtlen;
+
+ if (!k->p_uvalid) {
+ if (mode == PRINTMODE)
+ (void)printf("%*s", v->width, "-");
+ return;
+ }
+
+ if (mode == WIDTHMODE) {
+ if (secs == 0)
+ /* non-zero so fmtlen is calculated at least once */
+ secs = 1;
+ if (secs > v->longestu) {
+ v->longestu = secs;
+ if (secs <= 999)
+ /* sss.ssssss */
+ fmtlen = iwidth(secs) + 6 + 1;
+ else
+ /* hh:mm:ss.ss */
+ fmtlen = iwidth((secs + 1) / SECSPERHOUR)
+ + 2 + 1 + 2 + 1 + 2 + 1;
+ if (fmtlen > v->width)
+ v->width = fmtlen;
+ }
+ return;
+ }
+
+ if (secs < 999)
+ (void)printf( "%*lu.%.6lu", v->width - 6 - 1, secs, usec);
+ else {
+ uint h, m;
+ usec += 5000;
+ if (usec >= 1000000) {
+ usec -= 1000000;
+ secs++;
+ }
+ m = secs / SECSPERMIN;
+ secs -= m * SECSPERMIN;
+ h = m / MINSPERHOUR;
+ m -= h * MINSPERHOUR;
+ (void)printf( "%*u:%.2u:%.2lu.%.2lu", v->width - 9, h, m, secs,
+ usec / 10000u );
+ }
+}
+
+void
+lname(struct pinfo *pi, VARENT *ve, enum mode mode)
+{
+ struct kinfo_lwp *l = pi->li;
+ VAR *v;
+
+ v = ve->var;
+ if (l->l_name[0] != '\0') {
+ strprintorsetwidth(v, l->l_name, mode);
+ v->width = min(v->width, KI_LNAMELEN);
+ } else {
+ if (mode == PRINTMODE)
+ (void)printf("%-*s", v->width, "-");
+ }
+}
diff --git a/bin/ps/ps.1 b/bin/ps/ps.1
new file mode 100644
index 0000000..635ec3b
--- /dev/null
+++ b/bin/ps/ps.1
@@ -0,0 +1,706 @@
+.\" $NetBSD: ps.1,v 1.109 2017/08/28 05:57:37 wiz Exp $
+.\"
+.\" Copyright (c) 1980, 1990, 1991, 1993, 1994
+.\" The Regents of the University of California. All rights reserved.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\" notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must reproduce the above copyright
+.\" notice, this list of conditions and the following disclaimer in the
+.\" documentation and/or other materials provided with the distribution.
+.\" 3. Neither the name of the University nor the names of its contributors
+.\" may be used to endorse or promote products derived from this software
+.\" without specific prior written permission.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+.\" ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+.\" SUCH DAMAGE.
+.\"
+.\" @(#)ps.1 8.3 (Berkeley) 4/18/94
+.\"
+.Dd August 28, 2017
+.Dt PS 1
+.Os
+.Sh NAME
+.Nm ps
+.Nd process status
+.Sh SYNOPSIS
+.Nm
+.Op Fl AaCcdehjlmrSsTuvwx
+.Op Fl k Ar key
+.Op Fl M Ar core
+.Op Fl N Ar system
+.Op Fl O Ar fmt
+.Op Fl o Ar fmt
+.Op Fl p Ar pid
+.Op Fl t Ar tty
+.Op Fl U Ar user
+.Op Fl W Ar swap
+.Nm
+.Fl L
+.Sh DESCRIPTION
+.Nm
+displays a header line followed by lines containing information about
+running processes.
+By default, the display includes only processes that have
+controlling terminals and are owned by your uid.
+The default sort order of controlling terminal and
+(among processes with the same controlling terminal) process ID
+may be changed using the
+.Fl k , Fl m ,
+or
+.Fl r
+options.
+.Pp
+The information displayed for each process
+is selected based on a set of keywords (see the
+.Fl L ,
+.Fl O ,
+and
+.Fl o
+options).
+The default output format includes, for each process, the process' ID,
+controlling terminal, CPU time (including both user and system time),
+state, and associated command.
+.Pp
+The options are as follows:
+.Bl -tag -width XNXsystemXX
+.It Fl A
+Display information about all processes.
+This is equivalent to
+.Fl a Fl x .
+.It Fl a
+Display information about other users' processes as well as your own.
+Note that this does not display information about processes
+without controlling terminals.
+.It Fl C
+Change the way the CPU percentage is calculated by using a
+.Dq raw
+CPU calculation that ignores
+.Dq resident
+time (this normally has no effect).
+.It Fl c
+Do not display full command with arguments, but only the
+executable name.
+This may be somewhat confusing; for example, all
+.Xr sh 1
+scripts will show as
+.Dq sh .
+.It Fl d
+Arrange processes into descendancy order and prefix each command with
+indentation text showing sibling and parent/child relationships.
+If either of the
+.Fl m
+and
+.Fl r
+options are also used, they control how sibling processes are sorted
+relative to each other.
+.It Fl e
+Display the environment as well.
+The environment for other
+users' processes can only be displayed by the super-user.
+.It Fl h
+Repeat the information header as often as necessary to guarantee one
+header per page of information.
+.It Fl j
+Print information associated with the following keywords:
+.Ar user , pid , ppid , pgid , sess , jobc , state , tt , time ,
+and
+.Ar command .
+.It Fl k Ar key
+Sort the output using the space or comma separated list of keywords.
+Multiple sort keys may be specified, using any of the
+.Fl k , Fl m ,
+or
+.Fl r
+options.
+The default sort order is equivalent to
+.Fl k Ar tdev,pid .
+.It Fl L
+List the set of available keywords.
+.It Fl l
+Display information associated with the following keywords:
+.Ar uid , pid , ppid , cpu , pri , nice , vsz , rss , wchan , state ,
+.Ar tt , time ,
+and
+.Ar command .
+.It Fl M Ar core
+Extract values from the specified core file instead of the running system.
+.It Fl m
+Sort by memory usage.
+This is equivalent to
+.Fl k Ar vsz .
+.It Fl N Ar system
+Extract the name list from the specified system instead of the default,
+.Dq Pa /netbsd .
+Ignored unless
+.Fl M
+is specified.
+.It Fl O Ar fmt
+Display information associated with the space or comma separated list
+of keywords specified.
+The
+.Fl O
+option does not suppress the default display;
+it inserts additional keywords just after the
+.Ar pid
+keyword in the default display, or after the
+.Ar pid
+keyword (if any) in a non-default display specified before the
+first use of the
+.Fl O
+flag.
+Keywords inserted by multiple
+.Fl O
+options will be adjacent.
+.Pp
+An equals sign
+.Pq Dq \&=
+followed by a customised header string may be appended to a keyword,
+as described in more detail under the
+.Fl o
+option.
+.It Fl o Ar fmt
+Display information associated with the space or comma separated list
+of keywords specified.
+Use of the
+.Fl o
+option suppresses the set of keywords that would be displayed by default,
+or appends to the set of keywords specified by other options.
+.Pp
+An equals sign
+.Pq Dq \&=
+followed by a customised header string may be appended to a keyword.
+This causes the printed header to use the specified string instead of
+the default header associated with the keyword.
+.Pp
+Everything after the first equals sign is part of the customised
+header text, and this may include embedded spaces
+.Pq Dq " " ,
+commas
+.Pq Dq \&, ,
+or equals signs
+.Pq Dq \&= .
+To specify multiple keywords with customised headers, use multiple
+.Fl o
+or
+.Fl O
+options.
+.Pp
+If all the keywords to be displayed have customised headers,
+and all the customised headers are entirely empty,
+then the header line is not printed at all.
+.It Fl p Ar pid
+Display information associated with the specified process ID.
+.It Fl r
+Sort by current CPU usage.
+This is equivalent to
+.Fl k Ar %cpu .
+.It Fl S
+Change the way the process time is calculated by summing all exited
+children to their parent process.
+.It Fl s
+Display one line for each LWP, rather than one line for each process,
+and display information associated with the following keywords:
+.Ar uid , pid , ppid , cpu , lid , nlwp , pri , nice , vsz , rss ,
+.Ar wchan , lstate , tt , time ,
+and
+.Ar command .
+.It Fl T
+Display information about processes attached to the device associated
+with the standard input.
+.It Fl t Ar tty
+Display information about processes attached to the specified terminal
+device.
+Use a question mark
+.Pq Dq \&?
+for processes not attached to a
+terminal device and a minus sign
+.Pq Dq -
+for processes that have
+been revoked from their terminal device.
+.It Fl U Ar user
+Display processes belonging to the specified user,
+given either as a user name or a uid.
+.It Fl u
+Display information associated with the following keywords:
+.Ar user , pid , %cpu , %mem , vsz , rss , tt , state , start , time ,
+and
+.Ar command .
+The
+.Fl u
+option implies the
+.Fl r
+option.
+.It Fl v
+Display information associated with the following keywords:
+.Ar pid , state , time , sl , re , pagein , vsz , rss , lim , tsiz ,
+.Ar %cpu , %mem ,
+and
+.Ar command .
+The
+.Fl v
+option implies the
+.Fl m
+option.
+.It Fl W Ar swap
+Extract swap information from the specified file instead of the default,
+.Dq Pa /dev/drum .
+Ignored unless
+.Fl M
+is specified.
+.It Fl w
+Use 132 columns to display information instead of the default, which
+is your window size.
+If the
+.Fl w
+option is specified more than once,
+.Nm
+will use as many columns as necessary without regard to your window size.
+.It Fl x
+Also display information about processes without controlling terminals.
+.El
+.Pp
+A complete list of the available keywords are listed below.
+Some of these keywords are further specified as follows:
+.Bl -tag -width indent
+.It Ar %cpu
+The CPU utilization of the process; this is a decaying average over up to
+a minute of previous (real) time.
+Since the time base over which this is computed varies (since processes may
+be very young) it is possible for the sum of all %CPU fields to exceed 100%.
+.It Ar %mem
+The percentage of real memory used by this process.
+.It Ar flags
+The flags (in hexadecimal) associated with the process as in
+the include file
+.In sys/proc.h :
+.Bl -column P_NOCLDSTOP P_NOCLDSTOP compact
+.It Dv "P_ADVLOCK" Ta No "0x00000001 process may hold a POSIX advisory lock"
+.It Dv "P_CONTROLT" Ta No "0x00000002 process has a controlling terminal"
+.It Dv "P_NOCLDSTOP" Ta No "0x00000008 no" Dv SIGCHLD No when children stop
+.It Dv "P_PPWAIT" Ta No "0x00000010 parent is waiting for child to exec/exit"
+.It Dv "P_PROFIL" Ta No "0x00000020 process has started profiling"
+.It Dv "P_SELECT" Ta No "0x00000040 selecting; wakeup/waiting danger"
+.It Dv "P_SINTR" Ta No "0x00000080 sleep is interruptible"
+.It Dv "P_SUGID" Ta No "0x00000100 process had set id privileges since last exec"
+.It Dv "P_SYSTEM" Ta No "0x00000200 system process: no sigs or stats"
+.It Dv "P_TIMEOUT" Ta No "0x00000400 timing out during sleep"
+.It Dv "P_TRACED" Ta No "0x00000800 process is being traced"
+.It Dv "P_WAITED" Ta No "0x00001000 debugging process has waited for child"
+.It Dv "P_WEXIT" Ta No "0x00002000 working on exiting"
+.It Dv "P_EXEC" Ta No "0x00004000 process called" Xr execve 2
+.It Dv "P_OWEUPC" Ta No "0x00008000 owe process an addupc() call at next ast"
+.\" the routine addupc is not documented in the man pages
+.It Dv "P_NOCLDWAIT" Ta No "0x00020000 no zombies when children die"
+.It Dv "P_32" Ta No "0x00040000 32-bit process (used on 64-bit kernels)"
+.It Dv "P_BIGLOCK" Ta No "0x00080000 process needs kernel ``big lock'' to run"
+.It Dv "P_INEXEC" Ta No "0x00100000 process is exec'ing and cannot be traced"
+.El
+.It Ar lim
+The soft limit on memory used, specified via a call to
+.Xr setrlimit 2 .
+.It Ar lstart
+The exact time the command started, using the
+.Dq \&%c
+format described in
+.Xr strftime 3 .
+.It Ar nice
+The process scheduling increment (see
+.Xr setpriority 2 ) .
+.It Ar rss
+the real memory (resident set) size of the process (in 1024 byte units).
+.It Ar start
+The time the command started.
+If the command started less than 24 hours ago, the start time is
+displayed using the
+.Dq %l:%M%p
+format described in
+.Xr strftime 3 .
+If the command started less than 7 days ago, the start time is
+displayed using the
+.Dq %a%p
+format.
+Otherwise, the start time is displayed using the
+.Dq %e%b%y
+format.
+.It Ar state
+The state is given by a sequence of letters, for example,
+.Dq RNs .
+The first letter indicates the run state of the process:
+.Pp
+.Bl -tag -width indent -compact
+.It D
+Marks a process in device or other short term, uninterruptible wait.
+.It I
+Marks a process that is idle (sleeping interruptibly for longer than about
+.Dv MAXSLP
+(default 20) seconds).
+.It O
+Marks a process running on a processor.
+.It R
+Marks a runnable process, or one that is in the process of creation.
+.It S
+Marks a process that is sleeping interruptibly for less than about
+.Dv MAXSLP
+(default 20) seconds.
+.It T
+Marks a stopped process.
+.It U
+Marks a suspended process.
+.It Z
+Marks a dead process that has exited, but not been waited for (a
+.Dq zombie ) .
+.El
+.Pp
+Additional characters after these, if any, indicate additional state
+information:
+.Pp
+.Bl -tag -width indent -compact
+.It +
+The process is in the foreground process group of its control terminal.
+.It -
+The LWP is detached (can't be waited for).
+.It <
+The process has raised CPU scheduling priority.
+.It a
+The process is using scheduler activations (deprecated).
+.It E
+The process is in the process of exiting.
+.It K
+The process is a kernel thread or system process.
+.It l
+The process has multiple LWPs.
+.It N
+The process is niced (has reduced CPU scheduling priority) (see
+.Xr setpriority 2 ) .
+.It s
+The process is a session leader.
+.It V
+The process is suspended during a
+.Xr vfork 2 .
+.It X
+The process is being traced or debugged.
+.El
+.It Ar tt
+An abbreviation for the pathname of the controlling terminal, if any.
+The abbreviation consists of the two letters following
+.Dq Pa /dev/tty
+or, for the console,
+.Dq co .
+This is followed by a
+.Dq \&-
+if the process can no longer reach that
+controlling terminal (i.e., it has been revoked).
+.It Ar wchan
+The event (an address in the system) on which a process waits.
+When printed numerically, the initial part of the address is
+trimmed off and the result is printed in hex, for example, 0x80324000 prints
+as 324000.
+.El
+.Pp
+When printing using the
+.Ar command
+keyword, a process that has exited and has a parent that has not yet
+waited for the process (in other words, a zombie) is listed as
+.Dq Aq defunct ,
+and a process which is blocked while trying to exit is listed as
+.Dq Aq exiting .
+.Pp
+.Nm
+will try to locate the processes' argument vector from the user
+area in order to print the command name and arguments.
+This method is not reliable because a process is allowed to destroy this
+information.
+The
+.Ar ucomm
+(accounting) keyword will always contain the real command name as
+contained in the process structure's
+.Va p_comm
+field.
+.Pp
+If the command vector cannot be located (usually because it has not
+been set, as is the case of system processes and/or kernel threads)
+the command name is printed within square brackets.
+.Pp
+To indicate that the argument vector has been tampered with,
+.Nm
+will append the real command name to the output within parentheses
+if the basename of the first argument in the argument vector
+does not match the contents of the real command name.
+.Pp
+In addition,
+.Nm
+checks for the following two situations and does not append the
+real command name parenthesized:
+.Bl -tag -width indent
+.It -shellname
+The login process traditionally adds a
+.Sq -
+in front of the shell name to indicate a login shell.
+.Nm
+will not append parenthesized the command name if it matches with
+the name in the first argument of the argument vector, skipping
+the leading
+.Sq - .
+.It daemonname: current-activity
+Daemon processes frequently report their current activity by setting
+their name to be like
+.Dq daemonname: current-activity .
+.Nm
+will not append parenthesized the command name, if the string preceding the
+.Sq \&:
+in the first argument of the argument vector matches the command name.
+.El
+.Sh KEYWORDS
+The following is a complete list of the available keywords and their
+meanings.
+Several of them have aliases (keywords which are synonyms).
+.Pp
+.Bl -tag -width groupnames -compact
+.It Ar %cpu
+percentage CPU usage (alias
+.Ar pcpu )
+.It Ar %mem
+percentage memory usage (alias
+.Ar pmem )
+.It Ar acflag
+accounting flag (alias
+.Ar acflg )
+.It Ar comm
+command (the argv[0] value)
+.It Ar command
+command and arguments (alias
+.Ar args )
+.It Ar cpu
+short-term CPU usage factor (for scheduling)
+.It Ar cpuid
+CPU number the current process or lwp is running on.
+.It Ar ctime
+accumulated CPU time of all children that have exited
+.It Ar egid
+effective group id
+.It Ar egroup
+group name (from egid)
+.It Ar emul
+emulation name
+.It Ar etime
+elapsed time since the process was started, in the form
+.Li [[dd-]hh:]mm:ss
+.It Ar euid
+effective user id
+.It Ar euser
+user name (from euid)
+.It Ar flags
+the process flags, in hexadecimal (alias
+.Ar f )
+.It Ar gid
+effective group id
+.It Ar group
+group name (from gid)
+.It Ar groupnames
+group names (from group access list)
+.It Ar groups
+group access list
+.It Ar inblk
+total blocks read (alias
+.Ar inblock )
+.It Ar jobc
+job control count
+.It Ar ktrace
+tracing flags
+.It Ar ktracep
+tracing vnode
+.It Ar laddr
+kernel virtual address of the
+.Ft "struct lwp"
+belonging to the LWP.
+.It Ar lid
+ID of the LWP
+.It Ar lim
+memory use limit
+.It Ar lname
+descriptive name of the LWP
+.It Ar logname
+login name of user who started the process (alias
+.Ar login )
+.It Ar lstart
+time started
+.It Ar lstate
+symbolic LWP state
+.It Ar ltime
+CPU time of the LWP
+.It Ar majflt
+total page faults
+.It Ar minflt
+total page reclaims
+.It Ar msgrcv
+total messages received (reads from pipes/sockets)
+.It Ar msgsnd
+total messages sent (writes on pipes/sockets)
+.It Ar nice
+nice value (alias
+.Ar ni )
+.It Ar nivcsw
+total involuntary context switches
+.It Ar nlwp
+number of LWPs in the process
+.It Ar nsigs
+total signals taken (alias
+.Ar nsignals )
+.It Ar nvcsw
+total voluntary context switches
+.It Ar nwchan
+wait channel (as an address)
+.It Ar oublk
+total blocks written (alias
+.Ar oublock )
+.It Ar p_ru
+resource usage pointer (valid only for zombie)
+.It Ar paddr
+kernel virtual address of the
+.Ft "struct proc"
+belonging to the process.
+.It Ar pagein
+pageins (same as majflt)
+.It Ar pgid
+process group number
+.It Ar pid
+process ID
+.It Ar ppid
+parent process ID
+.It Ar pri
+scheduling priority
+.It Ar re
+core residency time (in seconds; 127 = infinity)
+.It Ar rgid
+real group ID
+.It Ar rlink
+reverse link on run queue, or 0
+.It Ar rlwp
+number of LWPs on a processor or run queue
+.It Ar rss
+resident set size
+.It Ar rsz
+resident set size + (text size / text use count) (alias
+.Ar rssize )
+.It Ar ruid
+real user ID
+.It Ar ruser
+user name (from ruid)
+.It Ar sess
+session pointer
+.It Ar sid
+session ID
+.It Ar sig
+pending signals (alias
+.Ar pending )
+.It Ar sigcatch
+caught signals (alias
+.Ar caught )
+.It Ar sigignore
+ignored signals (alias
+.Ar ignored )
+.It Ar sigmask
+blocked signals (alias
+.Ar blocked )
+.It Ar sl
+sleep time (in seconds; 127 = infinity)
+.It Ar start
+time started
+.It Ar state
+symbolic process state (alias
+.Ar stat )
+.It Ar stime
+accumulated system CPU time
+.It Ar svgid
+saved gid from a setgid executable
+.It Ar svgroup
+group name (from svgid)
+.It Ar svuid
+saved uid from a setuid executable
+.It Ar svuser
+user name (from svuid)
+.It Ar tdev
+control terminal device number
+.It Ar time
+accumulated CPU time, user + system (alias
+.Ar cputime )
+.It Ar tpgid
+control terminal process group ID
+.It Ar tsess
+control terminal session pointer
+.It Ar tsiz
+text size (in Kbytes)
+.It Ar tt
+control terminal name (two letter abbreviation)
+.It Ar tty
+full name of control terminal
+.It Ar uaddr
+kernel virtual address of the
+.Ft "struct user"
+belonging to the LWP.
+.It Ar ucomm
+name to be used for accounting
+.It Ar uid
+effective user ID
+.It Ar upr
+scheduling priority on return from system call (alias
+.Ar usrpri )
+.It Ar user
+user name (from uid)
+.It Ar utime
+accumulated user CPU time
+.It Ar vsz
+virtual size in Kbytes (alias
+.Ar vsize )
+.It Ar wchan
+wait channel (as a symbolic name)
+.It Ar xstat
+exit or stop status (valid only for stopped or zombie process)
+.El
+.Sh FILES
+.Bl -tag -width /var/run/kvm.db -compact
+.It Pa /dev
+special files and device names
+.It Pa /dev/drum
+default swap device
+.It Pa /var/run/dev.cdb
+/dev name database
+.It Pa /var/db/kvm.db
+system name list database
+.It Pa /netbsd
+default system name list
+.El
+.Sh SEE ALSO
+.Xr kill 1 ,
+.Xr pgrep 1 ,
+.Xr pkill 1 ,
+.Xr sh 1 ,
+.Xr w 1 ,
+.Xr kvm 3 ,
+.Xr strftime 3 ,
+.Xr dev_mkdb 8 ,
+.Xr pstat 8
+.Sh HISTORY
+A
+.Nm
+utility appeared in
+.At v3
+in section 8 of the manual.
+.Sh BUGS
+Since
+.Nm
+cannot run faster than the system and is run as any other scheduled
+process, the information it displays can never be exact.
diff --git a/bin/ps/ps.c b/bin/ps/ps.c
new file mode 100644
index 0000000..da26fc1
--- /dev/null
+++ b/bin/ps/ps.c
@@ -0,0 +1,947 @@
+/* $NetBSD: ps.c,v 1.91 2018/04/11 18:52:29 christos Exp $ */
+
+/*
+ * Copyright (c) 2000-2008 The NetBSD Foundation, Inc.
+ * All rights reserved.
+ *
+ * This code is derived from software contributed to The NetBSD Foundation
+ * by Simon Burge.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
+ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/*
+ * Copyright (c) 1990, 1993, 1994
+ * The Regents of the University of California. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+#ifndef lint
+__COPYRIGHT("@(#) Copyright (c) 1990, 1993, 1994\
+ The Regents of the University of California. All rights reserved.");
+#endif /* not lint */
+
+#ifndef lint
+#if 0
+static char sccsid[] = "@(#)ps.c 8.4 (Berkeley) 4/2/94";
+#else
+__RCSID("$NetBSD: ps.c,v 1.91 2018/04/11 18:52:29 christos Exp $");
+#endif
+#endif /* not lint */
+
+#include <sys/param.h>
+#include <sys/time.h>
+#include <sys/resource.h>
+#include <sys/lwp.h>
+#include <sys/proc.h>
+#include <sys/stat.h>
+#include <sys/ioctl.h>
+#include <sys/sysctl.h>
+
+#include <stddef.h>
+#include <ctype.h>
+#include <err.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <kvm.h>
+#include <limits.h>
+#include <locale.h>
+#include <math.h>
+#include <nlist.h>
+#include <paths.h>
+#include <pwd.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "ps.h"
+
+/*
+ * ARGOPTS must contain all option characters that take arguments
+ * (except for 't'!) - it is used in kludge_oldps_options()
+ */
+#define GETOPTSTR "aAcCdeghjk:LlM:mN:O:o:p:rSsTt:U:uvW:wx"
+#define ARGOPTS "kMNOopUW"
+
+struct varlist displaylist = SIMPLEQ_HEAD_INITIALIZER(displaylist);
+struct varlist sortlist = SIMPLEQ_HEAD_INITIALIZER(sortlist);
+
+int eval; /* exit value */
+int sumrusage; /* -S */
+int termwidth; /* width of screen (0 == infinity) */
+int totwidth; /* calculated width of requested variables */
+
+int needcomm, needenv, commandonly;
+uid_t myuid;
+
+static struct kinfo_lwp
+ *pick_representative_lwp(struct kinfo_proc2 *,
+ struct kinfo_lwp *, int);
+static struct kinfo_proc2
+ *getkinfo_kvm(kvm_t *, int, int, int *);
+static struct pinfo
+ *setpinfo(struct kinfo_proc2 *, int, int, int);
+static char *kludge_oldps_options(char *);
+static int pscomp(const void *, const void *);
+static void scanvars(void);
+__dead static void usage(void);
+static int parsenum(const char *, const char *);
+static void descendant_sort(struct pinfo *, int);
+
+char dfmt[] = "pid tt state time command";
+char jfmt[] = "user pid ppid pgid sess jobc state tt time command";
+char lfmt[] = "uid pid ppid cpu pri nice vsz rss wchan state tt time command";
+char sfmt[] = "uid pid ppid cpu lid nlwp pri nice vsz rss wchan lstate tt "
+ "ltime command";
+char ufmt[] = "user pid %cpu %mem vsz rss tt state start time command";
+char vfmt[] = "pid state time sl re pagein vsz rss lim tsiz %cpu %mem command";
+
+const char *default_fmt = dfmt;
+
+struct varent *Opos = NULL; /* -O flag inserts after this point */
+
+kvm_t *kd;
+
+static long long
+ttyname2dev(const char *ttname, int *xflg, int *what)
+{
+ struct stat sb;
+ const char *ttypath;
+ char pathbuf[MAXPATHLEN];
+
+ ttypath = NULL;
+ if (strcmp(ttname, "?") == 0) {
+ *xflg = 1;
+ return KERN_PROC_TTY_NODEV;
+ }
+ if (strcmp(ttname, "-") == 0)
+ return KERN_PROC_TTY_REVOKE;
+
+ if (strcmp(ttname, "co") == 0)
+ ttypath = _PATH_CONSOLE;
+ else if (strncmp(ttname, "pts/", 4) == 0 ||
+ strncmp(ttname, "tty", 3) == 0) {
+ (void)snprintf(pathbuf,
+ sizeof(pathbuf), "%s%s", _PATH_DEV, ttname);
+ ttypath = pathbuf;
+ } else if (*ttname != '/') {
+ (void)snprintf(pathbuf,
+ sizeof(pathbuf), "%s%s", _PATH_TTY, ttname);
+ ttypath = pathbuf;
+ } else
+ ttypath = ttname;
+ *what = KERN_PROC_TTY;
+ if (stat(ttypath, &sb) == -1) {
+ devmajor_t pts;
+ int serrno;
+
+ serrno = errno;
+ pts = getdevmajor("pts", S_IFCHR);
+ if (pts != NODEVMAJOR && strncmp(ttname, "pts/", 4) == 0) {
+ int ptsminor = atoi(ttname + 4);
+
+ snprintf(pathbuf, sizeof(pathbuf), "pts/%d", ptsminor);
+ if (strcmp(pathbuf, ttname) == 0 && ptsminor >= 0)
+ return makedev(pts, ptsminor);
+ }
+ errno = serrno;
+ err(EXIT_FAILURE, "%s", ttypath);
+ }
+ if (!S_ISCHR(sb.st_mode))
+ errx(EXIT_FAILURE, "%s: not a terminal", ttypath);
+ return sb.st_rdev;
+}
+
+int
+main(int argc, char *argv[])
+{
+ struct kinfo_proc2 *kinfo;
+ struct pinfo *pinfo;
+ struct varent *vent;
+ struct winsize ws;
+ struct kinfo_lwp *kl, *l;
+ int ch, i, j, fmt, lineno, descendancy, nentries, nlwps;
+ long long flag;
+ int calc_pcpu, prtheader, wflag, what, xflg, rawcpu, showlwps;
+ char *nlistf, *memf, *swapf, errbuf[_POSIX2_LINE_MAX];
+ char *ttname;
+
+ setprogname(argv[0]);
+ (void)setlocale(LC_ALL, "");
+
+ if ((ioctl(STDOUT_FILENO, TIOCGWINSZ, (char *)&ws) == -1 &&
+ ioctl(STDERR_FILENO, TIOCGWINSZ, (char *)&ws) == -1 &&
+ ioctl(STDIN_FILENO, TIOCGWINSZ, (char *)&ws) == -1) ||
+ ws.ws_col == 0)
+ termwidth = 79;
+ else
+ termwidth = ws.ws_col - 1;
+
+ if (argc > 1)
+ argv[1] = kludge_oldps_options(argv[1]);
+
+ descendancy = fmt = prtheader = wflag = xflg = rawcpu = showlwps = 0;
+ what = KERN_PROC_UID;
+ flag = myuid = getuid();
+ memf = nlistf = swapf = NULL;
+
+ while ((ch = getopt(argc, argv, GETOPTSTR)) != -1)
+ switch((char)ch) {
+ case 'A':
+ /* "-A" shows all processes, like "-ax" */
+ xflg = 1;
+ /*FALLTHROUGH*/
+ case 'a':
+ what = KERN_PROC_ALL;
+ flag = 0;
+ break;
+ case 'c':
+ commandonly = 1;
+ break;
+ case 'd':
+ descendancy = 1;
+ break;
+ case 'e': /* XXX set ufmt */
+ needenv = 1;
+ break;
+ case 'C':
+ rawcpu = 1;
+ break;
+ case 'g':
+ break; /* no-op */
+ case 'h':
+ prtheader = ws.ws_row > 5 ? ws.ws_row : 22;
+ break;
+ case 'j':
+ parsefmt(jfmt);
+ fmt = 1;
+ jfmt[0] = '\0';
+ break;
+ case 'k':
+ parsesort(optarg);
+ break;
+ case 'K':
+ break; /* no-op - was dontuseprocfs */
+ case 'L':
+ showkey();
+ return 0;
+ case 'l':
+ parsefmt(lfmt);
+ fmt = 1;
+ lfmt[0] = '\0';
+ break;
+ case 'M':
+ memf = optarg;
+ break;
+ case 'm':
+ parsesort("vsz");
+ break;
+ case 'N':
+ nlistf = optarg;
+ break;
+ case 'O':
+ /*
+ * If this is not the first -O option, insert
+ * just after the previous one.
+ *
+ * If there is no format yet, start with the default
+ * format, and insert after the pid column.
+ *
+ * If there is already a format, insert after
+ * the pid column, or at the end if there's no
+ * pid column.
+ */
+ if (!Opos) {
+ if (!fmt)
+ parsefmt(default_fmt);
+ Opos = varlist_find(&displaylist, "pid");
+ }
+ parsefmt_insert(optarg, &Opos);
+ fmt = 1;
+ break;
+ case 'o':
+ parsefmt(optarg);
+ fmt = 1;
+ break;
+ case 'p':
+ what = KERN_PROC_PID;
+ flag = parsenum(optarg, "process id");
+ xflg = 1;
+ break;
+ case 'r':
+ parsesort("%cpu");
+ break;
+ case 'S':
+ sumrusage = 1;
+ break;
+ case 's':
+ /* -L was already taken... */
+ showlwps = 1;
+ default_fmt = sfmt;
+ break;
+ case 'T':
+ if ((ttname = ttyname(STDIN_FILENO)) == NULL)
+ errx(EXIT_FAILURE, "stdin: not a terminal");
+ flag = ttyname2dev(ttname, &xflg, &what);
+ break;
+ case 't':
+ flag = ttyname2dev(optarg, &xflg, &what);
+ break;
+ case 'U':
+ if (*optarg != '\0') {
+ struct passwd *pw;
+
+ what = KERN_PROC_UID;
+ pw = getpwnam(optarg);
+ if (pw == NULL) {
+ flag = parsenum(optarg, "user name");
+ } else
+ flag = pw->pw_uid;
+ }
+ break;
+ case 'u':
+ parsefmt(ufmt);
+ parsesort("%cpu");
+ fmt = 1;
+ ufmt[0] = '\0';
+ break;
+ case 'v':
+ parsefmt(vfmt);
+ parsesort("vsz");
+ fmt = 1;
+ vfmt[0] = '\0';
+ break;
+ case 'W':
+ swapf = optarg;
+ break;
+ case 'w':
+ if (wflag)
+ termwidth = UNLIMITED;
+ else if (termwidth < 131)
+ termwidth = 131;
+ wflag++;
+ break;
+ case 'x':
+ xflg = 1;
+ break;
+ case '?':
+ default:
+ usage();
+ }
+ argc -= optind;
+ argv += optind;
+
+#define BACKWARD_COMPATIBILITY
+#ifdef BACKWARD_COMPATIBILITY
+ if (*argv) {
+ nlistf = *argv;
+ if (*++argv) {
+ memf = *argv;
+ if (*++argv)
+ swapf = *argv;
+ }
+ }
+#endif
+
+ if (memf == NULL) {
+ kd = kvm_openfiles(NULL, NULL, NULL, KVM_NO_FILES, errbuf);
+ donlist_sysctl();
+ } else
+ kd = kvm_openfiles(nlistf, memf, swapf, O_RDONLY, errbuf);
+
+ if (kd == NULL)
+ errx(EXIT_FAILURE, "%s", errbuf);
+
+ if (!fmt)
+ parsefmt(default_fmt);
+
+ /* Add default sort criteria */
+ parsesort("tdev,pid");
+ calc_pcpu = 0;
+ SIMPLEQ_FOREACH(vent, &sortlist, next) {
+ if (vent->var->flag & LWP || vent->var->type == UNSPECIFIED)
+ warnx("Cannot sort on %s, sort key ignored",
+ vent->var->name);
+ if (vent->var->type == PCPU)
+ calc_pcpu = 1;
+ }
+ if (!calc_pcpu)
+ SIMPLEQ_FOREACH(vent, &displaylist, next)
+ if (vent->var->type == PCPU) {
+ calc_pcpu = 1;
+ break;
+ }
+
+ /*
+ * scan requested variables, noting what structures are needed.
+ */
+ scanvars();
+
+ /*
+ * select procs
+ */
+ if (!(kinfo = getkinfo_kvm(kd, what, flag, &nentries)))
+ errx(EXIT_FAILURE, "%s", kvm_geterr(kd));
+ if (nentries == 0) {
+ printheader();
+ return 1;
+ }
+ pinfo = setpinfo(kinfo, nentries, calc_pcpu, rawcpu);
+
+ /*
+ * sort proc list
+ */
+ qsort(pinfo, nentries, sizeof(struct pinfo), pscomp);
+
+ /*
+ * We want things in descendant order
+ */
+ if (descendancy)
+ descendant_sort(pinfo, nentries);
+
+ /*
+ * For each proc, call each variable output function in
+ * "setwidth" mode to determine the widest element of
+ * the column.
+ */
+
+ for (i = 0; i < nentries; i++) {
+ struct pinfo *pi = &pinfo[i];
+ struct kinfo_proc2 *ki = pi->ki;
+
+ if (xflg == 0 && (ki->p_tdev == (uint32_t)NODEV ||
+ (ki->p_flag & P_CONTROLT) == 0))
+ continue;
+
+ kl = kvm_getlwps(kd, ki->p_pid, ki->p_paddr,
+ sizeof(struct kinfo_lwp), &nlwps);
+ if (kl == 0)
+ nlwps = 0;
+ if (showlwps == 0) {
+ l = pick_representative_lwp(ki, kl, nlwps);
+ SIMPLEQ_FOREACH(vent, &displaylist, next)
+ OUTPUT(vent, l, pi, ki, WIDTHMODE);
+ } else {
+ /* The printing is done with the loops
+ * reversed, but here we don't need that,
+ * and this improves the code locality a bit.
+ */
+ SIMPLEQ_FOREACH(vent, &displaylist, next)
+ for (j = 0; j < nlwps; j++)
+ OUTPUT(vent, &kl[j], pi, ki, WIDTHMODE);
+ }
+ }
+ /*
+ * Print header - AFTER determining process field widths.
+ * printheader() also adds up the total width of all
+ * fields the first time it's called.
+ */
+ printheader();
+ /*
+ * For each proc, call each variable output function in
+ * print mode.
+ */
+ for (i = lineno = 0; i < nentries; i++) {
+ struct pinfo *pi = &pinfo[i];
+ struct kinfo_proc2 *ki = pi->ki;
+
+ if (xflg == 0 && (ki->p_tdev == (uint32_t)NODEV ||
+ (ki->p_flag & P_CONTROLT ) == 0))
+ continue;
+ kl = kvm_getlwps(kd, ki->p_pid, (u_long)ki->p_paddr,
+ sizeof(struct kinfo_lwp), &nlwps);
+ if (kl == 0)
+ nlwps = 0;
+ if (showlwps == 0) {
+ l = pick_representative_lwp(ki, kl, nlwps);
+ SIMPLEQ_FOREACH(vent, &displaylist, next) {
+ OUTPUT(vent, l, pi, ki, PRINTMODE);
+ if (SIMPLEQ_NEXT(vent, next) != NULL)
+ (void)putchar(' ');
+ }
+ (void)putchar('\n');
+ if (prtheader && lineno++ == prtheader - 4) {
+ (void)putchar('\n');
+ printheader();
+ lineno = 0;
+ }
+ } else {
+ for (j = 0; j < nlwps; j++) {
+ SIMPLEQ_FOREACH(vent, &displaylist, next) {
+ OUTPUT(vent, &kl[j], pi, ki, PRINTMODE);
+ if (SIMPLEQ_NEXT(vent, next) != NULL)
+ (void)putchar(' ');
+ }
+ (void)putchar('\n');
+ if (prtheader && lineno++ == prtheader - 4) {
+ (void)putchar('\n');
+ printheader();
+ lineno = 0;
+ }
+ }
+ }
+ }
+ return eval;
+}
+
+static struct kinfo_lwp *
+pick_representative_lwp(struct kinfo_proc2 *ki, struct kinfo_lwp *kl, int nlwps)
+{
+ int i, onproc, running, sleeping, stopped, suspended;
+ static struct kinfo_lwp zero_lwp;
+
+ if (kl == 0)
+ return &zero_lwp;
+
+ /* Trivial case: only one LWP */
+ if (nlwps == 1)
+ return kl;
+
+ switch (ki->p_realstat) {
+ case SSTOP:
+ case SACTIVE:
+ /* Pick the most live LWP */
+ onproc = running = sleeping = stopped = suspended = -1;
+ for (i = 0; i < nlwps; i++) {
+ switch (kl[i].l_stat) {
+ case LSONPROC:
+ onproc = i;
+ break;
+ case LSRUN:
+ running = i;
+ break;
+ case LSSLEEP:
+ sleeping = i;
+ break;
+ case LSSTOP:
+ stopped = i;
+ break;
+ case LSSUSPENDED:
+ suspended = i;
+ break;
+ }
+ }
+ if (onproc != -1)
+ return &kl[onproc];
+ if (running != -1)
+ return &kl[running];
+ if (sleeping != -1)
+ return &kl[sleeping];
+ if (stopped != -1)
+ return &kl[stopped];
+ if (suspended != -1)
+ return &kl[suspended];
+ break;
+ case SZOMB:
+ /* First will do */
+ return kl;
+ break;
+ }
+ /* Error condition! */
+ warnx("Inconsistent LWP state for process %d", ki->p_pid);
+ return kl;
+}
+
+static struct kinfo_proc2 *
+getkinfo_kvm(kvm_t *kdp, int what, int flag, int *nentriesp)
+{
+
+ return (kvm_getproc2(kdp, what, flag, sizeof(struct kinfo_proc2),
+ nentriesp));
+}
+
+static struct pinfo *
+setpinfo(struct kinfo_proc2 *ki, int nentries, int calc_pcpu, int rawcpu)
+{
+ struct pinfo *pi;
+ int i;
+
+ pi = calloc(nentries, sizeof(*pi));
+ if (pi == NULL)
+ err(EXIT_FAILURE, "calloc");
+
+ if (calc_pcpu && !nlistread)
+ donlist();
+
+ for (i = 0; i < nentries; i++) {
+ pi[i].ki = &ki[i];
+ if (!calc_pcpu)
+ continue;
+ if (ki[i].p_swtime == 0 || ki[i].p_realstat == SZOMB) {
+ pi[i].pcpu = 0.0;
+ continue;
+ }
+ pi[i].pcpu = 100.0 * (double)ki[i].p_pctcpu / fscale;
+ if (!rawcpu)
+ pi[i].pcpu /= 1.0 - exp(ki[i].p_swtime * log_ccpu);
+ }
+
+ return pi;
+}
+
+static void
+scanvars(void)
+{
+ struct varent *vent;
+ VAR *v;
+
+ SIMPLEQ_FOREACH(vent, &displaylist, next) {
+ v = vent->var;
+ if (v->flag & COMM) {
+ needcomm = 1;
+ break;
+ }
+ }
+}
+
+static int
+pscomp(const void *a, const void *b)
+{
+ const struct pinfo *pa = (const struct pinfo *)a;
+ const struct pinfo *pb = (const struct pinfo *)b;
+ const struct kinfo_proc2 *ka = pa->ki;
+ const struct kinfo_proc2 *kb = pb->ki;
+
+ int i;
+ int64_t i64;
+ VAR *v;
+ struct varent *ve;
+ const sigset_t *sa, *sb;
+
+#define V_SIZE(k) ((k)->p_vm_msize)
+#define RDIFF_N(t, n) \
+ if (((const t *)((const char *)ka + v->off))[n] > ((const t *)((const char *)kb + v->off))[n]) \
+ return 1; \
+ if (((const t *)((const char *)ka + v->off))[n] < ((const t *)((const char *)kb + v->off))[n]) \
+ return -1;
+
+#define RDIFF(type) RDIFF_N(type, 0); continue
+
+ SIMPLEQ_FOREACH(ve, &sortlist, next) {
+ v = ve->var;
+ if (v->flag & LWP)
+ /* LWP structure not available (yet) */
+ continue;
+ /* Sort on pvar() fields, + a few others */
+ switch (v->type) {
+ case CHAR:
+ RDIFF(char);
+ case UCHAR:
+ RDIFF(u_char);
+ case SHORT:
+ RDIFF(short);
+ case USHORT:
+ RDIFF(ushort);
+ case INT:
+ RDIFF(int);
+ case UINT:
+ RDIFF(uint);
+ case LONG:
+ RDIFF(long);
+ case ULONG:
+ RDIFF(ulong);
+ case INT32:
+ RDIFF(int32_t);
+ case UINT32:
+ RDIFF(uint32_t);
+ case SIGLIST:
+ sa = (const void *)((const char *)ka + v->off);
+ sb = (const void *)((const char *)kb + v->off);
+ i = 0;
+ do {
+ if (sa->__bits[i] > sb->__bits[i])
+ return 1;
+ if (sa->__bits[i] < sb->__bits[i])
+ return -1;
+ i++;
+ } while (i < (int)__arraycount(sa->__bits));
+ continue;
+ case INT64:
+ RDIFF(int64_t);
+ case KPTR:
+ case KPTR24:
+ case UINT64:
+ RDIFF(uint64_t);
+ case TIMEVAL:
+ /* compare xxx_sec then xxx_usec */
+ RDIFF_N(uint32_t, 0);
+ RDIFF_N(uint32_t, 1);
+ continue;
+ case CPUTIME:
+ i64 = ka->p_rtime_sec * 1000000 + ka->p_rtime_usec;
+ i64 -= kb->p_rtime_sec * 1000000 + kb->p_rtime_usec;
+ if (sumrusage) {
+ i64 += ka->p_uctime_sec * 1000000
+ + ka->p_uctime_usec;
+ i64 -= kb->p_uctime_sec * 1000000
+ + kb->p_uctime_usec;
+ }
+ if (i64 != 0)
+ return i64 > 0 ? 1 : -1;
+ continue;
+ case PCPU:
+ i = pb->pcpu - pa->pcpu;
+ if (i != 0)
+ return i;
+ continue;
+ case VSIZE:
+ i = V_SIZE(kb) - V_SIZE(ka);
+ if (i != 0)
+ return i;
+ continue;
+
+ default:
+ /* Ignore everything else */
+ break;
+ }
+ }
+ return 0;
+
+#undef VSIZE
+}
+
+/*
+ * ICK (all for getopt), would rather hide the ugliness
+ * here than taint the main code.
+ *
+ * ps foo -> ps -foo
+ * ps 34 -> ps -p34
+ *
+ * The old convention that 't' with no trailing tty arg means the user's
+ * tty, is only supported if argv[1] doesn't begin with a '-'. This same
+ * feature is available with the option 'T', which takes no argument.
+ */
+static char *
+kludge_oldps_options(char *s)
+{
+ size_t len;
+ char *newopts, *ns, *cp;
+
+ len = strlen(s);
+ if ((newopts = ns = malloc(len + 3)) == NULL)
+ err(EXIT_FAILURE, NULL);
+ /*
+ * options begin with '-'
+ */
+ if (*s != '-')
+ *ns++ = '-'; /* add option flag */
+ /*
+ * gaze to end of argv[1]
+ */
+ cp = s + len - 1;
+ /*
+ * if the last letter is a 't' flag and there are no other option
+ * characters that take arguments (eg U, p, o) in the option
+ * string and the option string doesn't start with a '-' then
+ * convert to 'T' (meaning *this* terminal, i.e. ttyname(0)).
+ */
+ if (*cp == 't' && *s != '-' && strpbrk(s, ARGOPTS) == NULL)
+ *cp = 'T';
+ else {
+ /*
+ * otherwise check for trailing number, which *may* be a
+ * pid.
+ */
+ while (cp >= s && isdigit((unsigned char)*cp))
+ --cp;
+ }
+ cp++;
+ memmove(ns, s, (size_t)(cp - s)); /* copy up to trailing number */
+ ns += cp - s;
+ /*
+ * if there's a trailing number, and not a preceding 'p' (pid) or
+ * 't' (tty) flag, then assume it's a pid and insert a 'p' flag.
+ */
+ if (isdigit((unsigned char)*cp) &&
+ (cp == s || (cp[-1] != 'U' && cp[-1] != 't' && cp[-1] != 'p' &&
+ cp[-1] != '/' && (cp - 1 == s || cp[-2] != 't'))))
+ *ns++ = 'p';
+ /* and append the number */
+ (void)strcpy(ns, cp); /* XXX strcpy is safe here */
+
+ return (newopts);
+}
+
+static int
+parsenum(const char *str, const char *msg)
+{
+ char *ep;
+ unsigned long ul;
+
+ ul = strtoul(str, &ep, 0);
+
+ if (*str == '\0' || *ep != '\0')
+ errx(EXIT_FAILURE, "Invalid %s: `%s'", msg, str);
+
+ if (ul > INT_MAX)
+ errx(EXIT_FAILURE, "Out of range %s: `%s'", msg, str);
+
+ return (int)ul;
+}
+
+static void
+descendant_sort(struct pinfo *ki, int items)
+{
+ int dst, lvl, maxlvl, n, ndst, nsrc, siblings, src;
+ unsigned char *path;
+ struct pinfo kn;
+
+ /*
+ * First, sort the entries by descendancy, tracking the descendancy
+ * depth in the level field.
+ */
+ src = 0;
+ maxlvl = 0;
+ while (src < items) {
+ if (ki[src].level) {
+ src++;
+ continue;
+ }
+ for (nsrc = 1; src + nsrc < items; nsrc++)
+ if (!ki[src + nsrc].level)
+ break;
+
+ for (dst = 0; dst < items; dst++) {
+ if (ki[dst].ki->p_pid == ki[src].ki->p_pid)
+ continue;
+ if (ki[dst].ki->p_pid == ki[src].ki->p_ppid)
+ break;
+ }
+
+ if (dst == items) {
+ src += nsrc;
+ continue;
+ }
+
+ for (ndst = 1; dst + ndst < items; ndst++)
+ if (ki[dst + ndst].level <= ki[dst].level)
+ break;
+
+ for (n = src; n < src + nsrc; n++) {
+ ki[n].level += ki[dst].level + 1;
+ if (maxlvl < ki[n].level)
+ maxlvl = ki[n].level;
+ }
+
+ while (nsrc) {
+ if (src < dst) {
+ kn = ki[src];
+ memmove(ki + src, ki + src + 1,
+ (dst - src + ndst - 1) * sizeof *ki);
+ ki[dst + ndst - 1] = kn;
+ nsrc--;
+ dst--;
+ ndst++;
+ } else if (src != dst + ndst) {
+ kn = ki[src];
+ memmove(ki + dst + ndst + 1, ki + dst + ndst,
+ (src - dst - ndst) * sizeof *ki);
+ ki[dst + ndst] = kn;
+ ndst++;
+ nsrc--;
+ src++;
+ } else {
+ ndst += nsrc;
+ src += nsrc;
+ nsrc = 0;
+ }
+ }
+ }
+
+ /*
+ * Now populate prefix (instead of level) with the command
+ * prefix used to show descendancies.
+ */
+ path = malloc((maxlvl + 7) / 8);
+ memset(path, '\0', (maxlvl + 7) / 8);
+ for (src = 0; src < items; src++) {
+ if ((lvl = ki[src].level) == 0) {
+ ki[src].prefix = NULL;
+ continue;
+ }
+ if ((ki[src].prefix = malloc(lvl * 2 + 1)) == NULL)
+ errx(EXIT_FAILURE, "malloc failed");
+ for (n = 0; n < lvl - 2; n++) {
+ ki[src].prefix[n * 2] =
+ path[n / 8] & 1 << (n % 8) ? '|' : ' ';
+ ki[src].prefix[n * 2 + 1] = ' ';
+
+ }
+ if (n == lvl - 2) {
+ /* Have I any more siblings? */
+ for (siblings = 0, dst = src + 1; dst < items; dst++) {
+ if (ki[dst].level > lvl)
+ continue;
+ if (ki[dst].level == lvl)
+ siblings = 1;
+ break;
+ }
+ if (siblings)
+ path[n / 8] |= 1 << (n % 8);
+ else
+ path[n / 8] &= ~(1 << (n % 8));
+ ki[src].prefix[n * 2] = siblings ? '|' : '`';
+ ki[src].prefix[n * 2 + 1] = '-';
+ n++;
+ }
+ strcpy(ki[src].prefix + n * 2, "- ");
+ }
+ free(path);
+}
+
+static void
+usage(void)
+{
+
+ (void)fprintf(stderr,
+ "usage:\t%s\n\t %s\n\t%s\n",
+ "ps [-AaCcdehjlmrSsTuvwx] [-k key] [-M core] [-N system] [-O fmt]",
+ "[-o fmt] [-p pid] [-t tty] [-U user] [-W swap]",
+ "ps -L");
+ exit(1);
+ /* NOTREACHED */
+}
diff --git a/bin/ps/ps.h b/bin/ps/ps.h
new file mode 100644
index 0000000..7d8ddb8
--- /dev/null
+++ b/bin/ps/ps.h
@@ -0,0 +1,104 @@
+/* $NetBSD: ps.h,v 1.29 2016/12/02 21:59:03 christos Exp $ */
+
+/*-
+ * Copyright (c) 1990, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * @(#)ps.h 8.1 (Berkeley) 5/31/93
+ */
+
+#include <sys/queue.h>
+
+#define UNLIMITED 0 /* unlimited terminal width */
+
+enum mode {
+ PRINTMODE = 0, /* print values */
+ WIDTHMODE = 1 /* determine width of column */
+};
+
+enum type {
+ UNSPECIFIED,
+ CHAR, UCHAR, SHORT, USHORT, INT, UINT, LONG, ULONG,
+ KPTR, KPTR24, INT32, UINT32, SIGLIST, INT64, UINT64,
+ TIMEVAL, CPUTIME, PCPU, VSIZE
+};
+
+/* Variables. */
+typedef SIMPLEQ_HEAD(varlist, varent) VARLIST;
+
+typedef struct varent {
+ SIMPLEQ_ENTRY(varent) next;
+ struct var *var;
+} VARENT;
+
+struct pinfo {
+ struct kinfo_proc2 *ki;
+ struct kinfo_lwp *li;
+ char *prefix;
+ int level;
+ double pcpu;
+};
+
+typedef struct var {
+ const char *name; /* name(s) of variable */
+ const char *header; /* header, possibly changed from default */
+#define COMM 0x01 /* needs exec arguments and environment (XXX) */
+#define ARGV0 0x02 /* only print argv[0] */
+#define LJUST 0x04 /* left adjust on output (trailing blanks) */
+#define INF127 0x08 /* 127 = infinity: if > 127, print 127. */
+#define LWP 0x10 /* dispatch to kinfo_lwp routine */
+#define UAREA 0x20 /* need to check p_uvalid */
+#define ALIAS 0x40 /* entry is alias for 'header' */
+ u_int flag;
+ /* output routine */
+ void (*oproc)(struct pinfo *pi, struct varent *, enum mode);
+ /*
+ * The following (optional) elements are hooks for passing information
+ * to the generic output routine: pvar (that which prints simple
+ * elements from struct kinfo_proc2).
+ */
+ int off; /* offset in structure */
+ enum type type; /* type of element */
+ const char *fmt; /* printf format */
+
+ /* current longest element */
+ int width; /* printing width */
+ int64_t longestp; /* longest positive signed value */
+ int64_t longestn; /* longest negative signed value */
+ u_int64_t longestu; /* longest unsigned value */
+ double longestpd; /* longest positive double */
+ double longestnd; /* longest negative double */
+} VAR;
+
+
+#define OUTPUT(vent, kl, pi, ki, mode) do { \
+ if ((vent)->var->flag & LWP) \
+ pi->li = kl; \
+ ((vent)->var->oproc)(pi, (vent), (mode)); \
+ } while (/*CONSTCOND*/ 0)
+
+#include "extern.h"
diff --git a/bin/pwd/pwd.1 b/bin/pwd/pwd.1
new file mode 100644
index 0000000..bbdaf32
--- /dev/null
+++ b/bin/pwd/pwd.1
@@ -0,0 +1,110 @@
+.\" $NetBSD: pwd.1,v 1.27 2017/07/04 06:50:37 wiz Exp $
+.\"
+.\" Copyright (c) 1990, 1993
+.\" The Regents of the University of California. All rights reserved.
+.\"
+.\" This code is derived from software contributed to Berkeley by
+.\" the Institute of Electrical and Electronics Engineers, Inc.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\" notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must reproduce the above copyright
+.\" notice, this list of conditions and the following disclaimer in the
+.\" documentation and/or other materials provided with the distribution.
+.\" 3. Neither the name of the University nor the names of its contributors
+.\" may be used to endorse or promote products derived from this software
+.\" without specific prior written permission.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+.\" ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+.\" SUCH DAMAGE.
+.\"
+.\" @(#)pwd.1 8.2 (Berkeley) 4/28/95
+.\"
+.Dd August 12, 2016
+.Dt PWD 1
+.Os
+.Sh NAME
+.Nm pwd
+.Nd return working directory name
+.Sh SYNOPSIS
+.Nm
+.Op Fl LP
+.Sh DESCRIPTION
+.Nm
+writes the absolute pathname of the current working directory to
+the standard output.
+.Pp
+The following options are available:
+.Bl -tag -width indent
+.It Fl L
+If the
+.Ev PWD
+environment variable is an absolute pathname that contains
+neither "/./" nor "/../" and references the current directory, then
+.Ev PWD
+is assumed to be the name of the current directory.
+.It Fl P
+Print the physical path to the current working directory, with symbolic
+links in the path resolved.
+.El
+.Pp
+The default for the
+.Nm
+command is
+.Fl P .
+.Pp
+.Nm
+is usually provided as a shell builtin (which may have a different
+default).
+.Sh EXIT STATUS
+.Ex -std pwd
+.Sh SEE ALSO
+.Xr cd 1 ,
+.Xr csh 1 ,
+.Xr ksh 1 ,
+.Xr sh 1 ,
+.Xr getcwd 3
+.Sh STANDARDS
+The
+.Nm
+utility is expected to be conforming to
+.St -p1003.1 ,
+except that the default is
+.Fl P
+not
+.Fl L .
+.Sh HISTORY
+A
+.Nm
+utility appeared in
+.At v5 .
+.Sh BUGS
+In
+.Xr csh 1
+the command
+.Ic dirs
+is always faster (although it can give a different answer in the rare case
+that the current directory or a containing directory was moved after
+the shell descended into it).
+.Pp
+.Nm
+.Fl L
+relies on the file system having unique inode numbers.
+If this is not true (e.g., on FAT file systems) then
+.Nm
+.Fl L
+may fail to detect that
+.Ev PWD
+is incorrect.
diff --git a/bin/pwd/pwd.c b/bin/pwd/pwd.c
new file mode 100644
index 0000000..27888eb
--- /dev/null
+++ b/bin/pwd/pwd.c
@@ -0,0 +1,143 @@
+/* $NetBSD: pwd.c,v 1.22 2011/08/29 14:51:19 joerg Exp $ */
+
+/*
+ * Copyright (c) 1991, 1993, 1994
+ * The Regents of the University of California. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+#ifndef lint
+__COPYRIGHT("@(#) Copyright (c) 1991, 1993, 1994\
+ The Regents of the University of California. All rights reserved.");
+#endif /* not lint */
+
+#ifndef lint
+#if 0
+static char sccsid[] = "@(#)pwd.c 8.3 (Berkeley) 4/1/94";
+#else
+__RCSID("$NetBSD: pwd.c,v 1.22 2011/08/29 14:51:19 joerg Exp $");
+#endif
+#endif /* not lint */
+
+#include <sys/param.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+
+#include <err.h>
+#include <errno.h>
+#include <locale.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+static char *getcwd_logical(void);
+__dead static void usage(void);
+
+/*
+ * Note that EEE Std 1003.1, 2003 requires that the default be -L.
+ * This is inconsistent with the historic behaviour of everything
+ * except the ksh builtin.
+ * To avoid breaking scripts the default has been kept as -P.
+ * (Some scripts run /bin/pwd in order to get 'pwd -P'.)
+ */
+
+int
+main(int argc, char *argv[])
+{
+ int ch, lFlag;
+ const char *p;
+
+ setprogname(argv[0]);
+ (void)setlocale(LC_ALL, "");
+
+ lFlag = 0;
+ while ((ch = getopt(argc, argv, "LP")) != -1) {
+ switch (ch) {
+ case 'L':
+ lFlag = 1;
+ break;
+ case 'P':
+ lFlag = 0;
+ break;
+ case '?':
+ default:
+ usage();
+ }
+ }
+ argc -= optind;
+ argv += optind;
+
+ if (argc != 0)
+ usage();
+
+ if (lFlag)
+ p = getcwd_logical();
+ else
+ p = NULL;
+ if (p == NULL)
+ p = getcwd(NULL, 0);
+
+ if (p == NULL)
+ err(EXIT_FAILURE, NULL);
+
+ (void)printf("%s\n", p);
+
+ exit(EXIT_SUCCESS);
+ /* NOTREACHED */
+}
+
+static char *
+getcwd_logical(void)
+{
+ char *pwd;
+ struct stat s_pwd, s_dot;
+
+ /* Check $PWD -- if it's right, it's fast. */
+ pwd = getenv("PWD");
+ if (pwd == NULL)
+ return NULL;
+ if (pwd[0] != '/')
+ return NULL;
+ if (strstr(pwd, "/./") != NULL)
+ return NULL;
+ if (strstr(pwd, "/../") != NULL)
+ return NULL;
+ if (stat(pwd, &s_pwd) == -1 || stat(".", &s_dot) == -1)
+ return NULL;
+ if (s_pwd.st_dev != s_dot.st_dev || s_pwd.st_ino != s_dot.st_ino)
+ return NULL;
+ return pwd;
+}
+
+static void
+usage(void)
+{
+ (void)fprintf(stderr, "usage: %s [-LP]\n", getprogname());
+ exit(EXIT_FAILURE);
+ /* NOTREACHED */
+}
diff --git a/bin/rm/rm.1 b/bin/rm/rm.1
new file mode 100644
index 0000000..d7c5720
--- /dev/null
+++ b/bin/rm/rm.1
@@ -0,0 +1,215 @@
+.\" $NetBSD: rm.1,v 1.29 2017/07/03 21:33:23 wiz Exp $
+.\"
+.\" Copyright (c) 1990, 1993, 1994, 2003
+.\" The Regents of the University of California. All rights reserved.
+.\"
+.\" This code is derived from software contributed to Berkeley by
+.\" the Institute of Electrical and Electronics Engineers, Inc.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\" notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must reproduce the above copyright
+.\" notice, this list of conditions and the following disclaimer in the
+.\" documentation and/or other materials provided with the distribution.
+.\" 3. Neither the name of the University nor the names of its contributors
+.\" may be used to endorse or promote products derived from this software
+.\" without specific prior written permission.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+.\" ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+.\" SUCH DAMAGE.
+.\"
+.\" @(#)rm.1 8.5 (Berkeley) 12/5/94
+.\"
+.Dd August 12, 2016
+.Dt RM 1
+.Os
+.Sh NAME
+.Nm rm
+.Nd remove directory entries
+.Sh SYNOPSIS
+.Nm
+.Op Fl f | Fl i
+.Op Fl dPRrvWx
+.Ar
+.Sh DESCRIPTION
+The
+.Nm
+utility attempts to remove the non-directory type files specified on the
+command line.
+If the permissions of the file do not permit writing, and the standard
+input device is a terminal, the user is prompted (on the standard error
+output) for confirmation.
+.Pp
+The options are as follows:
+.Bl -tag -width flag
+.It Fl d
+Attempt to remove directories as well as other types of files.
+.It Fl f
+Attempt to remove the files without prompting for confirmation,
+regardless of the file's permissions.
+If the file does not exist, do not display a diagnostic message or modify
+the exit status to reflect an error.
+The
+.Fl f
+option overrides any previous
+.Fl i
+options.
+.It Fl i
+Request confirmation before attempting to remove each file, regardless of
+the file's permissions, or whether or not the standard input device is a
+terminal.
+The
+.Fl i
+option overrides any previous
+.Fl f
+options.
+.It Fl P
+Overwrite regular files before deleting them.
+Files are overwritten three times, first with the byte pattern 0xff,
+then 0x00, and then with random data, before they are deleted.
+Some care is taken to ensure that the data are actually written to
+disk, but this cannot be guaranteed, even on traditional filesystems;
+on log-structured filesystems or if any block-journaling scheme is
+in use, this option is completely useless.
+If the file cannot be
+overwritten, it will not be removed.
+.It Fl R
+Attempt to remove the file hierarchy rooted in each file argument.
+The
+.Fl R
+option implies the
+.Fl d
+option.
+If the
+.Fl i
+option is specified, the user is prompted for confirmation before
+each directory's contents are processed (as well as before the attempt
+is made to remove the directory).
+If the user does not respond affirmatively, the file hierarchy rooted in
+that directory is skipped.
+.It Fl r
+Equivalent to
+.Fl R .
+.It Fl v
+Cause
+.Nm
+to be verbose, showing files as they are processed.
+.It Fl W
+Attempts to undelete the named files.
+Currently, this option can only be used to recover
+files covered by whiteouts.
+.It Fl x
+When removing a hierarchy, do not cross mount points.
+.El
+.Pp
+The
+.Nm
+utility removes symbolic links, not the files referenced by the links.
+.Pp
+It is an error to attempt to remove the files ``.'' and ``..''.
+.Sh EXIT STATUS
+The
+.Nm
+utility exits 0 if all of the named files or file hierarchies were removed,
+or if the
+.Fl f
+option was specified and all of the existing files or file hierarchies were
+removed.
+If an error occurs,
+.Nm
+exits with a value >0.
+.Sh EXAMPLES
+.Nm
+uses
+.Xr getopt 3
+standard argument processing.
+Removing filenames that begin with a dash
+.Pq e.g., Ar -file
+in the current directory which might otherwise be taken as option flags to
+.Nm
+can be accomplished as follows:
+.Pp
+.Ic "rm -- -file"
+.Pp
+or
+.Pp
+.Ic "rm ./-file"
+.Sh COMPATIBILITY
+The
+.Nm
+utility differs from historical implementations in that the
+.Fl f
+option only masks attempts to remove non-existent files instead of
+masking a large variety of errors.
+.Pp
+Also, historical
+.Bx
+implementations prompted on the standard output,
+not the standard error output.
+.Sh SEE ALSO
+.Xr rmdir 1 ,
+.Xr undelete 2 ,
+.Xr unlink 2 ,
+.Xr fts 3 ,
+.Xr getopt 3 ,
+.Xr symlink 7
+.Sh STANDARDS
+The
+.Nm
+utility is expected to be
+.St -p1003.2
+compatible.
+The
+.Fl v
+and
+.Fl x
+options are extensions.
+.Pp
+The
+.Fl P
+option attempts to conform to U.S. DoD 5220-22.M, "National Industrial
+Security Program Operating Manual" ("NISPOM") as updated by Change
+2 and the July 23, 2003 "Clearing & Sanitization Matrix".
+However, unlike earlier revisions of NISPOM, the 2003 matrix imposes
+requirements which make it clear that the standard does not and
+can not apply to the erasure of individual files, in particular
+requirements relating to spare sector management for an entire
+magnetic disk.
+.Em Because these requirements are not met, the
+.Fl P
+.Em option does not conform to the standard .
+.Sh HISTORY
+An
+.Nm
+utility appeared in
+.At v1 .
+.Sh BUGS
+The
+.Fl P
+option assumes that the underlying file system is a fixed-block file
+system.
+FFS is a fixed-block file system, LFS is not.
+In addition, only regular files are overwritten, other types of files
+are not.
+Recent research indicates that as many as 35 overwrite passes with
+carefully chosen data patterns may be necessary to actually prevent
+recovery of data from a magnetic disk.
+Thus the
+.Fl P
+option is likely both insufficient for its design purpose and far
+too costly for default operation.
+However, it will at least prevent the recovery of data from FFS
+volumes with
+.Xr fsdb 8 .
diff --git a/bin/rm/rm.c b/bin/rm/rm.c
new file mode 100644
index 0000000..c1d2e8d
--- /dev/null
+++ b/bin/rm/rm.c
@@ -0,0 +1,611 @@
+/* $NetBSD: rm.c,v 1.53 2013/04/26 18:43:22 christos Exp $ */
+
+/*-
+ * Copyright (c) 1990, 1993, 1994, 2003
+ * The Regents of the University of California. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+#ifndef lint
+__COPYRIGHT("@(#) Copyright (c) 1990, 1993, 1994\
+ The Regents of the University of California. All rights reserved.");
+#endif /* not lint */
+
+#ifndef lint
+#if 0
+static char sccsid[] = "@(#)rm.c 8.8 (Berkeley) 4/27/95";
+#else
+__RCSID("$NetBSD: rm.c,v 1.53 2013/04/26 18:43:22 christos Exp $");
+#endif
+#endif /* not lint */
+
+#include <sys/param.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+
+#include <err.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <fts.h>
+#include <grp.h>
+#include <locale.h>
+#include <pwd.h>
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+static int dflag, eval, fflag, iflag, Pflag, stdin_ok, vflag, Wflag;
+static int xflag;
+static sig_atomic_t pinfo;
+
+static int check(char *, char *, struct stat *);
+static void checkdot(char **);
+static void progress(int);
+static void rm_file(char **);
+static int rm_overwrite(char *, struct stat *);
+static void rm_tree(char **);
+__dead static void usage(void);
+
+/*
+ * For the sake of the `-f' flag, check whether an error number indicates the
+ * failure of an operation due to an non-existent file, either per se (ENOENT)
+ * or because its filename argument was illegal (ENAMETOOLONG, ENOTDIR).
+ */
+#define NONEXISTENT(x) \
+ ((x) == ENOENT || (x) == ENAMETOOLONG || (x) == ENOTDIR)
+
+/*
+ * rm --
+ * This rm is different from historic rm's, but is expected to match
+ * POSIX 1003.2 behavior. The most visible difference is that -f
+ * has two specific effects now, ignore non-existent files and force
+ * file removal.
+ */
+int
+main(int argc, char *argv[])
+{
+ int ch, rflag;
+
+ setprogname(argv[0]);
+ (void)setlocale(LC_ALL, "");
+
+ Pflag = rflag = xflag = 0;
+ while ((ch = getopt(argc, argv, "dfiPRrvWx")) != -1)
+ switch (ch) {
+ case 'd':
+ dflag = 1;
+ break;
+ case 'f':
+ fflag = 1;
+ iflag = 0;
+ break;
+ case 'i':
+ fflag = 0;
+ iflag = 1;
+ break;
+ case 'P':
+ Pflag = 1;
+ break;
+ case 'R':
+ case 'r': /* Compatibility. */
+ rflag = 1;
+ break;
+ case 'v':
+ vflag = 1;
+ break;
+ case 'x':
+ xflag = 1;
+ break;
+ case 'W':
+ Wflag = 1;
+ break;
+ case '?':
+ default:
+ usage();
+ }
+ argc -= optind;
+ argv += optind;
+
+ if (argc < 1) {
+ if (fflag)
+ return 0;
+ usage();
+ }
+
+ (void)signal(SIGINFO, progress);
+
+ checkdot(argv);
+
+ if (*argv) {
+ stdin_ok = isatty(STDIN_FILENO);
+
+ if (rflag)
+ rm_tree(argv);
+ else
+ rm_file(argv);
+ }
+
+ exit(eval);
+ /* NOTREACHED */
+}
+
+static void
+rm_tree(char **argv)
+{
+ FTS *fts;
+ FTSENT *p;
+ int flags, needstat, rval;
+
+ /*
+ * Remove a file hierarchy. If forcing removal (-f), or interactive
+ * (-i) or can't ask anyway (stdin_ok), don't stat the file.
+ */
+ needstat = !fflag && !iflag && stdin_ok;
+
+ /*
+ * If the -i option is specified, the user can skip on the pre-order
+ * visit. The fts_number field flags skipped directories.
+ */
+#define SKIPPED 1
+
+ flags = FTS_PHYSICAL;
+ if (!needstat)
+ flags |= FTS_NOSTAT;
+ if (Wflag)
+ flags |= FTS_WHITEOUT;
+ if (xflag)
+ flags |= FTS_XDEV;
+ if ((fts = fts_open(argv, flags, NULL)) == NULL)
+ err(1, "fts_open failed");
+ while ((p = fts_read(fts)) != NULL) {
+
+ switch (p->fts_info) {
+ case FTS_DNR:
+ if (!fflag || p->fts_errno != ENOENT) {
+ warnx("%s: %s", p->fts_path,
+ strerror(p->fts_errno));
+ eval = 1;
+ }
+ continue;
+ case FTS_ERR:
+ errx(EXIT_FAILURE, "%s: %s", p->fts_path,
+ strerror(p->fts_errno));
+ /* NOTREACHED */
+ case FTS_NS:
+ /*
+ * FTS_NS: assume that if can't stat the file, it
+ * can't be unlinked.
+ */
+ if (fflag && NONEXISTENT(p->fts_errno))
+ continue;
+ if (needstat) {
+ warnx("%s: %s", p->fts_path,
+ strerror(p->fts_errno));
+ eval = 1;
+ continue;
+ }
+ break;
+ case FTS_D:
+ /* Pre-order: give user chance to skip. */
+ if (!fflag && !check(p->fts_path, p->fts_accpath,
+ p->fts_statp)) {
+ (void)fts_set(fts, p, FTS_SKIP);
+ p->fts_number = SKIPPED;
+ }
+ continue;
+ case FTS_DP:
+ /* Post-order: see if user skipped. */
+ if (p->fts_number == SKIPPED)
+ continue;
+ break;
+ default:
+ if (!fflag &&
+ !check(p->fts_path, p->fts_accpath, p->fts_statp))
+ continue;
+ }
+
+ rval = 0;
+ /*
+ * If we can't read or search the directory, may still be
+ * able to remove it. Don't print out the un{read,search}able
+ * message unless the remove fails.
+ */
+ switch (p->fts_info) {
+ case FTS_DP:
+ case FTS_DNR:
+ rval = rmdir(p->fts_accpath);
+ if (rval != 0 && fflag && errno == ENOENT)
+ continue;
+ break;
+
+ case FTS_W:
+ rval = undelete(p->fts_accpath);
+ if (rval != 0 && fflag && errno == ENOENT)
+ continue;
+ break;
+
+ default:
+ if (Pflag) {
+ if (rm_overwrite(p->fts_accpath, NULL))
+ continue;
+ }
+ rval = unlink(p->fts_accpath);
+ if (rval != 0 && fflag && NONEXISTENT(errno))
+ continue;
+ break;
+ }
+ if (rval != 0) {
+ warn("%s", p->fts_path);
+ eval = 1;
+ } else if (vflag || pinfo) {
+ pinfo = 0;
+ (void)printf("%s\n", p->fts_path);
+ }
+ }
+ if (errno)
+ err(1, "fts_read");
+ fts_close(fts);
+}
+
+static void
+rm_file(char **argv)
+{
+ struct stat sb;
+ int rval;
+ char *f;
+
+ /*
+ * Remove a file. POSIX 1003.2 states that, by default, attempting
+ * to remove a directory is an error, so must always stat the file.
+ */
+ while ((f = *argv++) != NULL) {
+ /* Assume if can't stat the file, can't unlink it. */
+ if (lstat(f, &sb)) {
+ if (Wflag) {
+ sb.st_mode = S_IFWHT|S_IWUSR|S_IRUSR;
+ } else {
+ if (!fflag || !NONEXISTENT(errno)) {
+ warn("%s", f);
+ eval = 1;
+ }
+ continue;
+ }
+ } else if (Wflag) {
+ warnx("%s: %s", f, strerror(EEXIST));
+ eval = 1;
+ continue;
+ }
+
+ if (S_ISDIR(sb.st_mode) && !dflag) {
+ warnx("%s: is a directory", f);
+ eval = 1;
+ continue;
+ }
+ if (!fflag && !S_ISWHT(sb.st_mode) && !check(f, f, &sb))
+ continue;
+ if (S_ISWHT(sb.st_mode))
+ rval = undelete(f);
+ else if (S_ISDIR(sb.st_mode))
+ rval = rmdir(f);
+ else {
+ if (Pflag) {
+ if (rm_overwrite(f, &sb))
+ continue;
+ }
+ rval = unlink(f);
+ }
+ if (rval && (!fflag || !NONEXISTENT(errno))) {
+ warn("%s", f);
+ eval = 1;
+ }
+ if (vflag && rval == 0)
+ (void)printf("%s\n", f);
+ }
+}
+
+/*
+ * rm_overwrite --
+ * Overwrite the file 3 times with varying bit patterns.
+ *
+ * This is an expensive way to keep people from recovering files from your
+ * non-snapshotted FFS filesystems using fsdb(8). Really. No more. Only
+ * regular files are deleted, directories (and therefore names) will remain.
+ * Also, this assumes a fixed-block file system (like FFS, or a V7 or a
+ * System V file system). In a logging file system, you'll have to have
+ * kernel support.
+ *
+ * A note on standards: U.S. DoD 5220.22-M "National Industrial Security
+ * Program Operating Manual" ("NISPOM") is often cited as a reference
+ * for clearing and sanitizing magnetic media. In fact, a matrix of
+ * "clearing" and "sanitization" methods for various media was given in
+ * Chapter 8 of the original 1995 version of NISPOM. However, that
+ * matrix was *removed from the document* when Chapter 8 was rewritten
+ * in Change 2 to the document in 2001. Recently, the Defense Security
+ * Service has made a revised clearing and sanitization matrix available
+ * in Microsoft Word format on the DSS web site. The standardization
+ * status of this matrix is unclear. Furthermore, one must be very
+ * careful when referring to this matrix: it is intended for the "clearing"
+ * prior to reuse or "sanitization" prior to disposal of *entire media*,
+ * not individual files and the only non-physically-destructive method of
+ * "sanitization" that is permitted for magnetic disks of any kind is
+ * specifically noted to be prohibited for media that have contained
+ * Top Secret data.
+ *
+ * It is impossible to actually conform to the exact procedure given in
+ * the matrix if one is overwriting a file, not an entire disk, because
+ * the procedure requires examination and comparison of the disk's defect
+ * lists. Any program that claims to securely erase *files* while
+ * conforming to the standard, then, is not correct. We do as much of
+ * what the standard requires as can actually be done when erasing a
+ * file, rather than an entire disk; but that does not make us conformant.
+ *
+ * Furthermore, the presence of track caches, disk and controller write
+ * caches, and so forth make it extremely difficult to ensure that data
+ * have actually been written to the disk, particularly when one tries
+ * to repeatedly overwrite the same sectors in quick succession. We call
+ * fsync(), but controllers with nonvolatile cache, as well as IDE disks
+ * that just plain lie about the stable storage of data, will defeat this.
+ *
+ * Finally, widely respected research suggests that the given procedure
+ * is nowhere near sufficient to prevent the recovery of data using special
+ * forensic equipment and techniques that are well-known. This is
+ * presumably one reason that the matrix requires physical media destruction,
+ * rather than any technique of the sort attempted here, for secret data.
+ *
+ * Caveat Emptor.
+ *
+ * rm_overwrite will return 0 on success.
+ */
+
+static int
+rm_overwrite(char *file, struct stat *sbp)
+{
+ struct stat sb, sb2;
+ int fd, randint;
+ char randchar;
+
+ fd = -1;
+ if (sbp == NULL) {
+ if (lstat(file, &sb))
+ goto err;
+ sbp = &sb;
+ }
+ if (!S_ISREG(sbp->st_mode))
+ return 0;
+
+ /* flags to try to defeat hidden caching by forcing seeks */
+ if ((fd = open(file, O_RDWR|O_SYNC|O_RSYNC|O_NOFOLLOW, 0)) == -1)
+ goto err;
+
+ if (fstat(fd, &sb2)) {
+ goto err;
+ }
+
+ if (sb2.st_dev != sbp->st_dev || sb2.st_ino != sbp->st_ino ||
+ !S_ISREG(sb2.st_mode)) {
+ errno = EPERM;
+ goto err;
+ }
+
+#define RAND_BYTES 1
+#define THIS_BYTE 0
+
+#define WRITE_PASS(mode, byte) do { \
+ off_t len; \
+ size_t wlen, i; \
+ char buf[8 * 1024]; \
+ \
+ if (fsync(fd) || lseek(fd, (off_t)0, SEEK_SET)) \
+ goto err; \
+ \
+ if (mode == THIS_BYTE) \
+ memset(buf, byte, sizeof(buf)); \
+ for (len = sbp->st_size; len > 0; len -= wlen) { \
+ if (mode == RAND_BYTES) { \
+ for (i = 0; i < sizeof(buf); \
+ i+= sizeof(u_int32_t)) \
+ *(int *)(buf + i) = arc4random(); \
+ } \
+ wlen = len < (off_t)sizeof(buf) ? (size_t)len : sizeof(buf); \
+ if ((size_t)write(fd, buf, wlen) != wlen) \
+ goto err; \
+ } \
+ sync(); /* another poke at hidden caches */ \
+} while (/* CONSTCOND */ 0)
+
+#define READ_PASS(byte) do { \
+ off_t len; \
+ size_t rlen; \
+ char pattern[8 * 1024]; \
+ char buf[8 * 1024]; \
+ \
+ if (fsync(fd) || lseek(fd, (off_t)0, SEEK_SET)) \
+ goto err; \
+ \
+ memset(pattern, byte, sizeof(pattern)); \
+ for(len = sbp->st_size; len > 0; len -= rlen) { \
+ rlen = len < (off_t)sizeof(buf) ? (size_t)len : sizeof(buf); \
+ if((size_t)read(fd, buf, rlen) != rlen) \
+ goto err; \
+ if(memcmp(buf, pattern, rlen)) \
+ goto err; \
+ } \
+ sync(); /* another poke at hidden caches */ \
+} while (/* CONSTCOND */ 0)
+
+ /*
+ * DSS sanitization matrix "clear" for magnetic disks:
+ * option 'c' "Overwrite all addressable locations with a single
+ * character."
+ */
+ randint = arc4random();
+ randchar = *(char *)&randint;
+ WRITE_PASS(THIS_BYTE, randchar);
+
+ /*
+ * DSS sanitization matrix "sanitize" for magnetic disks:
+ * option 'd', sub 2 "Overwrite all addressable locations with a
+ * character, then its complement. Verify "complement" character
+ * was written successfully to all addressable locations, then
+ * overwrite all addressable locations with random characters; or
+ * verify third overwrite of random characters." The rest of the
+ * text in d-sub-2 specifies requirements for overwriting spared
+ * sectors; we cannot conform to it when erasing only a file, thus
+ * we do not conform to the standard.
+ */
+
+ /* 1. "a character" */
+ WRITE_PASS(THIS_BYTE, 0xff);
+
+ /* 2. "its complement" */
+ WRITE_PASS(THIS_BYTE, 0x00);
+
+ /* 3. "Verify 'complement' character" */
+ READ_PASS(0x00);
+
+ /* 4. "overwrite all addressable locations with random characters" */
+
+ WRITE_PASS(RAND_BYTES, 0x00);
+
+ /*
+ * As the file might be huge, and we note that this revision of
+ * the matrix says "random characters", not "a random character"
+ * as the original did, we do not verify the random-character
+ * write; the "or" in the standard allows this.
+ */
+
+ if (close(fd) == -1) {
+ fd = -1;
+ goto err;
+ }
+
+ return 0;
+
+err: eval = 1;
+ warn("%s", file);
+ if (fd != -1)
+ close(fd);
+ return 1;
+}
+
+static int
+check(char *path, char *name, struct stat *sp)
+{
+ int ch, first;
+ char modep[15];
+
+ /* Check -i first. */
+ if (iflag)
+ (void)fprintf(stderr, "remove '%s'? ", path);
+ else {
+ /*
+ * If it's not a symbolic link and it's unwritable and we're
+ * talking to a terminal, ask. Symbolic links are excluded
+ * because their permissions are meaningless. Check stdin_ok
+ * first because we may not have stat'ed the file.
+ */
+ if (!stdin_ok || S_ISLNK(sp->st_mode) ||
+ !(access(name, W_OK) && (errno != ETXTBSY)))
+ return (1);
+ strmode(sp->st_mode, modep);
+ if (Pflag) {
+ warnx(
+ "%s: -P was specified but file could not"
+ " be overwritten", path);
+ return 0;
+ }
+ (void)fprintf(stderr, "override %s%s%s:%s for '%s'? ",
+ modep + 1, modep[9] == ' ' ? "" : " ",
+ user_from_uid(sp->st_uid, 0),
+ group_from_gid(sp->st_gid, 0), path);
+ }
+ (void)fflush(stderr);
+
+ first = ch = getchar();
+ while (ch != '\n' && ch != EOF)
+ ch = getchar();
+ return (first == 'y' || first == 'Y');
+}
+
+/*
+ * POSIX.2 requires that if "." or ".." are specified as the basename
+ * portion of an operand, a diagnostic message be written to standard
+ * error and nothing more be done with such operands.
+ *
+ * Since POSIX.2 defines basename as the final portion of a path after
+ * trailing slashes have been removed, we'll remove them here.
+ */
+#define ISDOT(a) ((a)[0] == '.' && (!(a)[1] || ((a)[1] == '.' && !(a)[2])))
+static void
+checkdot(char **argv)
+{
+ char *p, **save, **t;
+ int complained;
+
+ complained = 0;
+ for (t = argv; *t;) {
+ /* strip trailing slashes */
+ p = strrchr(*t, '\0');
+ while (--p > *t && *p == '/')
+ *p = '\0';
+
+ /* extract basename */
+ if ((p = strrchr(*t, '/')) != NULL)
+ ++p;
+ else
+ p = *t;
+
+ if (ISDOT(p)) {
+ if (!complained++)
+ warnx("\".\" and \"..\" may not be removed");
+ eval = 1;
+ for (save = t; (t[0] = t[1]) != NULL; ++t)
+ continue;
+ t = save;
+ } else
+ ++t;
+ }
+}
+
+static void
+usage(void)
+{
+
+ (void)fprintf(stderr, "usage: %s [-f|-i] [-dPRrvWx] file ...\n",
+ getprogname());
+ exit(1);
+ /* NOTREACHED */
+}
+
+static void
+progress(int sig __unused)
+{
+
+ pinfo++;
+}
diff --git a/bin/rmdir/rmdir.1 b/bin/rmdir/rmdir.1
new file mode 100644
index 0000000..8fe42f9
--- /dev/null
+++ b/bin/rmdir/rmdir.1
@@ -0,0 +1,95 @@
+.\" $NetBSD: rmdir.1,v 1.17 2017/07/03 21:33:23 wiz Exp $
+.\"
+.\" Copyright (c) 1990, 1993
+.\" The Regents of the University of California. All rights reserved.
+.\"
+.\" This code is derived from software contributed to Berkeley by
+.\" the Institute of Electrical and Electronics Engineers, Inc.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\" notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must reproduce the above copyright
+.\" notice, this list of conditions and the following disclaimer in the
+.\" documentation and/or other materials provided with the distribution.
+.\" 3. Neither the name of the University nor the names of its contributors
+.\" may be used to endorse or promote products derived from this software
+.\" without specific prior written permission.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+.\" ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+.\" SUCH DAMAGE.
+.\"
+.\" @(#)rmdir.1 8.1 (Berkeley) 5/31/93
+.\"
+.Dd August 12, 2016
+.Dt RMDIR 1
+.Os
+.Sh NAME
+.Nm rmdir
+.Nd remove directories
+.Sh SYNOPSIS
+.Nm
+.Op Fl p
+.Ar directory ...
+.Sh DESCRIPTION
+The rmdir utility removes the directory entry specified by
+each
+.Ar directory
+argument, provided it is empty.
+.Pp
+Arguments are processed in the order given.
+In order to remove both a parent directory and a subdirectory
+of that parent, the subdirectory
+must be specified first so the parent directory
+is empty when
+.Nm
+tries to remove it.
+.Pp
+The following option is available:
+.Bl -tag -width Ds
+.It Fl p
+Each
+.Ar directory
+argument is treated as a pathname of which all
+components will be removed, if they are empty,
+starting with the last most component.
+(See
+.Xr rm 1
+for fully non-discriminant recursive removal.)
+.El
+.Sh EXIT STATUS
+The
+.Nm
+utility exits with one of the following values:
+.Bl -tag -width Ds
+.It Li \&0
+Each directory entry specified by a dir operand
+referred to an empty directory and was removed
+successfully.
+.It Li \&>\&0
+An error occurred.
+.El
+.Sh SEE ALSO
+.Xr rm 1
+.Sh STANDARDS
+The
+.Nm
+utility is expected to be
+.St -p1003.2
+compatible.
+.Sh HISTORY
+An
+.Nm
+utility appeared in
+.At v1 .
diff --git a/bin/rmdir/rmdir.c b/bin/rmdir/rmdir.c
new file mode 100644
index 0000000..7a87b30
--- /dev/null
+++ b/bin/rmdir/rmdir.c
@@ -0,0 +1,125 @@
+/* $NetBSD: rmdir.c,v 1.27 2017/08/10 22:52:13 ginsbach Exp $ */
+
+/*-
+ * Copyright (c) 1992, 1993, 1994
+ * The Regents of the University of California. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+#ifndef lint
+__COPYRIGHT("@(#) Copyright (c) 1992, 1993, 1994\
+ The Regents of the University of California. All rights reserved.");
+#endif /* not lint */
+
+#ifndef lint
+#if 0
+static char sccsid[] = "@(#)rmdir.c 8.3 (Berkeley) 4/2/94";
+#else
+__RCSID("$NetBSD: rmdir.c,v 1.27 2017/08/10 22:52:13 ginsbach Exp $");
+#endif
+#endif /* not lint */
+
+#include <sys/param.h>
+
+#include <err.h>
+#include <locale.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+static int rm_path(char *);
+__dead static void usage(void);
+
+int
+main(int argc, char *argv[])
+{
+ int ch, errors, pflag;
+
+ setprogname(argv[0]);
+ (void)setlocale(LC_ALL, "");
+
+ pflag = 0;
+ while ((ch = getopt(argc, argv, "p")) != -1)
+ switch(ch) {
+ case 'p':
+ pflag = 1;
+ break;
+ case '?':
+ default:
+ usage();
+ }
+ argc -= optind;
+ argv += optind;
+
+ if (argc == 0)
+ usage();
+
+ for (errors = 0; *argv; argv++) {
+ /* We rely on the kernel to ignore trailing '/' characters. */
+ if (rmdir(*argv) < 0) {
+ warn("%s", *argv);
+ errors = 1;
+ } else if (pflag)
+ errors |= rm_path(*argv);
+ }
+
+ exit(errors);
+ /* NOTREACHED */
+}
+
+static int
+rm_path(char *path)
+{
+ char *p;
+
+ while ((p = strrchr(path, '/')) != NULL) {
+ *p = 0;
+ if (p[1] == 0)
+ /* Ignore trailing '/' on deleted name */
+ continue;
+
+ if (*path == 0)
+ /* At top level (root) directory */
+ break;
+
+ if (rmdir(path) < 0) {
+ warn("%s", path);
+ return (1);
+ }
+ }
+
+ return (0);
+}
+
+static void
+usage(void)
+{
+ (void)fprintf(stderr, "usage: %s [-p] directory ...\n", getprogname());
+ exit(1);
+ /* NOTREACHED */
+}
diff --git a/bin/sh/TOUR b/bin/sh/TOUR
new file mode 100644
index 0000000..30cee04
--- /dev/null
+++ b/bin/sh/TOUR
@@ -0,0 +1,357 @@
+# $NetBSD: TOUR,v 1.11 2016/10/25 13:01:59 abhinav Exp $
+# @(#)TOUR 8.1 (Berkeley) 5/31/93
+
+NOTE -- This is the original TOUR paper distributed with ash and
+does not represent the current state of the shell. It is provided anyway
+since it provides helpful information for how the shell is structured,
+but be warned that things have changed -- the current shell is
+still under development.
+
+================================================================
+
+ A Tour through Ash
+
+ Copyright 1989 by Kenneth Almquist.
+
+
+DIRECTORIES: The subdirectory bltin contains commands which can
+be compiled stand-alone. The rest of the source is in the main
+ash directory.
+
+SOURCE CODE GENERATORS: Files whose names begin with "mk" are
+programs that generate source code. A complete list of these
+programs is:
+
+ program input files generates
+ ------- ------------ ---------
+ mkbuiltins builtins builtins.h builtins.c
+ mkinit *.c init.c
+ mknodes nodetypes nodes.h nodes.c
+ mksignames - signames.h signames.c
+ mksyntax - syntax.h syntax.c
+ mktokens - token.h
+ bltin/mkexpr unary_op binary_op operators.h operators.c
+
+There are undoubtedly too many of these. Mkinit searches all the
+C source files for entries looking like:
+
+ INIT {
+ x = 1; /* executed during initialization */
+ }
+
+ RESET {
+ x = 2; /* executed when the shell does a longjmp
+ back to the main command loop */
+ }
+
+ SHELLPROC {
+ x = 3; /* executed when the shell runs a shell procedure */
+ }
+
+It pulls this code out into routines which are when particular
+events occur. The intent is to improve modularity by isolating
+the information about which modules need to be explicitly
+initialized/reset within the modules themselves.
+
+Mkinit recognizes several constructs for placing declarations in
+the init.c file.
+ INCLUDE "file.h"
+includes a file. The storage class MKINIT makes a declaration
+available in the init.c file, for example:
+ MKINIT int funcnest; /* depth of function calls */
+MKINIT alone on a line introduces a structure or union declara-
+tion:
+ MKINIT
+ struct redirtab {
+ short renamed[10];
+ };
+Preprocessor #define statements are copied to init.c without any
+special action to request this.
+
+INDENTATION: The ash source is indented in multiples of six
+spaces. The only study that I have heard of on the subject con-
+cluded that the optimal amount to indent is in the range of four
+to six spaces. I use six spaces since it is not too big a jump
+from the widely used eight spaces. If you really hate six space
+indentation, use the adjind (source included) program to change
+it to something else.
+
+EXCEPTIONS: Code for dealing with exceptions appears in
+exceptions.c. The C language doesn't include exception handling,
+so I implement it using setjmp and longjmp. The global variable
+exception contains the type of exception. EXERROR is raised by
+calling error. EXINT is an interrupt. EXSHELLPROC is an excep-
+tion which is raised when a shell procedure is invoked. The pur-
+pose of EXSHELLPROC is to perform the cleanup actions associated
+with other exceptions. After these cleanup actions, the shell
+can interpret a shell procedure itself without exec'ing a new
+copy of the shell.
+
+INTERRUPTS: In an interactive shell, an interrupt will cause an
+EXINT exception to return to the main command loop. (Exception:
+EXINT is not raised if the user traps interrupts using the trap
+command.) The INTOFF and INTON macros (defined in exception.h)
+provide uninterruptible critical sections. Between the execution
+of INTOFF and the execution of INTON, interrupt signals will be
+held for later delivery. INTOFF and INTON can be nested.
+
+MEMALLOC.C: Memalloc.c defines versions of malloc and realloc
+which call error when there is no memory left. It also defines a
+stack oriented memory allocation scheme. Allocating off a stack
+is probably more efficient than allocation using malloc, but the
+big advantage is that when an exception occurs all we have to do
+to free up the memory in use at the time of the exception is to
+restore the stack pointer. The stack is implemented using a
+linked list of blocks.
+
+STPUTC: If the stack were contiguous, it would be easy to store
+strings on the stack without knowing in advance how long the
+string was going to be:
+ p = stackptr;
+ *p++ = c; /* repeated as many times as needed */
+ stackptr = p;
+The following three macros (defined in memalloc.h) perform these
+operations, but grow the stack if you run off the end:
+ STARTSTACKSTR(p);
+ STPUTC(c, p); /* repeated as many times as needed */
+ grabstackstr(p);
+
+We now start a top-down look at the code:
+
+MAIN.C: The main routine performs some initialization, executes
+the user's profile if necessary, and calls cmdloop. Cmdloop
+repeatedly parses and executes commands.
+
+OPTIONS.C: This file contains the option processing code. It is
+called from main to parse the shell arguments when the shell is
+invoked, and it also contains the set builtin. The -i and -j op-
+tions (the latter turns on job control) require changes in signal
+handling. The routines setjobctl (in jobs.c) and setinteractive
+(in trap.c) are called to handle changes to these options.
+
+PARSING: The parser code is all in parser.c. A recursive des-
+cent parser is used. Syntax tables (generated by mksyntax) are
+used to classify characters during lexical analysis. There are
+three tables: one for normal use, one for use when inside single
+quotes, and one for use when inside double quotes. The tables
+are machine dependent because they are indexed by character vari-
+ables and the range of a char varies from machine to machine.
+
+PARSE OUTPUT: The output of the parser consists of a tree of
+nodes. The various types of nodes are defined in the file node-
+types.
+
+Nodes of type NARG are used to represent both words and the con-
+tents of here documents. An early version of ash kept the con-
+tents of here documents in temporary files, but keeping here do-
+cuments in memory typically results in significantly better per-
+formance. It would have been nice to make it an option to use
+temporary files for here documents, for the benefit of small
+machines, but the code to keep track of when to delete the tem-
+porary files was complex and I never fixed all the bugs in it.
+(AT&T has been maintaining the Bourne shell for more than ten
+years, and to the best of my knowledge they still haven't gotten
+it to handle temporary files correctly in obscure cases.)
+
+The text field of a NARG structure points to the text of the
+word. The text consists of ordinary characters and a number of
+special codes defined in parser.h. The special codes are:
+
+ CTLVAR Variable substitution
+ CTLENDVAR End of variable substitution
+ CTLBACKQ Command substitution
+ CTLBACKQ|CTLQUOTE Command substitution inside double quotes
+ CTLESC Escape next character
+
+A variable substitution contains the following elements:
+
+ CTLVAR type name '=' [ alternative-text CTLENDVAR ]
+
+The type field is a single character specifying the type of sub-
+stitution. The possible types are:
+
+ VSNORMAL $var
+ VSMINUS ${var-text}
+ VSMINUS|VSNUL ${var:-text}
+ VSPLUS ${var+text}
+ VSPLUS|VSNUL ${var:+text}
+ VSQUESTION ${var?text}
+ VSQUESTION|VSNUL ${var:?text}
+ VSASSIGN ${var=text}
+ VSASSIGN|VSNUL ${var=text}
+
+In addition, the type field will have the VSQUOTE flag set if the
+variable is enclosed in double quotes. The name of the variable
+comes next, terminated by an equals sign. If the type is not
+VSNORMAL, then the text field in the substitution follows, ter-
+minated by a CTLENDVAR byte.
+
+Commands in back quotes are parsed and stored in a linked list.
+The locations of these commands in the string are indicated by
+CTLBACKQ and CTLBACKQ+CTLQUOTE characters, depending upon whether
+the back quotes were enclosed in double quotes.
+
+The character CTLESC escapes the next character, so that in case
+any of the CTL characters mentioned above appear in the input,
+they can be passed through transparently. CTLESC is also used to
+escape '*', '?', '[', and '!' characters which were quoted by the
+user and thus should not be used for file name generation.
+
+CTLESC characters have proved to be particularly tricky to get
+right. In the case of here documents which are not subject to
+variable and command substitution, the parser doesn't insert any
+CTLESC characters to begin with (so the contents of the text
+field can be written without any processing). Other here docu-
+ments, and words which are not subject to splitting and file name
+generation, have the CTLESC characters removed during the vari-
+able and command substitution phase. Words which are subject
+splitting and file name generation have the CTLESC characters re-
+moved as part of the file name phase.
+
+EXECUTION: Command execution is handled by the following files:
+ eval.c The top level routines.
+ redir.c Code to handle redirection of input and output.
+ jobs.c Code to handle forking, waiting, and job control.
+ exec.c Code to do path searches and the actual exec sys call.
+ expand.c Code to evaluate arguments.
+ var.c Maintains the variable symbol table. Called from expand.c.
+
+EVAL.C: Evaltree recursively executes a parse tree. The exit
+status is returned in the global variable exitstatus. The alter-
+native entry evalbackcmd is called to evaluate commands in back
+quotes. It saves the result in memory if the command is a buil-
+tin; otherwise it forks off a child to execute the command and
+connects the standard output of the child to a pipe.
+
+JOBS.C: To create a process, you call makejob to return a job
+structure, and then call forkshell (passing the job structure as
+an argument) to create the process. Waitforjob waits for a job
+to complete. These routines take care of process groups if job
+control is defined.
+
+REDIR.C: Ash allows file descriptors to be redirected and then
+restored without forking off a child process. This is accom-
+plished by duplicating the original file descriptors. The redir-
+tab structure records where the file descriptors have been dupli-
+cated to.
+
+EXEC.C: The routine find_command locates a command, and enters
+the command in the hash table if it is not already there. The
+third argument specifies whether it is to print an error message
+if the command is not found. (When a pipeline is set up,
+find_command is called for all the commands in the pipeline be-
+fore any forking is done, so to get the commands into the hash
+table of the parent process. But to make command hashing as
+transparent as possible, we silently ignore errors at that point
+and only print error messages if the command cannot be found
+later.)
+
+The routine shellexec is the interface to the exec system call.
+
+EXPAND.C: Arguments are processed in three passes. The first
+(performed by the routine argstr) performs variable and command
+substitution. The second (ifsbreakup) performs word splitting
+and the third (expandmeta) performs file name generation. If the
+"/u" directory is simulated, then when "/u/username" is replaced
+by the user's home directory, the flag "didudir" is set. This
+tells the cd command that it should print out the directory name,
+just as it would if the "/u" directory were implemented using
+symbolic links.
+
+VAR.C: Variables are stored in a hash table. Probably we should
+switch to extensible hashing. The variable name is stored in the
+same string as the value (using the format "name=value") so that
+no string copying is needed to create the environment of a com-
+mand. Variables which the shell references internally are preal-
+located so that the shell can reference the values of these vari-
+ables without doing a lookup.
+
+When a program is run, the code in eval.c sticks any environment
+variables which precede the command (as in "PATH=xxx command") in
+the variable table as the simplest way to strip duplicates, and
+then calls "environment" to get the value of the environment.
+There are two consequences of this. First, if an assignment to
+PATH precedes the command, the value of PATH before the assign-
+ment must be remembered and passed to shellexec. Second, if the
+program turns out to be a shell procedure, the strings from the
+environment variables which preceded the command must be pulled
+out of the table and replaced with strings obtained from malloc,
+since the former will automatically be freed when the stack (see
+the entry on memalloc.c) is emptied.
+
+BUILTIN COMMANDS: The procedures for handling these are scat-
+tered throughout the code, depending on which location appears
+most appropriate. They can be recognized because their names al-
+ways end in "cmd". The mapping from names to procedures is
+specified in the file builtins, which is processed by the mkbuil-
+tins command.
+
+A builtin command is invoked with argc and argv set up like a
+normal program. A builtin command is allowed to overwrite its
+arguments. Builtin routines can call nextopt to do option pars-
+ing. This is kind of like getopt, but you don't pass argc and
+argv to it. Builtin routines can also call error. This routine
+normally terminates the shell (or returns to the main command
+loop if the shell is interactive), but when called from a builtin
+command it causes the builtin command to terminate with an exit
+status of 2.
+
+The directory bltins contains commands which can be compiled in-
+dependently but can also be built into the shell for efficiency
+reasons. The makefile in this directory compiles these programs
+in the normal fashion (so that they can be run regardless of
+whether the invoker is ash), but also creates a library named
+bltinlib.a which can be linked with ash. The header file bltin.h
+takes care of most of the differences between the ash and the
+stand-alone environment. The user should call the main routine
+"main", and #define main to be the name of the routine to use
+when the program is linked into ash. This #define should appear
+before bltin.h is included; bltin.h will #undef main if the pro-
+gram is to be compiled stand-alone.
+
+CD.C: This file defines the cd and pwd builtins. The pwd com-
+mand runs /bin/pwd the first time it is invoked (unless the user
+has already done a cd to an absolute pathname), but then
+remembers the current directory and updates it when the cd com-
+mand is run, so subsequent pwd commands run very fast. The main
+complication in the cd command is in the docd command, which
+resolves symbolic links into actual names and informs the user
+where the user ended up if he crossed a symbolic link.
+
+SIGNALS: Trap.c implements the trap command. The routine set-
+signal figures out what action should be taken when a signal is
+received and invokes the signal system call to set the signal ac-
+tion appropriately. When a signal that a user has set a trap for
+is caught, the routine "onsig" sets a flag. The routine dotrap
+is called at appropriate points to actually handle the signal.
+When an interrupt is caught and no trap has been set for that
+signal, the routine "onint" in error.c is called.
+
+OUTPUT: Ash uses its own output routines. There are three out-
+put structures allocated. "Output" represents the standard out-
+put, "errout" the standard error, and "memout" contains output
+which is to be stored in memory. This last is used when a buil-
+tin command appears in backquotes, to allow its output to be col-
+lected without doing any I/O through the UNIX operating system.
+The variables out1 and out2 normally point to output and errout,
+respectively, but they are set to point to memout when appropri-
+ate inside backquotes.
+
+INPUT: The basic input routine is pgetc, which reads from the
+current input file. There is a stack of input files; the current
+input file is the top file on this stack. The code allows the
+input to come from a string rather than a file. (This is for the
+-c option and the "." and eval builtin commands.) The global
+variable plinno is saved and restored when files are pushed and
+popped from the stack. The parser routines store the number of
+the current line in this variable.
+
+DEBUGGING: If DEBUG is defined in shell.h, then the shell will
+write debugging information to the file $HOME/trace. Most of
+this is done using the TRACE macro, which takes a set of printf
+arguments inside two sets of parenthesis. Example:
+"TRACE(("n=%d0, n))". The double parenthesis are necessary be-
+cause the preprocessor can't handle functions with a variable
+number of arguments. Defining DEBUG also causes the shell to
+generate a core dump if it is sent a quit signal. The tracing
+code is in show.c.
diff --git a/bin/sh/USD.doc/Makefile b/bin/sh/USD.doc/Makefile
new file mode 100644
index 0000000..55b7203
--- /dev/null
+++ b/bin/sh/USD.doc/Makefile
@@ -0,0 +1,12 @@
+# $NetBSD: Makefile,v 1.4 2014/07/05 19:23:00 dholland Exp $
+# @(#)Makefile 8.1 (Berkeley) 8/14/93
+
+SECTION=reference/ref1
+ARTICLE=sh
+SRCS= referargs t.mac t1 t2 t3 t4
+MACROS=-ms
+ROFF_REFER=yes
+#REFER_ARGS=-e -p Rv7man
+EXTRAHTMLFILES=sh1.png sh2.png sh3.png sh4.png sh5.png
+
+.include <bsd.doc.mk>
diff --git a/bin/sh/USD.doc/Rv7man b/bin/sh/USD.doc/Rv7man
new file mode 100644
index 0000000..628c67f
--- /dev/null
+++ b/bin/sh/USD.doc/Rv7man
@@ -0,0 +1,405 @@
+%A L. P. Deutsch
+%A B. W. Lampson
+%T An online editor
+%J Comm. Assoc. Comp. Mach.
+%V 10
+%N 12
+%D December 1967
+%P 793-799, 803
+%K qed
+
+.[
+%r 17
+%K cstr
+%R Comp. Sci. Tech. Rep. No. 17
+%I Bell Laboratories
+%C Murray Hill, New Jersey
+%A B. W. Kernighan
+%A L. L. Cherry
+%T A System for Typesetting Mathematics
+%d May 1974, revised April 1977
+%J Comm. Assoc. Comp. Mach.
+%K acm cacm
+%V 18
+%P 151-157
+%D March 1975
+.]
+
+%T U\s-2NIX\s0 Time-Sharing System: Document Preparation
+%K unix bstj
+%A B. W. Kernighan
+%A M. E. Lesk
+%A J. F. Ossanna
+%J Bell Sys. Tech. J.
+%V 57
+%N 6
+%P 2115-2135
+%D 1978
+
+%A T. A. Dolotta
+%A J. R. Mashey
+%T An Introduction to the Programmer's Workbench
+%J Proc. 2nd Int. Conf. on Software Engineering
+%D October 13-15, 1976
+%P 164-168
+
+%T U\s-2NIX\s0 Time-Sharing System: The Programmer's Workbench
+%A T. A. Dolotta
+%A R. C. Haight
+%A J. R. Mashey
+%J Bell Sys. Tech. J.
+%V 57
+%N 6
+%P 2177-2200
+%D 1978
+%K unix bstj
+
+%T U\s-2NIX\s0 Time-Sharing System: U\s-2NIX\s0 on a Microprocessor
+%K unix bstj
+%A H. Lycklama
+%J Bell Sys. Tech. J.
+%V 57
+%N 6
+%P 2087-2101
+%D 1978
+
+%T The C Programming Language
+%A B. W. Kernighan
+%A D. M. Ritchie
+%I Prentice-Hall
+%C Englewood Cliffs, New Jersey
+%D 1978
+
+%T Computer Recreations
+%A Aleph-null
+%J Software Practice and Experience
+%V 1
+%N 2
+%D April-June 1971
+%P 201-204
+
+%T U\s-2NIX\s0 Time-Sharing System: The U\s-2NIX\s0 Shell
+%A S. R. Bourne
+%K unix bstj
+%J Bell Sys. Tech. J.
+%V 57
+%N 6
+%P 1971-1990
+%D 1978
+
+%A L. P. Deutsch
+%A B. W. Lampson
+%T \*sSDS\*n 930 time-sharing system preliminary reference manual
+%R Doc. 30.10.10, Project \*sGENIE\*n
+%C Univ. Cal. at Berkeley
+%D April 1965
+
+%A R. J. Feiertag
+%A E. I. Organick
+%T The Multics input-output system
+%J Proc. Third Symposium on Operating Systems Principles
+%D October 18-20, 1971
+%P 35-41
+
+%A D. G. Bobrow
+%A J. D. Burchfiel
+%A D. L. Murphy
+%A R. S. Tomlinson
+%T \*sTENEX\*n, a Paged Time Sharing System for the \*sPDP\*n-10
+%J Comm. Assoc. Comp. Mach.
+%V 15
+%N 3
+%D March 1972
+%K tenex
+%P 135-143
+
+%A R. E. Griswold
+%A D. R. Hanson
+%T An Overview of SL5
+%J SIGPLAN Notices
+%V 12
+%N 4
+%D April 1977
+%P 40-50
+
+%A E. W. Dijkstra
+%T Cooperating Sequential Processes
+%B Programming Languages
+%E F. Genuys
+%I Academic Press
+%C New York
+%D 1968
+%P 43-112
+
+%A J. A. Hawley
+%A W. B. Meyer
+%T M\s-2UNIX\s0, A Multiprocessing Version of U\s-2NIX\s0
+%K munix unix
+%R M.S. Thesis
+%I Naval Postgraduate School
+%C Monterey, Cal.
+%D 1975
+
+%T The U\s-2NIX\s0 Time-Sharing System
+%K unix bstj
+%A D. M. Ritchie
+%A K. Thompson
+%J Bell Sys. Tech. J.
+%V 57
+%N 6
+%P 1905-1929
+%D 1978
+
+%A E. I. Organick
+%T The M\s-2ULTICS\s0 System
+%K multics
+%I M.I.T. Press
+%C Cambridge, Mass.
+%D 1972
+
+%T UNIX for Beginners
+%A B. W. Kernighan
+%D 1978
+
+%T U\s-2NIX\s0 Programmer's Man\&ual
+%A K. Thompson
+%A D. M. Ritchie
+%K unix
+%I Bell Laboratories
+%O Seventh Edition.
+%D 1978
+
+%A K. Thompson
+%T The U\s-2NIX\s0 Command Language
+%B Structured Programming\(emInfotech State of the Art Report
+%I Infotech International Ltd.
+%C Nicholson House, Maidenhead, Berkshire, England
+%D March 1975
+%P 375-384
+%K unix
+%X pwb
+Brief description of shell syntax and semantics, without much
+detail on implementation.
+Much on pipes and convenience of hooking programs together.
+Includes SERMONETTE:
+"Many familiar computing `concepts' are missing from UNIX.
+Files have no records. There are no access methods.
+There are no file types. These concepts fill a much-needed gap.
+I sincerely hope that when future systems are designed by
+manufacturers the value of some of these ingrained notions is re-examined.
+Like the politician and his `common man', manufacturers have
+their `average user'.
+
+%A J. R. Mashey
+%T PWB/UNIX Shell Tutorial
+%D September 30, 1977
+
+%A D. F. Hartley (Ed.)
+%T The Cambridge Multiple Access System \- Users Reference Manual
+%I University Mathematical Laboratory
+%C Cambridge, England
+%D 1968
+
+%A P. A. Crisman (Ed.)
+%T The Compatible Time-Sharing System
+%I M.I.T. Press
+%K whole ctss book
+%C Cambridge, Mass.
+%D 1965
+
+%T LR Parsing
+%A A. V. Aho
+%A S. C. Johnson
+%J Comp. Surveys
+%V 6
+%N 2
+%P 99-124
+%D June 1974
+
+%T Deterministic Parsing of Ambiguous Grammars
+%A A. V. Aho
+%A S. C. Johnson
+%A J. D. Ullman
+%J Comm. Assoc. Comp. Mach.
+%K acm cacm
+%V 18
+%N 8
+%P 441-452
+%D August 1975
+
+%A A. V. Aho
+%A J. D. Ullman
+%T Principles of Compiler Design
+%I Addison-Wesley
+%C Reading, Mass.
+%D 1977
+
+.[
+%r 65
+%R Comp. Sci. Tech. Rep. No. 65
+%K CSTR
+%A S. C. Johnson
+%T Lint, a C Program Checker
+%D December 1977
+%O updated version TM 78-1273-3
+%D 1978
+.]
+
+%T A Portable Compiler: Theory and Practice
+%A S. C. Johnson
+%J Proc. 5th ACM Symp. on Principles of Programming Languages
+%P 97-104
+%D January 1978
+
+.[
+%r 39
+%K CSTR
+%R Comp. Sci. Tech. Rep. No. 39
+%I Bell Laboratories
+%C Murray Hill, New Jersey
+%A M. E. Lesk
+%T Lex \(em A Lexical Analyzer Generator
+%D October 1975
+.]
+
+.[
+%r 32
+%K CSTR
+%R Comp. Sci. Tech. Rep. No. 32
+%I Bell Laboratories
+%C Murray Hill, New Jersey
+%A S. C. Johnson
+%T Yacc \(em Yet Another Compiler-Compiler
+%D July 1975
+.]
+
+%T U\s-2NIX\s0 Time-Sharing System: Portability of C Programs and the U\s-2NIX\s0 System
+%K unix bstj
+%A S. C. Johnson
+%A D. M. Ritchie
+%J Bell Sys. Tech. J.
+%V 57
+%N 6
+%P 2021-2048
+%D 1978
+
+%T Typing Documents on UNIX and GCOS: The -ms Macros for Troff
+%A M. E. Lesk
+%D 1977
+
+%A K. Thompson
+%A D. M. Ritchie
+%T U\s-2NIX\s0 Programmer's Manual
+%K unix
+%I Bell Laboratories
+%O Sixth Edition
+%D May 1975
+
+%T The Network U\s-2NIX\s0 System
+%K unix
+%A G. L. Chesson
+%J Operating Systems Review
+%V 9
+%N 5
+%P 60-66
+%D 1975
+%O Also in \f2Proc. 5th Symp. on Operating Systems Principles.\f1
+
+%T Spider \(em An Experimental Data Communications System
+%Z ctr127
+%A A. G. Fraser
+%J Proc. IEEE Conf. on Communications
+%P 21F
+%O IEEE Cat. No. 74CH0859-9-CSCB.
+%D June 1974
+
+%T A Virtual Channel Network
+%A A. G. Fraser
+%J Datamation
+%P 51-56
+%D February 1975
+
+.[
+%r 41
+%K CSTR
+%R Comp. Sci. Tech. Rep. No. 41
+%I Bell Laboratories
+%C Murray Hill, New Jersey
+%A J. W. Hunt
+%A M. D. McIlroy
+%T An Algorithm for Differential File Comparison
+%D June 1976
+.]
+
+%A F. P. Brooks, Jr.
+%T The Mythical Man-Month
+%I Addison-Wesley
+%C Reading, Mass.
+%D 1975
+%X pwb
+Readable, classic reference on software engineering and
+problems of large projects, from someone with experience in them.
+Required reading for any software engineer, even if conclusions may not
+always be agreed with.
+%br
+"The second is the most dangerous system a man ever designs." p.55.
+%br
+"Hence plan to throw one away; you will, anyhow." p.116.
+%br
+"Cosgrove has perceptively pointed out that the programmer delivers
+satisfaction of a user need rather than any tangible product.
+And both the actual need and the user's perception of that need
+will change as programs are built, tested, and used." p.117.
+%br
+"The total cost of maintaining a widely used program is typically 40 percent
+or more of the cost of developing it." p.121.
+%br
+"As shown above, amalgamating prose and program reduces the total
+number of characters to be stored." p.175.
+
+%T A Portable Compiler for the Language C
+%A A. Snyder
+%I Master's Thesis, M.I.T.
+%C Cambridge, Mass.
+%D 1974
+
+%T The C Language Calling Sequence
+%A M. E. Lesk
+%A S. C. Johnson
+%A D. M. Ritchie
+%D 1977
+
+%T Optimal Code Generation for Expression Trees
+%A A. V. Aho
+%A S. C. Johnson
+%D 1975
+%J J. Assoc. Comp. Mach.
+%K acm jacm
+%V 23
+%N 3
+%P 488-501
+%O Also in \f2Proc. ACM Symp. on Theory of Computing,\f1 pp. 207-217, 1975.
+
+%A R. Sethi
+%A J. D. Ullman
+%T The Generation of Optimal Code for Arithmetic Expressions
+%J J. Assoc. Comp. Mach.
+%K acm jacm
+%V 17
+%N 4
+%D October 1970
+%P 715-728
+%O Reprinted as pp. 229-247 in \fICompiler Techniques\fR, ed. B. W. Pollack, Auerbach, Princeton NJ (1972).
+%X pwb
+Optimal approach for straight-line, fixed
+number of regs.
+
+%T Code Generation for Machines with Multiregister
+Operations
+%A A. V. Aho
+%A S. C. Johnson
+%A J. D. Ullman
+%J Proc. 4th ACM Symp. on Principles of Programming Languages
+%P 21-28
+%D January 1977
+
diff --git a/bin/sh/USD.doc/referargs b/bin/sh/USD.doc/referargs
new file mode 100644
index 0000000..3bb6284
--- /dev/null
+++ b/bin/sh/USD.doc/referargs
@@ -0,0 +1,8 @@
+.\" $NetBSD: referargs,v 1.1 2014/07/05 19:22:02 dholland Exp $
+.\"
+.\" Arguments for refer; these were previously passed on the refer(1)
+.\" command line: -e -p Rv7man
+.R1
+accumulate
+database Rv7man
+.R2
diff --git a/bin/sh/USD.doc/t.mac b/bin/sh/USD.doc/t.mac
new file mode 100644
index 0000000..9bf65c8
--- /dev/null
+++ b/bin/sh/USD.doc/t.mac
@@ -0,0 +1,69 @@
+.\" $NetBSD: t.mac,v 1.2 2010/08/22 02:19:07 perry Exp $
+.\"
+.\" Copyright (C) Caldera International Inc. 2001-2002. All rights reserved.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions are
+.\" met:
+.\"
+.\" Redistributions of source code and documentation must retain the above
+.\" copyright notice, this list of conditions and the following
+.\" disclaimer.
+.\"
+.\" Redistributions in binary form must reproduce the above copyright
+.\" notice, this list of conditions and the following disclaimer in the
+.\" documentation and/or other materials provided with the distribution.
+.\"
+.\" All advertising materials mentioning features or use of this software
+.\" must display the following acknowledgement:
+.\"
+.\" This product includes software developed or owned by Caldera
+.\" International, Inc. Neither the name of Caldera International, Inc.
+.\" nor the names of other contributors may be used to endorse or promote
+.\" products derived from this software without specific prior written
+.\" permission.
+.\"
+.\" USE OF THE SOFTWARE PROVIDED FOR UNDER THIS LICENSE BY CALDERA
+.\" INTERNATIONAL, INC. AND CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR
+.\" IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+.\" WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+.\" DISCLAIMED. IN NO EVENT SHALL CALDERA INTERNATIONAL, INC. BE LIABLE
+.\" FOR ANY DIRECT, INDIRECT INCIDENTAL, SPECIAL, EXEMPLARY, OR
+.\" CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+.\" SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+.\" BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+.\" WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+.\" OR OTHERWISE) RISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
+.\" IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+.\"
+.\" @(#)t.mac 8.1 (Berkeley) 8/14/93
+.\"
+.ds ZZ \fB.\|.\|.\fP
+.ds ST \v'.3m'\s+2*\s0\v'-.3m'
+.ds DO \h'\w'do 'u'
+.ds Ca \h'\w'case 'u'
+.ds WH \h'\w'while 'u'
+.ds VT \|\fB\(or\fP\|
+.ds TH \h'\w'then 'u'
+.ds DC \*(DO\*(Ca
+.ds AP >\h'-.2m'>
+.ds HE <\h'-.2m'<
+. \" macros for algol 68c reference manual
+.ds DA 1977 November 1
+.ds md \v'.25m'
+.ds mu \v'-.25m'
+.ds U \*(mu\s-3
+.ds V \s0\*(md
+.ds L \*(md\s-3
+.ds M \s0\*(mu
+.ds S \s-1
+.ds T \s0
+. \" small 1
+.ds O \*S1\*T
+.ds h \|
+.ds s \|\|
+. \" ellipsis
+.ds e .\|.\|.
+. \" subscripts
+.ds 1 \*(md\s-41\s0\*(mu
+.ds 2 \*(md\s-42\s0\*(mu
diff --git a/bin/sh/USD.doc/t1 b/bin/sh/USD.doc/t1
new file mode 100644
index 0000000..075511f
--- /dev/null
+++ b/bin/sh/USD.doc/t1
@@ -0,0 +1,553 @@
+.\" $NetBSD: t1,v 1.3 2010/08/22 02:19:07 perry Exp $
+.\"
+.\" Copyright (C) Caldera International Inc. 2001-2002. All rights reserved.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions are
+.\" met:
+.\"
+.\" Redistributions of source code and documentation must retain the above
+.\" copyright notice, this list of conditions and the following
+.\" disclaimer.
+.\"
+.\" Redistributions in binary form must reproduce the above copyright
+.\" notice, this list of conditions and the following disclaimer in the
+.\" documentation and/or other materials provided with the distribution.
+.\"
+.\" All advertising materials mentioning features or use of this software
+.\" must display the following acknowledgment:
+.\"
+.\" This product includes software developed or owned by Caldera
+.\" International, Inc. Neither the name of Caldera International, Inc.
+.\" nor the names of other contributors may be used to endorse or promote
+.\" products derived from this software without specific prior written
+.\" permission.
+.\"
+.\" USE OF THE SOFTWARE PROVIDED FOR UNDER THIS LICENSE BY CALDERA
+.\" INTERNATIONAL, INC. AND CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR
+.\" IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+.\" WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+.\" DISCLAIMED. IN NO EVENT SHALL CALDERA INTERNATIONAL, INC. BE LIABLE
+.\" FOR ANY DIRECT, INDIRECT INCIDENTAL, SPECIAL, EXEMPLARY, OR
+.\" CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+.\" SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+.\" BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+.\" WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+.\" OR OTHERWISE) RISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
+.\" IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+.\"
+.\" @(#)t1 8.1 (Berkeley) 8/14/93
+.\"
+.EH 'USD:3-%''An Introduction to the UNIX Shell'
+.OH 'An Introduction to the UNIX Shell''USD:3-%'
+.\".RP
+.TL
+An Introduction to the UNIX Shell
+.AU
+S. R. Bourne
+.AI
+Murray Hill, NJ
+.AU
+(Updated for 4.3BSD by Mark Seiden)
+.AU
+(Further updated by Perry E. Metzger)\(dg
+.AB
+.FS
+\(dg This paper was updated in 2010 to reflect most features of modern
+POSIX shells, which all follow the design of S.R. Bourne's original v7
+Unix shell.
+Among these are ash, bash, ksh and others.
+Typically one of these will be installed as /bin/sh on a modern system.
+It does not describe the behavior of the c shell (csh).
+If it's the c shell (csh) you're interested in, a good place to
+begin is William Joy's paper "An Introduction to the C shell" (USD:4).
+.FE
+.LP
+The
+.ul
+shell
+is a command programming language that provides an interface
+to the
+.UX
+operating system.
+Its features include
+control-flow primitives, parameter passing, variables and
+string substitution.
+Constructs such as
+.ul
+while, if then else, case
+and
+.ul
+for
+are available.
+Two-way communication is possible between the
+.ul
+shell
+and commands.
+String-valued parameters, typically file names or flags, may be
+passed to a command.
+A return code is set by commands that may be used to determine control-flow,
+and the standard output from a command may be used
+as shell input.
+.LP
+The
+.ul
+shell
+can modify the environment
+in which commands run.
+Input and output can be redirected
+to files, and processes that communicate through `pipes'
+can be invoked.
+Commands are found by
+searching directories
+in the file system in a
+sequence that can be defined by the user.
+Commands can be read either from the terminal or from a file,
+which allows command procedures to be
+stored for later use.
+.AE
+.ds ST \v'.3m'\s+2*\s0\v'-.3m'
+.SH
+1.0\ Introduction
+.LP
+The shell is both a command language
+and a programming language
+that provides an interface to the UNIX
+operating system.
+This memorandum describes, with
+examples, the UNIX shell.
+The first section covers most of the
+everyday requirements
+of terminal users.
+Some familiarity with UNIX
+is an advantage when reading this section;
+see, for example,
+"UNIX for beginners".
+.[
+unix beginn kernigh 1978
+.]
+Section 2 describes those features
+of the shell primarily intended
+for use within shell procedures.
+These include the control-flow
+primitives and string-valued variables
+provided by the shell.
+A knowledge of a programming language
+would be a help when reading this section.
+The last section describes the more
+advanced features of the shell.
+References of the form "see \fIpipe\fP (2)"
+are to a section of the UNIX manual.
+.[
+seventh 1978 ritchie thompson
+.]
+.SH
+1.1\ Simple\ commands
+.LP
+Simple commands consist of one or more words
+separated by blanks.
+The first word is the name of the command
+to be executed; any remaining words
+are passed as arguments to the command.
+For example,
+.DS
+ who
+.DE
+is a command that prints the names
+of users logged in.
+The command
+.DS
+ ls \(mil
+.DE
+prints a list of files in the current
+directory.
+The argument \fI\(mil\fP tells \fIls\fP
+to print status information, size and
+the creation date for each file.
+.SH
+1.2\ Input\ output\ redirection
+.LP
+Most commands produce output on the standard output
+that is initially connected to the terminal.
+This output may be sent to a file
+by writing, for example,
+.DS
+ ls \(mil >file
+.DE
+The notation \fI>file\fP
+is interpreted by the shell and is not passed
+as an argument to \fIls.\fP
+If \fIfile\fP does not exist then the
+shell creates it;
+otherwise the original contents of
+\fIfile\fP are replaced with the output
+from \fIls.\fP
+Output may be appended to a file
+using the notation
+.DS
+ ls \(mil \*(APfile
+.DE
+In this case \fIfile\fP is also created if it does not already
+exist.
+.LP
+The standard input of a command may be taken
+from a file instead of the terminal by
+writing, for example,
+.DS
+ wc <file
+.DE
+The command \fIwc\fP reads its standard input
+(in this case redirected from \fIfile\fP)
+and prints the number of characters, words and
+lines found.
+If only the number of lines is required
+then
+.DS
+ wc \(mil <file
+.DE
+could be used.
+.\" I considered adding the following, but have thought better of it
+.\" for now.
+.\" -- Perry Metzger
+.\"
+.\" .LP
+.\" Error messages are typically printed by commands on a different
+.\" channel, called standard error, which may also be redirected using the
+.\" notation 2>\|.
+.\" For example
+.\" .DS
+.\" command some args >out 2>errors
+.\" .DE
+.\" will redirect standard output to the file `out' but standard error
+.\" (and thus all error messages) to `errors'.
+.\" The notation 2>&1 sets standard error pointing to the same
+.\" place as standard out.
+.\" Thus:
+.\" .DS
+.\" command some args 2>&1 >everything
+.\" .DE
+.\" will put both standard out and standard error into the file `everything'.
+.\" See section 3.7 below for more details.
+.SH
+1.3\ Pipelines\ and\ filters
+.LP
+The standard output of one command may be
+connected to the standard input of another
+by writing
+the `pipe' operator,
+indicated by \*(VT,
+as in,
+.DS
+ ls \(mil \*(VT wc
+.DE
+Two commands connected in this way constitute
+a \fIpipeline\fP and
+the overall effect is the same as
+.DS
+ ls \(mil >file; wc <file
+.DE
+except that no \fIfile\fP is used.
+Instead the two \fIprocesses\fP are connected
+by a pipe (see \fIpipe\fP(2)) and are
+run in parallel.
+Pipes are unidirectional and
+synchronization is achieved by
+halting \fIwc\fP when there is
+nothing to read and halting \fIls\fP
+when the pipe is full.
+.LP
+A \fIfilter\fP is a command
+that reads its standard input,
+transforms it in some way,
+and prints the result as output.
+One such filter, \fIgrep,\fP
+selects from its input those lines
+that contain some specified string.
+For example,
+.DS
+ ls \*(VT grep old
+.DE
+prints those lines, if any, of the output
+from \fIls\fP that contain
+the string \fIold.\fP
+Another useful filter is \fIsort\fP.
+For example,
+.DS
+ who \*(VT sort
+.DE
+will print an alphabetically sorted list
+of logged in users.
+.LP
+A pipeline may consist of more than two commands,
+for example,
+.DS
+ ls \*(VT grep old \*(VT wc \(mil
+.DE
+prints the number of file names
+in the current directory containing
+the string \fIold.\fP
+.SH
+1.4\ Background\ commands
+.LP
+To execute a command (or pipeline) the shell normally
+creates the new \fIprocesses\fP
+and waits for them to finish.
+A command may be run without waiting
+for it to finish.
+For example,
+.DS
+ cc pgm.c &
+.DE
+calls the C compiler to compile
+the file \fIpgm.c\|.\fP
+The trailing \fB&\fP is an operator that instructs the shell
+not to wait for the command to finish.
+To help keep track of such a process
+the shell reports its job number (see below) and process
+id following its creation.
+Such a command is said to be running in the \fIbackground\fP.
+By contrast, a command executed without the \fB&\fP is said to be
+running in the \fIforeground\fP.\(dg
+.FS
+\(dg Even after execution, one may move commands from the foreground
+to the background, or temporarily suspend their execution (which is
+known as \fIstopping\fP a command.
+This is described in detail in section 3.10 on \fIJob Control\fB.
+.FE
+.LP
+A list of currently active processes, including ones not associated
+with the current shell, may be obtained using the \fIps\fP(1) command.
+.SH
+1.5\ File\ name\ generation
+.LP
+Many commands accept arguments
+which are file names.
+For example,
+.DS
+ ls \(mil main.c
+.DE
+prints information relating to the file \fImain.c\fP\|.
+.LP
+The shell provides a mechanism
+for generating a list of file names
+that match a pattern.
+For example,
+.DS
+ ls \(mil \*(ST.c
+.DE
+generates, as arguments to \fIls,\fP
+all file names in the current directory that end in \fI.c\|.\fP
+The character \*(ST is a pattern that will match any string
+including the null string.
+In general \fIpatterns\fP are specified
+as follows.
+.RS
+.IP \fB\*(ST\fR 8
+Matches any string of characters
+including the null string.
+.IP \fB?\fR 8
+Matches any single character.
+.IP \fB[\*(ZZ]\fR 8
+Matches any one of the characters
+enclosed.
+A pair of characters separated by a minus will
+match any character lexically between
+the pair.
+.RE
+.LP
+For example,
+.DS
+ [a\(miz]\*(ST
+.DE
+matches all names in the current directory
+beginning with
+one of the letters \fIa\fP through \fIz.\fP
+.DS
+ /usr/fred/test/?
+.DE
+matches all names in the directory
+\fB/usr/fred/test\fP that consist of a single character.
+If no file name is found that matches
+the pattern then the pattern is passed,
+unchanged, as an argument.
+.LP
+This mechanism is useful both to save typing
+and to select names according to some pattern.
+It may also be used to find files.
+For example,
+.DS
+ echo /usr/fred/\*(ST/core
+.DE
+finds and prints the names of all \fIcore\fP files in sub-directories
+of \fB/usr/fred\|.\fP
+(\fIecho\fP is a standard UNIX command that prints
+its arguments, separated by blanks.)
+This last feature can be expensive,
+requiring a scan of all
+sub-directories of \fB/usr/fred\|.\fP
+.LP
+There is one exception to the general
+rules given for patterns.
+The character `\fB.\fP'
+at the start of a file name must be explicitly
+matched.
+.DS
+ echo \*(ST
+.DE
+will therefore echo all file names in the current
+directory not beginning
+with `\fB.\fP'\|.
+.DS
+ echo \fB.\fP\*(ST
+.DE
+will echo all those file names that begin with `\fB.\fP'\|.
+This avoids inadvertent matching
+of the names `\fB.\fP' and `\fB..\fP'
+which mean `the current directory'
+and `the parent directory'
+respectively.
+(Notice that \fIls\fP suppresses
+information for the files `\fB.\fP' and `\fB..\fP'\|.)
+.LP
+Finally, the tilde character, `\fB\(ap\fP', may be used to indicate the
+home directory of a user.
+The `\fB\(ap\fP' at the beginning of a path name followed by a
+non-alphabetic character expands to the current user's home
+directory.
+If the `\fB\(ap\fP' is followed by a login name, it expands to the named
+user's home directory.
+For example:
+.DS
+ ls \(ap
+ cd \(apegbert/
+.DE
+will list the contents of the user's home directory and then change
+to the home directory of the user ``egbert''.
+.SH
+1.6\ Quoting
+.LP
+Characters that have a special meaning
+to the shell, such as \fB< > \*(ST ? \*(VT &\|,\fR
+are called metacharacters.
+A complete list of metacharacters is given
+in appendix B.
+Any character preceded by a \fB\\\fR is \fIquoted\fP
+and loses its special meaning, if any.
+The \fB\\\fP is elided so that
+.DS
+ echo \\?
+.DE
+will echo a single \fB?\|,\fP
+and
+.DS
+ echo \\\\
+.DE
+will echo a single \fB\\\|.\fR
+To allow long strings to be continued over
+more than one line
+the sequence \fB\\newline\fP
+is ignored.
+.LP
+\fB\\\fP is convenient for quoting
+single characters.
+When more than one character needs
+quoting the above mechanism is clumsy and
+error prone.
+A string of characters may be quoted
+by enclosing the string between single quotes.
+For example,
+.DS
+ echo xx\'\*(ST\*(ST\*(ST\*(ST\'xx
+.DE
+will echo
+.DS
+ xx\*(ST\*(ST\*(ST\*(STxx
+.DE
+The quoted string may not contain
+a single quote
+but may contain newlines, which are preserved.
+This quoting mechanism is the most
+simple and is recommended
+for casual use.
+.LP
+A third quoting mechanism using double quotes
+is also available
+that prevents interpretation of some but not all
+metacharacters.
+Discussion of the
+details is deferred
+to section 3.5\|.
+.SH
+1.7\ Prompting
+.LP
+When the shell is used from a terminal it will
+issue a prompt before reading a command.
+By default this prompt is `\fB$\ \fR'\|.
+It may be changed by saying,
+for example,
+.DS
+ \s-1PS1\s0="yesdear$ "
+.DE
+that sets the prompt to be the string \fIyesdear$\|.\fP
+If a newline is typed and further input is needed
+then the shell will issue the prompt `\fB>\ \fR'\|.
+Sometimes this can be caused by mistyping
+a quote mark.
+If it is unexpected then entering the interrupt character
+(typically \s-1CONTROL-C\s0)
+will return the shell to read another command.
+This prompt may be changed by saying, for example,
+.DS
+ \s-1PS2\s0=more
+.DE
+Entering the interrupt character may also be used to terminate most
+programs running as the current foreground job.
+.LP
+(\s-1PS1\s0 and \s-1PS2\s0 are \fIshell variables\fP, which will be
+described in section 2.4 below.)
+.SH
+1.8\ The\ shell\ and\ login
+.LP
+Following \fIlogin\fP(1)
+the shell is called to read and execute
+commands typed at the terminal.
+If the user's login directory
+contains the file \fB.profile\fP
+then it is assumed to contain commands
+and is read by the shell before reading
+any commands from the terminal.
+.LP
+(Most versions of the shell also specify a file that is read and
+executed on start-up whether or not the shell is invoked by login.
+The \s-1ENV\s0 shell variable, described in section 2.4 below, can be
+used to override the name of this file.
+See the shell manual page for further information.)
+.SH
+1.9\ Summary
+.sp
+.RS
+.IP \(bu
+\fBls\fP
+.br
+Print the names of files in the current directory.
+.IP \(bu
+\fBls >file\fP
+.br
+Put the output from \fIls\fP into \fIfile.\fP
+.IP \(bu
+\fBls \*(VT wc \(mil\fR
+.br
+Print the number of files in the current directory.
+.IP \(bu
+\fBls \*(VT grep old\fR
+.br
+Print those file names containing the string \fIold.\fP
+.IP \(bu
+\fBls \*(VT grep old \*(VT wc \(mil\fR
+.br
+Print the number of files whose name contains the string \fIold.\fP
+.IP \(bu
+\fBcc pgm.c &\fR
+.br
+Run \fIcc\fP in the background.
+.RE
diff --git a/bin/sh/USD.doc/t2 b/bin/sh/USD.doc/t2
new file mode 100644
index 0000000..d49747e
--- /dev/null
+++ b/bin/sh/USD.doc/t2
@@ -0,0 +1,971 @@
+.\" $NetBSD: t2,v 1.3 2010/08/22 02:19:07 perry Exp $
+.\"
+.\" Copyright (C) Caldera International Inc. 2001-2002. All rights reserved.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions are
+.\" met:
+.\"
+.\" Redistributions of source code and documentation must retain the above
+.\" copyright notice, this list of conditions and the following
+.\" disclaimer.
+.\"
+.\" Redistributions in binary form must reproduce the above copyright
+.\" notice, this list of conditions and the following disclaimer in the
+.\" documentation and/or other materials provided with the distribution.
+.\"
+.\" All advertising materials mentioning features or use of this software
+.\" must display the following acknowledgment:
+.\"
+.\" This product includes software developed or owned by Caldera
+.\" International, Inc. Neither the name of Caldera International, Inc.
+.\" nor the names of other contributors may be used to endorse or promote
+.\" products derived from this software without specific prior written
+.\" permission.
+.\"
+.\" USE OF THE SOFTWARE PROVIDED FOR UNDER THIS LICENSE BY CALDERA
+.\" INTERNATIONAL, INC. AND CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR
+.\" IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+.\" WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+.\" DISCLAIMED. IN NO EVENT SHALL CALDERA INTERNATIONAL, INC. BE LIABLE
+.\" FOR ANY DIRECT, INDIRECT INCIDENTAL, SPECIAL, EXEMPLARY, OR
+.\" CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+.\" SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+.\" BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+.\" WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+.\" OR OTHERWISE) RISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
+.\" IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+.\"
+.\" @(#)t2 8.1 (Berkeley) 6/8/93
+.\"
+.SH
+2.0\ Shell\ scripts
+.LP
+The shell may be used to read and execute commands
+contained in a file.
+For example,
+.DS
+ sh file [ args \*(ZZ ]
+.DE
+calls the shell to read commands from \fIfile.\fP
+Such a file is called a \fIshell script.\fP
+Arguments may be supplied with the call
+and are referred to in \fIfile\fP
+using the positional parameters
+\fB$1, $2, \*(ZZ\|.\fR
+.LP
+For example, if the file \fIwg\fP contains
+.DS
+ who \*(VT grep $1
+.DE
+then
+.DS
+ sh wg fred
+.DE
+is equivalent to
+.DS
+ who \*(VT grep fred
+.DE
+.LP
+UNIX files have three independent attributes,
+\fIread,\fP \fIwrite\fP and \fIexecute.\fP
+The UNIX command \fIchmod\fP(1) may be used
+to make a file executable.
+For example,
+.DS
+ chmod +x wg
+.DE
+will ensure that the file \fIwg\fP has execute status.
+Following this, the command
+.DS
+ wg fred
+.DE
+is equivalent to
+.DS
+ sh wg fred
+.DE
+This allows shell scripts and other programs
+to be used interchangeably.
+In either case a new process is created to
+run the command.
+.LP
+The `\fB#\fP' character is used as a comment character by the shell.
+All characters following the `#' on a line are ignored.
+.LP
+A typical modern system has several different shells, some with differing
+command syntax, and it is desirable to specify which one should be
+invoked when an executable script is invoked.
+If the special comment
+.DS
+ #!/\fIpath\fP/\fIto\fP/\fIinterpreter\fP
+.DE
+appears as the first line in a script, it is used to specify the
+absolute pathname of the shell (or other interpreter) that should be
+used to execute the file.
+(Without such a line, \fB/bin/sh\fP is assumed.)
+It is best if a script explicitly states
+what shell it is intended for in this manner.
+.LP
+As well as providing names for the positional
+parameters,
+the number of positional parameters to a script
+is available as \fB$#\|.\fP
+The name of the file being executed
+is available as \fB$0\|.\fP
+.LP
+A special shell parameter \fB$\*(ST\fP
+is used to substitute for all positional parameters
+except \fB$0\|.\fP
+A typical use of this is to provide
+some default arguments,
+as in,
+.DS
+ nroff \(miT450 \(mims $\*(ST
+.DE
+which simply prepends some arguments
+to those already given.
+(The variable \fB$@\fP also expands to ``all positional
+parameters'', but is subtly different when expanded inside quotes.
+See section 3.5, below.)
+.SH
+2.1\ Control\ flow\ -\ for
+.LP
+A frequent use of shell scripts is to loop
+through the arguments (\fB$1, $2, \*(ZZ\fR)
+executing commands once for each argument.
+An example of such a script is
+\fItel\fP that searches the file
+\fB/usr/share/telnos\fR
+that contains lines of the form
+.DS
+ \*(ZZ
+ fred mh0123
+ bert mh0789
+ \*(ZZ
+.DE
+The text of \fItel\fP is
+.DS
+ #!/bin/sh
+
+ for i
+ do
+ grep $i /usr/share/telnos
+ done
+.DE
+The command
+.DS
+ tel fred
+.DE
+prints those lines in \fB/usr/share/telnos\fR
+that contain the string \fIfred\|.\fP
+.DS
+ tel fred bert
+.DE
+prints those lines containing \fIfred\fP
+followed by those for \fIbert.\fP
+.LP
+The \fBfor\fP loop notation is recognized by the shell
+and has the general form
+.DS
+ \fBfor\fR \fIname\fR \fBin\fR \fIw1 w2 \*(ZZ\fR
+ \fBdo\fR \fIcommand-list\fR
+ \fBdone\fR
+.DE
+A \fIcommand-list\fP is a sequence of one or more
+simple commands separated or terminated by a newline or semicolon.
+Furthermore, reserved words
+like \fBdo\fP and \fBdone\fP are only
+recognized following a newline or
+semicolon.
+\fIname\fP is a shell variable that is set
+to the words \fIw1 w2 \*(ZZ\fR in turn each time the \fIcommand-list\fP
+following \fBdo\fP
+is executed.
+If \fBin\fR \fIw1 w2 \*(ZZ\fR
+is omitted then the loop
+is executed once for each positional parameter;
+that is, \fBin\fR \fI$\*(ST\fR is assumed.
+.LP
+Another example of the use of the \fBfor\fP
+loop is the \fIcreate\fP command
+whose text is
+.DS
+ for i do >$i; done
+.DE
+The command
+.DS
+ create alpha beta
+.DE
+ensures that two empty files
+\fIalpha\fP and \fIbeta\fP exist
+and are empty.
+The notation \fI>file\fP may be used on its
+own to create or clear the contents of a file.
+Notice also that a semicolon (or newline) is required before \fBdone.\fP
+.SH
+2.2\ Control\ flow\ -\ case
+.LP
+A multiple way branch is provided for by the
+\fBcase\fP notation.
+For example,
+.DS
+ case $# in
+ \*(Ca1) cat \*(AP$1 ;;
+ \*(Ca2) cat \*(AP$2 <$1 ;;
+ \*(Ca\*(ST) echo \'usage: append [ from ] to\' ;;
+ esac
+.DE
+is an \fIappend\fP command.
+When called
+with one argument as
+.DS
+ append file
+.DE
+\fB$#\fP is the string \fI1\fP and
+the standard input is copied onto the
+end of \fIfile\fP
+using the \fIcat\fP command.
+.DS
+ append file1 file2
+.DE
+appends the contents of \fIfile1\fP
+onto \fIfile2.\fP
+If the number of arguments supplied to
+\fIappend\fP is other than 1 or 2
+then a message is printed indicating
+proper usage.
+.LP
+The general form of the \fBcase\fP command
+is
+.DS
+ \fBcase \fIword \fBin
+ \*(Ca\fIpattern\|\fB)\ \fIcommand-list\fB\|;;
+ \*(Ca\*(ZZ
+ \fBesac\fR
+.DE
+The shell attempts to match
+\fIword\fR with each \fIpattern,\fR
+in the order in which the patterns
+appear.
+If a match is found the
+associated \fIcommand-list\fP is
+executed and execution
+of the \fBcase\fP is complete.
+Since \*(ST is the pattern that matches any
+string it can be used for the default case.
+.LP
+A word of caution:
+no check is made to ensure that only
+one pattern matches
+the case argument.
+The first match found defines the set of commands
+to be executed.
+In the example below the commands following
+the second \*(ST will never be executed.
+.DS
+ case $# in
+ \*(Ca\*(ST) \*(ZZ ;;
+ \*(Ca\*(ST) \*(ZZ ;;
+ esac
+.DE
+.LP
+Another example of the use of the \fBcase\fP
+construction is to distinguish
+between different forms
+of an argument.
+The following example is a fragment of a \fIcc\fP command.
+.DS
+ for i
+ do case $i in
+ \*(DC\(mi[ocs]) \*(ZZ ;;
+ \*(DC\(mi\*(ST) echo "unknown flag $i" ;;
+ \*(DC\*(ST.c) /lib/c0 $i \*(ZZ ;;
+ \*(DC\*(ST) echo "unexpected argument $i" ;;
+ \*(DOesac
+ done
+.DE
+.LP
+To allow the same commands to be associated
+with more than one pattern
+the \fBcase\fP command provides
+for alternative patterns
+separated by a \*(VT\|.
+For example,
+.DS
+ case $i in
+ \*(Ca\(mix\*(VT\(miy) \*(ZZ
+ esac
+.DE
+is equivalent to
+.DS
+ case $i in
+ \*(Ca\(mi[xy]) \*(ZZ
+ esac
+.DE
+.LP
+The usual quoting conventions apply
+so that
+.DS
+ case $i in
+ \*(Ca\\?) \*(ZZ
+.DE
+will match the character \fB?\|.\fP
+.SH
+2.3\ Here\ documents
+.LP
+The shell script \fItel\fP
+in section 2.1 uses the file \fB/usr/share/telnos\fR
+to supply the data
+for \fIgrep.\fP
+An alternative is to include this
+data
+within the shell script as a \fIhere\fP document, as in,
+.DS
+ for i
+ do grep $i \*(HE!
+ \*(DO\*(ZZ
+ \*(DOfred mh0123
+ \*(DObert mh0789
+ \*(DO\*(ZZ
+ !
+ done
+.DE
+In this example
+the shell takes the lines between \fB\*(HE!\fR and \fB!\fR
+as the standard input for \fIgrep.\fP
+The string \fB!\fR is arbitrary, the document
+being terminated by a line that consists
+of the string following \*(HE\|.
+.LP
+Parameters are substituted in the document
+before it is made available to \fIgrep\fP
+as illustrated by the following script
+called \fIedg\|.\fP
+.DS
+ ed $3 \*(HE%
+ g/$1/s//$2/g
+ w
+ %
+.DE
+The call
+.DS
+ edg string1 string2 file
+.DE
+is then equivalent to the command
+.DS
+ ed file \*(HE%
+ g/string1/s//string2/g
+ w
+ %
+.DE
+and changes all occurrences of \fIstring1\fP
+in \fIfile\fP to \fIstring2\|.\fP
+Substitution can be prevented using \\
+to quote the special character \fB$\fP
+as in
+.DS
+ ed $3 \*(HE+
+ 1,\\$s/$1/$2/g
+ w
+ +
+.DE
+(This version of \fIedg\fP is equivalent to
+the first except that \fIed\fP will print
+a \fB?\fR if there are no occurrences of
+the string \fB$1\|.\fP)
+Substitution within a \fIhere\fP document
+may be prevented entirely by quoting
+the terminating string,
+for example,
+.DS
+ grep $i \*(HE'end'
+ \*(ZZ
+ end
+.DE
+The document is presented
+without modification to \fIgrep.\fP
+If parameter substitution is not required
+in a \fIhere\fP document this latter form
+is more efficient.
+.SH
+2.4\ Shell\ variables\(dg
+.LP
+.FS
+Also known as \fIenvironment variables\fB, see \fIenvironment\fB(7).
+.FE
+The shell
+provides string-valued variables.
+Variable names begin with a letter
+and consist of letters, digits and
+underscores.
+Variables may be given values by writing, for example,
+.DS
+ user=fred\ box=m000\ acct=mh0000
+.DE
+which assigns values to the variables
+\fBuser, box\fP and \fBacct.\fP
+A variable may be set to the null string
+by saying, for example,
+.DS
+ null=
+.DE
+The value of a variable is substituted
+by preceding its name with \fB$\|\fP;
+for example,
+.DS
+ echo $user
+.DE
+will echo \fIfred.\fP
+.LP
+Variables may be used interactively
+to provide abbreviations for frequently
+used strings.
+For example,
+.DS
+ b=/usr/fred/bin
+ mv pgm $b
+.DE
+will move the file \fIpgm\fP
+from the current directory to the directory \fB/usr/fred/bin\|.\fR
+A more general notation is available for parameter
+(or variable)
+substitution, as in,
+.DS
+ echo ${user}
+.DE
+which is equivalent to
+.DS
+ echo $user
+.DE
+and is used when the parameter name is
+followed by a letter or digit.
+For example,
+.DS
+ tmp=/tmp/ps
+ ps a >${tmp}a
+.DE
+will direct the output of \fIps\fR
+to the file \fB/tmp/psa,\fR
+whereas,
+.DS
+ ps a >$tmpa
+.DE
+would cause the value of the variable \fBtmpa\fP
+to be substituted.
+.LP
+Except for \fB$?\fP the following
+are set initially by the shell.
+\fB$?\fP is set after executing each command.
+.RS
+.IP \fB$?\fP 8
+The exit status (return code)
+of the last command executed
+as a decimal string.
+Most commands return a zero exit status
+if they complete successfully,
+otherwise a non-zero exit status is returned.
+Testing the value of return codes is dealt with
+later under \fBif\fP and \fBwhile\fP commands.
+.IP \fB$#\fP 8
+The number of positional parameters
+(in decimal).
+Used, for example, in the \fIappend\fP command
+to check the number of parameters.
+.IP \fB$$\fP 8
+The process number of this shell (in decimal).
+Since process numbers are unique among
+all existing processes, this string is
+frequently used to generate
+unique
+temporary file names.
+For example,
+.DS
+ ps a >/tmp/ps$$
+ \*(ZZ
+ rm /tmp/ps$$
+.DE
+.IP \fB$\|!\fP 8
+The process number of the last process
+run in the background (in decimal).
+.IP \fB$\(mi\fP 8
+The current shell flags, such as
+\fB\(mix\fR and \fB\(miv\|.\fR
+.RE
+.LP
+Some variables have a special meaning to the
+shell and should be avoided for general
+use.
+.RS
+.IP \fB$\s-1MAIL\s0\fP 8
+When used interactively
+the shell looks at the file
+specified by this variable
+before it issues a prompt.
+If the specified file has been modified
+since it
+was last looked at the shell
+prints the message
+\fIyou have mail\fP before prompting
+for the next command.
+This variable is typically set
+in the file \fB.profile,\fP
+in the user's login directory.
+For example,
+.DS
+ \s-1MAIL\s0=/usr/spool/mail/fred
+.DE
+.IP \fB$\s-1HOME\s0\fP 8
+The default argument
+for the \fIcd\fP command.
+The current directory is used to resolve
+file name references that do not begin with
+a \fB/\|,\fR
+and is changed using the \fIcd\fP command.
+For example,
+.DS
+ cd /usr/fred/bin
+.DE
+makes the current directory \fB/usr/fred/bin\|.\fR
+.DS
+ cat wn
+.DE
+will print on the terminal the file \fIwn\fP
+in this directory.
+The command
+\fIcd\fP with no argument
+is equivalent to
+.DS
+ cd $\s-1HOME\s0
+.DE
+This variable is also typically set in the
+the user's login profile.
+.IP \fB$\s-1PWD\s0\fP 8
+The current working directory. Set by the \fIcd\fB command.
+.IP \fB$\s-1PATH\s0\fP 8
+A list of directories that contain commands (the \fIsearch path\fR\|).
+Each time a command is executed by the shell
+a list of directories is searched
+for an executable file.
+.ne 5
+If \fB$\s-1PATH\s0\fP is not set
+then the current directory,
+\fB/bin\fP, and \fB/usr/bin\fP are searched by default.
+.ne 5
+Otherwise \fB$\s-1PATH\s0\fP consists of directory
+names separated by \fB:\|.\fP
+For example,
+.DS
+ \s-1PATH\s0=\fB:\fP/usr/fred/bin\fB:\fP/bin\fB:\fP/usr/bin
+.DE
+specifies that the current directory
+(the null string before the first \fB:\fP\|),
+\fB/usr/fred/bin, /bin \fRand\fP /usr/bin\fR
+are to be searched in that order.
+In this way individual users
+can have their own `private' commands
+that are accessible independently
+of the current directory.
+If the command name contains a \fB/\fR then this directory search
+is not used; a single attempt
+is made to execute the command.
+.IP \fB$\s-1PS1\s0\fP 8
+The primary shell prompt string, by default, `\fB$\ \fR'.
+.IP \fB$\s-1PS2\s0\fP 8
+The shell prompt when further input is needed,
+by default, `\fB>\ \fR'.
+.IP \fB$\s-1IFS\s0\fP 8
+The set of characters used by \fIblank
+interpretation\fR (see section 3.5).
+.IP \fB$\s-1ENV\s0\fP 8
+The shell reads and executes the commands in the file
+specified by this variable when it is initially started.
+Unlike the \fB.profile\fP file, these commands are executed by all
+shells, not just the one started at login.
+(Most versions of the shell specify a filename that is used if
+\s-1ENV\s0 is not explicitly set. See the manual page for your shell.)
+.RE
+.SH
+2.5\ The\ test\ command
+.LP
+The \fItest\fP command, although not part of the shell,
+is intended for use by shell programs.
+For example,
+.DS
+ test \(mif file
+.DE
+returns zero exit status if \fIfile\fP
+exists and non-zero exit status otherwise.
+In general \fItest\fP evaluates a predicate
+and returns the result as its exit status.
+Some of the more frequently used \fItest\fP
+arguments are given here, see \fItest\fP(1)
+for a complete specification.
+.DS
+ test s true if the argument \fIs\fP is not the null string
+ test \(mif file true if \fIfile\fP exists
+ test \(mir file true if \fIfile\fP is readable
+ test \(miw file true if \fIfile\fP is writable
+ test \(mid file true if \fIfile\fP is a directory
+.DE
+The \fItest\fP command is known as `\fB[\fP' and may be invoked as
+such.
+For aesthetic reasons, the command ignores a close bracket `\fB]\fP' given
+at the end of a command so
+.DS
+ [ -f filename ]
+.DE
+and
+.DS
+ test -f filename
+.DE
+are completely equivalent.
+Typically, the bracket notation is used when \fItest\fP is invoked inside
+shell control constructs.
+.SH
+2.6\ Control\ flow\ -\ while
+.LP
+The actions of
+the \fBfor\fP loop and the \fBcase\fP
+branch are determined by data available to the shell.
+A \fBwhile\fP or \fBuntil\fP loop
+and an \fBif then else\fP branch
+are also provided whose
+actions are determined by the exit status
+returned by commands.
+A \fBwhile\fP loop has the general form
+.DS
+ \fBwhile\fP \fIcommand-list\*1\fP
+ \fBdo\fP \fIcommand-list\*2\fP
+ \fBdone\fP
+.DE
+.LP
+The value tested by the \fBwhile\fP command
+is the exit status of the last simple command
+following \fBwhile.\fP
+Each time round the loop
+\fIcommand-list\*1\fP is executed;
+if a zero exit status is returned then
+\fIcommand-list\*2\fP
+is executed;
+otherwise, the loop terminates.
+For example,
+.DS
+ while [ $1 ]
+ do \*(ZZ
+ \*(DOshift
+ done
+.DE
+is equivalent to
+.DS
+ for i
+ do \*(ZZ
+ done
+.DE
+\fIshift\fP is a shell command that
+renames the positional parameters
+\fB$2, $3, \*(ZZ\fR as \fB$1, $2, \*(ZZ\fR
+and loses \fB$1\|.\fP
+.LP
+Another kind of use for the \fBwhile/until\fP
+loop is to wait until some
+external event occurs and then run
+some commands.
+In an \fBuntil\fP loop
+the termination condition is reversed.
+For example,
+.DS
+ until [ \(mif file ]
+ do sleep 300; done
+ \fIcommands\fP
+.DE
+will loop until \fIfile\fP exists.
+Each time round the loop it waits for
+5 minutes before trying again.
+(Presumably another process
+will eventually create the file.)
+.LP
+The most recent enclosing loop may be exited with the \fBbreak\fP
+command, or the rest of the body skipped and the next iteration begun
+with the \fBcontinue\fP command.
+.LP
+The commands \fItrue\fP(1) and \fIfalse\fP(1) return 0 and non-zero
+exit statuses respectively. They are sometimes of use in control flow,
+e.g.:
+.DS
+ while true
+ do date; sleep 5
+ done
+.DE
+is an infinite loop that prints the date and time every five seconds.
+.SH
+2.7\ Control\ flow\ -\ if
+.LP
+Also available is a
+general conditional branch
+of the form,
+.DS
+ \fBif\fP \fIcommand-list
+ \fBthen \fIcommand-list
+ \fBelse \fIcommand-list
+ \fBfi\fR
+.DE
+that tests the value returned by the last simple command
+following \fBif.\fP
+.LP
+The \fBif\fP command may be used
+in conjunction with the \fItest\fP command
+to test for the existence of a file as in
+.DS
+ if [ \(mif file ]
+ then \fIprocess file\fP
+ else \fIdo something else\fP
+ fi
+.DE
+.LP
+An example of the use of \fBif, case\fP
+and \fBfor\fP constructions is given in
+section 2.10\|.
+.LP
+A multiple test \fBif\fP command
+of the form
+.DS
+ if \*(ZZ
+ then \*(ZZ
+ else if \*(ZZ
+ then \*(ZZ
+ else if \*(ZZ
+ \*(ZZ
+ fi
+ fi
+ fi
+.DE
+may be written using an extension of the \fBif\fP
+notation as,
+.DS
+ if \*(ZZ
+ then \*(ZZ
+ elif \*(ZZ
+ then \*(ZZ
+ elif \*(ZZ
+ \*(ZZ
+ fi
+.DE
+.LP
+The following example is an implementation of the \fItouch\fP command
+which changes the `last modified' time for a list
+of files.
+The command may be used in conjunction
+with \fImake\fP(1) to force recompilation of a list
+of files.
+.DS
+ #!/bin/sh
+
+ flag=
+ for i
+ do case $i in
+ \*(DC\(mic) flag=N ;;
+ \*(DC\*(ST) if [ \(mif $i ]
+ \*(DC then cp $i junk$$; mv junk$$ $i
+ \*(DC elif [ $flag ]
+ \*(DC then echo file \\'$i\\' does not exist
+ \*(DC else >$i
+ \*(DC fi
+ \*(DO esac
+ done
+.DE
+The \fB\(mic\fP flag is used in this command to
+force subsequent files to be created if they do not already exist.
+Otherwise, if the file does not exist, an error message is printed.
+The shell variable \fIflag\fP
+is set to some non-null string if the \fB\(mic\fP
+argument is encountered.
+The commands
+.DS
+ cp \*(ZZ; mv \*(ZZ
+.DE
+copy the file and then overwrite it with the copy,
+thus causing the last modified date to be updated.
+.LP
+The sequence
+.DS
+ if command1
+ then command2
+ fi
+.DE
+may be written
+.DS
+ command1 && command2
+.DE
+Conversely,
+.DS
+ command1 \*(VT\*(VT command2
+.DE
+executes \fIcommand2\fP only if \fIcommand1\fP
+fails.
+In each case the value returned
+is that of the last simple command executed.
+.LP
+Placing a `\fB!\fP' in front of a pipeline inverts its exit
+status, almost in the manner of the C operator of the same name.
+Thus:
+.DS
+ if ! [ -d $1 ]
+ then
+ echo $1 is not a directory
+ fi
+.DE
+will print a message only if $1 is not a directory.
+.SH
+2.8\ Command\ grouping
+.LP
+Commands may be grouped in two ways,
+.DS
+ \fB{\fI command-list\fB ; }\fR
+.DE
+and
+.DS
+ \fB(\fI command-list\fB )\fR
+.DE
+.LP
+In the first \fIcommand-list\fP is simply executed.
+The second form executes \fIcommand-list\fP
+as a separate process.
+For example,
+.DS
+ (cd x; rm junk )
+.DE
+executes \fIrm junk\fP in the directory
+\fBx\fP without changing the current
+directory of the invoking shell.
+.LP
+The commands
+.DS
+ cd x; rm junk
+.DE
+have the same effect but leave the invoking
+shell in the directory \fBx.\fP
+.SH
+2.9\ Shell\ Functions
+.LP
+A function may be defined by the syntax
+.DS
+ \fIfuncname\fP() \fB{\fI command-list\fB ; }\fR
+.DE
+Functions are invoked within a script as though they were separate
+commands of the same name.
+While they are executed, the
+positional parameters \fB$1, $2, \*(ZZ\fR are temporarily set to the
+arguments passed to the function. For example:
+.DS
+ count() {
+ echo $2 : $#
+ }
+
+ count a b c
+.DE
+would print `b : 3'.
+.SH
+2.10\ Debugging\ shell\ scripts
+.LP
+The shell provides two tracing mechanisms
+to help when debugging shell scripts.
+The first is invoked within the script
+as
+.DS
+ set \(miv
+.DE
+(\fBv\fP for verbose) and causes lines of the
+script to be printed as they are read.
+It is useful to help isolate syntax errors.
+It may be invoked without modifying the script
+by saying
+.DS
+ sh \(miv \fIscript\fP \*(ZZ
+.DE
+where \fIscript\fP is the name of the shell script.
+This flag may be used in conjunction
+with the \fB\(min\fP flag which prevents
+execution of subsequent commands.
+(Note that saying \fIset \(min\fP at a terminal
+will render the terminal useless
+until an end-of-file is typed.)
+.LP
+The command
+.DS
+ set \(mix
+.DE
+will produce an execution
+trace.
+Following parameter substitution
+each command is printed as it is executed.
+(Try these at the terminal to see
+what effect they have.)
+Both flags may be turned off by saying
+.DS
+ set \(mi
+.DE
+and the current setting of the shell flags is available as \fB$\(mi\|\fR.
+.SH
+2.11\ The\ man\ command
+.LP
+The following is a simple implementation of the \fIman\fP command,
+which is used to display sections of the UNIX manual on your terminal.
+It is called, for example, as
+.DS
+ man sh
+ man \(mit ed
+ man 2 fork
+.DE
+In the first the manual section for \fIsh\fP
+is displayed..
+Since no section is specified, section 1 is used.
+The second example will typeset (\fB\(mit\fP option)
+the manual section for \fIed.\fP
+The last prints the \fIfork\fP manual page
+from section 2, which covers system calls.
+.sp 2
+.DS
+ #!/bin/sh
+
+ cd /usr/share/man
+
+ # "#" is the comment character
+ # default is nroff ($N), section 1 ($s)
+ N=n\ s=1
+
+ for i
+ do case $i in
+.sp .5
+ \*(DC[1\(mi9]\*(ST) s=$i ;;
+.sp .5
+ \*(DC\(mit) N=t ;;
+.sp .5
+ \*(DC\(min) N=n ;;
+.sp .5
+ \*(DC\(mi\*(ST) echo unknown flag \\'$i\\' ;;
+.sp .5
+ \*(DC\*(ST) if [ \(mif man$s/$i.$s ]
+ \*(DC then
+ \*(DC ${N}roff \(miman man$s/$i.$s
+ \*(DC else # look through all manual sections
+ \*(DC found=no
+ \*(DC for j in 1 2 3 4 5 6 7 8 9
+ \*(DC do
+ \*(DC \*(DOif [ \(mif man$j/$i.$j ]
+ \*(DC \*(DOthen
+ \*(DC \*(DO\*(THman $j $i
+ \*(DC \*(DO\*(THfound=yes
+ \*(DC \*(DO\*(THbreak
+ \*(DC \*(DOfi
+ \*(DC done
+ \*(DC case $found in
+ \*(DC \*(Cano) echo \\'$i: manual page not found\\'
+ \*(DC esac
+ \*(DC fi
+ \*(DOesac
+ done
+.DE
+.ce
+.ft B
+Figure 1. A version of the man command
+.ft R
diff --git a/bin/sh/USD.doc/t3 b/bin/sh/USD.doc/t3
new file mode 100644
index 0000000..aab53ee
--- /dev/null
+++ b/bin/sh/USD.doc/t3
@@ -0,0 +1,976 @@
+.\" $NetBSD: t3,v 1.3 2010/08/22 02:19:07 perry Exp $
+.\"
+.\" Copyright (C) Caldera International Inc. 2001-2002. All rights reserved.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions are
+.\" met:
+.\"
+.\" Redistributions of source code and documentation must retain the above
+.\" copyright notice, this list of conditions and the following
+.\" disclaimer.
+.\"
+.\" Redistributions in binary form must reproduce the above copyright
+.\" notice, this list of conditions and the following disclaimer in the
+.\" documentation and/or other materials provided with the distribution.
+.\"
+.\" All advertising materials mentioning features or use of this software
+.\" must display the following acknowledgement:
+.\"
+.\" This product includes software developed or owned by Caldera
+.\" International, Inc. Neither the name of Caldera International, Inc.
+.\" nor the names of other contributors may be used to endorse or promote
+.\" products derived from this software without specific prior written
+.\" permission.
+.\"
+.\" USE OF THE SOFTWARE PROVIDED FOR UNDER THIS LICENSE BY CALDERA
+.\" INTERNATIONAL, INC. AND CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR
+.\" IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+.\" WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+.\" DISCLAIMED. IN NO EVENT SHALL CALDERA INTERNATIONAL, INC. BE LIABLE
+.\" FOR ANY DIRECT, INDIRECT INCIDENTAL, SPECIAL, EXEMPLARY, OR
+.\" CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+.\" SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+.\" BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+.\" WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+.\" OR OTHERWISE) RISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
+.\" IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+.\"
+.\" @(#)t3 8.1 (Berkeley) 6/8/93
+.\"
+.SH
+3.0\ Keyword\ parameters
+.LP
+Shell variables may be given values
+by assignment
+or when a shell script is invoked.
+An argument to a command of the form
+\fIname=value\fP
+that precedes the command name
+causes \fIvalue\fP
+to be assigned to \fIname\fP
+before execution of the command begins.
+The value of \fIname\fP in the invoking
+shell is not affected.
+For example,
+.DS
+ user=fred\ command
+.DE
+will execute \fIcommand\fP with
+\fBuser\fP set to \fIfred\fP.
+.\" Removed by Perry Metzger because -k is not in POSIX
+.\"
+.\" The \fB\(mik\fR flag causes arguments of the form
+.\" \fIname=value\fP to be interpreted in this way
+.\" anywhere in the argument list.
+.\" Such \fInames\fP are sometimes
+.\" called keyword parameters.
+.\" If any arguments remain they
+.\" are available as positional
+.\" parameters \fB$1, $2, \*(ZZ\|.\fP
+.LP
+The \fIset\fP command
+may also be used to set positional parameters
+from within a script.
+For example,
+.DS
+ set\ \(mi\(mi\ \*(ST
+.DE
+will set \fB$1\fP to the first file name
+in the current directory, \fB$2\fP to the next,
+and so on.
+Note that the first argument, \(mi\(mi, ensures correct treatment
+when the first file name begins with a \(mi\|.
+.LP
+.SH
+3.1\ Parameter\ transmission
+.LP
+When a command is executed both positional parameters
+and shell variables may be set on invocation.
+Variables are also made available implicitly
+to a command
+by specifying in advance that such parameters
+are to be exported from the invoking shell.
+For example,
+.DS
+ export\ user\ box=red
+.DE
+marks the variables \fBuser\fP and \fBbox\fP
+for export (setting \fBbox\fP to ``red'' in the process).
+When a command is invoked
+copies are made of all exportable variables
+(also known as \fIenvironment variables\fP)
+for use within the invoked program.
+Modification of such variables
+within an invoked command does not
+affect the values in the invoking shell.
+It is generally true of
+a shell script or other program
+that it
+cannot modify the state
+of its caller without explicit
+actions on the part of the caller.
+.\" Removed by Perry Metzger because this is confusing to beginners.
+.\"
+.\" (Shared file descriptors are an
+.\" exception to this rule.)
+.LP
+Names whose value is intended to remain
+constant may be declared \fIreadonly\|.\fP
+The form of this command is the same as that of the \fIexport\fP
+command,
+.DS
+ readonly name[=value] \*(ZZ
+.DE
+Subsequent attempts to set readonly variables
+are illegal.
+.SH
+3.2\ Parameter\ substitution
+.LP
+If a shell parameter is not set
+then the null string is substituted for it.
+For example, if the variable \fBd\fP
+is not set
+.DS
+ echo $d
+.DE
+or
+.DS
+ echo ${d}
+.DE
+will echo nothing.
+A default string may be given
+as in
+.DS
+ echo ${d:\(mi\fB.\fR}
+.DE
+which will echo
+the value of the variable \fBd\fP
+if it is set and not null and `\fB.\fP' otherwise.
+The default string is evaluated using the usual
+quoting conventions so that
+.DS
+ echo ${d:\(mi\'\*(ST\'}
+.DE
+will echo \fB\*(ST\fP if the variable \fBd\fP
+is not set or null.
+Similarly
+.DS
+ echo ${d:\(mi$1}
+.DE
+will echo the value of \fBd\fP if it is set and not null
+and the value (if any) of \fB$1\fP otherwise.
+.LP
+The notation ${d:+\fB.\fR} performs the inverse operation. It
+substitutes `\fB.\fP' if \fBd\fP is set or not null, and otherwise
+substitutes null.
+.LP
+A variable may be assigned a default value
+using
+the notation
+.DS
+ echo ${d:=\fB.\fR}
+.DE
+which substitutes the same string as
+.DS
+ echo ${d:\(mi\fB.\fR}
+.DE
+and if \fBd\fP were not previously set or null
+then it will be set to the string `\fB.\fP'\|.
+.LP
+If there is no sensible default then
+the notation
+.DS
+ echo ${d:?\fImessage\fP}
+.DE
+will echo the value of the variable \fBd\fP if it is set and not null,
+otherwise \fImessage\fP is printed by the shell and
+execution of the shell script is abandoned.
+If \fImessage\fP is absent then a standard message
+is printed.
+A shell script that requires some variables
+to be set might start as follows:
+.DS
+ :\ ${user:?}\ ${acct:?}\ ${bin:?}
+ \*(ZZ
+.DE
+Colon (\fB:\fP) is a command
+that is
+built in to the shell and does nothing
+once its arguments have been evaluated.
+If any of the variables \fBuser, acct\fP
+or \fBbin\fP are not set then the shell
+will abandon execution of the script.
+.SH
+3.3\ Command\ substitution
+.LP
+The standard output from a command can be
+substituted in a similar way to parameters.
+The command \fIpwd\fP prints on its standard
+output the name of the current directory.
+For example, if the current directory is
+\fB/usr/fred/bin\fR
+then the commands
+.DS
+ d=$(pwd)
+.DE
+(or the older notation d=\`pwd\`)
+is equivalent to
+.DS
+ d=/usr/fred/bin
+.DE
+.LP
+The entire string inside $(\*(ZZ)\| (or between grave accents \`\*(ZZ\`)
+is taken as the command
+to be executed
+and is replaced with the output from
+the command.
+(The difference between the $(\*(ZZ) and \`\*(ZZ\` notations is that
+the former may be nested, while the latter cannot be.)
+.LP
+The command is written using the usual quoting conventions,
+except that inside \`\*(ZZ\`
+a \fB\`\fR must be escaped using
+a \fB\\\|\fR.
+For example,
+.DS
+ ls $(echo "$HOME")
+.DE
+is equivalent to
+.DS
+ ls $HOME
+.DE
+Command substitution occurs in all contexts
+where parameter substitution occurs (including \fIhere\fP documents) and the
+treatment of the resulting text is the same
+in both cases.
+This mechanism allows string
+processing commands to be used within
+shell scripts.
+An example of such a command is \fIbasename\fP
+which removes a specified suffix from a string.
+For example,
+.DS
+ basename main\fB.\fPc \fB.\fPc
+.DE
+will print the string \fImain\|.\fP
+Its use is illustrated by the following
+fragment from a \fIcc\fP command.
+.DS
+ case $A in
+ \*(Ca\*(ZZ
+ \*(Ca\*(ST\fB.\fPc) B=$(basename $A \fB.\fPc)
+ \*(Ca\*(ZZ
+ esac
+.DE
+that sets \fBB\fP to the part of \fB$A\fP
+with the suffix \fB.c\fP stripped.
+.LP
+Here are some composite examples.
+.RS
+.IP \(bu
+.ft B
+for i in \`ls \(mit\`; do \*(ZZ
+.ft R
+.br
+The variable \fBi\fP is set
+to the names of files in time order,
+most recent first.
+.IP \(bu
+.ft B
+set \(mi\(mi\| \`date\`; echo $6 $2 $3, $4
+.ft R
+.br
+will print, e.g.,
+.ft I
+1977 Nov 1, 23:59:59
+.ft R
+.RE
+.SH
+3.4\ Arithmetic\ Expansion
+.LP
+Within a $((\*(ZZ)) construct, integer arithmetic operations are
+evaluated.
+(The $ in front of variable names is optional within $((\*(ZZ)).
+For example:
+.DS
+ x=5; y=1
+ echo $(($x+3*2))
+ echo $((y+=x))
+ echo $y
+.DE
+will print `11', then `6', then `6' again.
+Most of the constructs permitted in C arithmetic operations are
+permitted though some (like `++') are not universally supported \(em
+see the shell manual page for details.
+.SH
+3.5\ Evaluation\ and\ quoting
+.LP
+The shell is a macro processor that
+provides parameter substitution, command substitution and file
+name generation for the arguments to commands.
+This section discusses the order in which
+these evaluations occur and the
+effects of the various quoting mechanisms.
+.LP
+Commands are parsed initially according to the grammar
+given in appendix A.
+Before a command is executed
+the following
+substitutions occur.
+.RS
+.IP \(bu
+parameter substitution, e.g. \fB$user\fP
+.IP \(bu
+command substitution, e.g. \fB$(pwd)\fP or \fB\`pwd\`\fP
+.IP \(bu
+arithmetic expansion, e.g. \fB$(($count+1))\fP
+.RS
+.LP
+Only one evaluation occurs so that if, for example, the value of the variable
+\fBX\fP
+is the string \fI$y\fP
+then
+.DS
+ echo $X
+.DE
+will echo \fI$y\|.\fP
+.RE
+.IP \(bu
+blank interpretation
+.RS
+.LP
+Following the above substitutions
+the resulting characters
+are broken into non-blank words (\fIblank interpretation\fP).
+For this purpose `blanks' are the characters of the string
+\fB$\s-1IFS\s0\fP.
+By default, this string consists of blank, tab and newline.
+The null string
+is not regarded as a word unless it is quoted.
+For example,
+.DS
+ echo \'\'
+.DE
+will pass on the null string as the first argument to \fIecho\fP,
+whereas
+.DS
+ echo $null
+.DE
+will call \fIecho\fR with no arguments
+if the variable \fBnull\fP is not set
+or set to the null string.
+.RE
+.IP \(bu
+file name generation
+.RS
+.LP
+Each word
+is then scanned for the file pattern characters
+\fB\*(ST, ?\fR and \fB[\*(ZZ]\fR
+and an alphabetical list of file names
+is generated to replace the word.
+Each such file name is a separate argument.
+.RE
+.RE
+.LP
+The evaluations just described also occur
+in the list of words associated with a \fBfor\fP
+loop.
+Only substitution occurs
+in the \fIword\fP used
+for a \fBcase\fP branch.
+.LP
+As well as the quoting mechanisms described
+earlier using \fB\\\fR and \fB\'\*(ZZ\'\fR
+a third quoting mechanism is provided using double quotes.
+Within double quotes parameter and command substitution
+occurs but file name generation and the interpretation
+of blanks does not.
+The following characters
+have a special meaning within double quotes
+and may be quoted using \fB\\\|.\fP
+.DS
+ \fB$ \fPparameter substitution
+ \fB$()\fP command substitution
+ \fB\`\fP command substitution
+ \fB"\fP ends the quoted string
+ \fB\e\fP quotes the special characters \fB$ \` " \e\fP
+.DE
+For example,
+.DS
+ echo "$x"
+.DE
+will pass the value of the variable \fBx\fP as a
+single argument to \fIecho.\fP
+Similarly,
+.DS
+ echo "$\*(ST"
+.DE
+will pass the positional parameters as a single
+argument and is equivalent to
+.DS
+ echo "$1 $2 \*(ZZ"
+.DE
+The notation \fB$@\fP
+is the same as \fB$\*(ST\fR
+except when it is quoted.
+.DS
+ echo "$@"
+.DE
+will pass the positional parameters, unevaluated, to \fIecho\fR
+and is equivalent to
+.DS
+ echo "$1" "$2" \*(ZZ
+.DE
+.LP
+The following table gives, for each quoting mechanism,
+the shell metacharacters that are evaluated.
+.DS
+.ce
+.ft I
+metacharacter
+.ft
+.in 1.5i
+ \e $ * \` " \'
+\' n n n n n t
+\` y n n t n n
+" y y n y t n
+
+ t terminator
+ y interpreted
+ n not interpreted
+
+.in
+.ft B
+.ce
+Figure 2. Quoting mechanisms
+.ft
+.DE
+.LP
+In cases where more than one evaluation of a string
+is required the built-in command \fIeval\fP
+may be used.
+For example,
+if the variable \fBX\fP has the value
+\fI$y\fP, and if \fBy\fP has the value \fIpqr\fP
+then
+.DS
+ eval echo $X
+.DE
+will echo the string \fIpqr\|.\fP
+.LP
+In general the \fIeval\fP command
+evaluates its arguments (as do all commands)
+and treats the result as input to the shell.
+The input is read and the resulting command(s)
+executed.
+For example,
+.DS
+ wg=\'eval who\*(VTgrep\'
+ $wg fred
+.DE
+is equivalent to
+.DS
+ who\*(VTgrep fred
+.DE
+In this example,
+\fIeval\fP is required
+since there is no interpretation
+of metacharacters, such as \fB\*(VT\|\fR, following
+substitution.
+.SH
+3.6\ Error\ handling
+.LP
+The treatment of errors detected by
+the shell depends on the type of error
+and on whether the shell is being
+used interactively.
+An interactive shell is one whose
+input and output are connected
+to a terminal.
+.\" Removed by Perry Metzger, obsolete and excess detail
+.\"
+.\" (as determined by
+.\" \fIgtty\fP (2)).
+A shell invoked with the \fB\(mii\fP
+flag is also interactive.
+.LP
+Execution of a command (see also 3.7) may fail
+for any of the following reasons.
+.IP \(bu
+Input output redirection may fail.
+For example, if a file does not exist
+or cannot be created.
+.IP \(bu
+The command itself does not exist
+or cannot be executed.
+.IP \(bu
+The command terminates abnormally,
+for example, with a "bus error"
+or "memory fault".
+See Figure 2 below for a complete list
+of UNIX signals.
+.IP \(bu
+The command terminates normally
+but returns a non-zero exit status.
+.LP
+In all of these cases the shell
+will go on to execute the next command.
+Except for the last case an error
+message will be printed by the shell.
+All remaining errors cause the shell
+to exit from a script.
+An interactive shell will return
+to read another command from the terminal.
+Such errors include the following.
+.IP \(bu
+Syntax errors.
+e.g., if \*(ZZ then \*(ZZ done
+.IP \(bu
+A signal such as interrupt.
+The shell waits for the current
+command, if any, to finish execution and
+then either exits or returns to the terminal.
+.IP \(bu
+Failure of any of the built-in commands
+such as \fIcd.\fP
+.LP
+The shell flag \fB\(mie\fP
+causes the shell to terminate
+if any error is detected.
+.DS
+1 hangup
+2 interrupt
+3* quit
+4* illegal instruction
+5* trace trap
+6* IOT instruction
+7* EMT instruction
+8* floating point exception
+9 kill (cannot be caught or ignored)
+10* bus error
+11* segmentation violation
+12* bad argument to system call
+13 write on a pipe with no one to read it
+14 alarm clock
+15 software termination (from \fIkill\fP (1))
+
+.DE
+.ft B
+.ce
+Figure 3. UNIX signals\(dg
+.ft
+.FS
+\(dg Additional signals have been added in modern Unix.
+See \fIsigvec\fP(2) or \fIsignal\fP(3) for an up-to-date list.
+.FE
+Those signals marked with an asterisk
+produce a core dump
+if not caught.
+However,
+the shell itself ignores quit which is the only
+external signal that can cause a dump.
+The signals in this list of potential interest
+to shell programs are 1, 2, 3, 14 and 15.
+.SH
+3.7\ Fault\ handling
+.LP
+shell scripts normally terminate
+when an interrupt is received from the
+terminal.
+The \fItrap\fP command is used
+if some cleaning up is required, such
+as removing temporary files.
+For example,
+.DS
+ trap\ \'rm\ /tmp/ps$$; exit\'\ 2
+.DE
+sets a trap for signal 2 (terminal
+interrupt), and if this signal is received
+will execute the commands
+.DS
+ rm /tmp/ps$$; exit
+.DE
+\fIexit\fP is
+another built-in command
+that terminates execution of a shell script.
+The \fIexit\fP is required; otherwise,
+after the trap has been taken,
+the shell will resume executing
+the script
+at the place where it was interrupted.
+.LP
+UNIX signals can be handled in one of three ways.
+They can be ignored, in which case
+the signal is never sent to the process.
+They can be caught, in which case the process
+must decide what action to take when the
+signal is received.
+Lastly, they can be left to cause
+termination of the process without
+it having to take any further action.
+If a signal is being ignored
+on entry to the shell script, for example,
+by invoking it in the background (see 3.7) then \fItrap\fP
+commands (and the signal) are ignored.
+.LP
+The use of \fItrap\fP is illustrated
+by this modified version of the \fItouch\fP
+command (Figure 4).
+The cleanup action is to remove the file \fBjunk$$\fR\|.
+.DS
+ #!/bin/sh
+
+ flag=
+ trap\ \'rm\ \(mif\ junk$$;\ exit\'\ 1 2 3 15
+ for i
+ do\ case\ $i\ in
+ \*(DC\(mic) flag=N ;;
+ \*(DC\*(ST) if\ test\ \(mif\ $i
+ \*(DC then cp\ $i\ junk$$;\ mv\ junk$$ $i
+ \*(DC elif\ test\ $flag
+ \*(DC then echo\ file\ \\'$i\\'\ does\ not\ exist
+ \*(DC else >$i
+ \*(DC fi
+ \*(DOesac
+ done
+.DE
+.sp
+.ft B
+.ce
+Figure 4. The touch command
+.ft
+.sp
+The \fItrap\fP command
+appears before the creation
+of the temporary file;
+otherwise it would be
+possible for the process
+to die without removing
+the file.
+.LP
+Since there is no signal 0 in UNIX
+it is used by the shell to indicate the
+commands to be executed on exit from the
+shell script.
+.LP
+A script may, itself, elect to
+ignore signals by specifying the null
+string as the argument to trap.
+The following fragment is taken from the
+\fInohup\fP command.
+.DS
+ trap \'\' 1 2 3 15
+.DE
+which causes \fIhangup, interrupt, quit \fRand\fI kill\fR
+to be ignored both by the
+script and by invoked commands.
+.LP
+Traps may be reset by saying
+.DS
+ trap 2 3
+.DE
+which resets the traps for signals 2 and 3 to their default values.
+A list of the current values of traps may be obtained
+by writing
+.DS
+ trap
+.DE
+.LP
+The script \fIscan\fP (Figure 5) is an example
+of the use of \fItrap\fP where there is no exit
+in the trap command.
+\fIscan\fP takes each directory
+in the current directory, prompts
+with its name, and then executes
+commands typed at the terminal
+until an end of file or an interrupt is received.
+Interrupts are ignored while executing
+the requested commands but cause
+termination when \fIscan\fP is
+waiting for input.
+.DS
+ d=\`pwd\`
+ for\ i\ in\ \*(ST
+ do\ if\ test\ \(mid\ $d/$i
+ \*(DOthen\ cd\ $d/$i
+ \*(DO\*(THwhile\ echo\ "$i:"
+ \*(DO\*(TH\*(WHtrap\ exit\ 2
+ \*(DO\*(TH\*(WHread\ x
+ \*(DO\*(THdo\ trap\ :\ 2;\ eval\ $x;\ done
+ \*(DOfi
+ done
+.DE
+.sp
+.ft B
+.ce
+Figure 5. The scan command
+.ft
+.sp
+\fIread x\fR is a built-in command that reads one line from the
+standard input
+and places the result in the variable \fBx\|.\fP
+It returns a non-zero exit status if either
+an end-of-file is read or an interrupt
+is received.
+.SH
+3.8\ Command\ execution
+.LP
+To run a command (other than a built-in) the shell first creates
+a new process using the system call \fIfork.\fP
+The execution environment for the command
+includes input, output and the states of signals, and
+is established in the child process
+before the command is executed.
+The built-in command \fIexec\fP
+is used in the rare cases when no fork
+is required
+and simply replaces the shell with a new command.
+For example, a simple version of the \fInohup\fP
+command looks like
+.DS
+ trap \\'\\' 1 2 3 15
+ exec $\*(ST
+.DE
+The \fItrap\fP turns off the signals specified
+so that they are ignored by subsequently created commands
+and \fIexec\fP replaces the shell by the command
+specified.
+.LP
+Most forms of input output redirection have already been
+described.
+In the following \fIword\fP is only subject
+to parameter and command substitution.
+No file name generation or blank interpretation
+takes place so that, for example,
+.DS
+ echo \*(ZZ >\*(ST.c
+.DE
+will write its output into a file whose name is \fB\*(ST.c\|.\fP
+Input output specifications are evaluated left to right
+as they appear in the command.
+.IP >\ \fIword\fP 12
+The standard output (file descriptor 1)
+is sent to the file \fIword\fP which is
+created if it does not already exist.
+.IP \*(AP\ \fIword\fP 12
+The standard output is sent to file \fIword.\fP
+If the file exists then output is appended
+(by seeking to the end);
+otherwise the file is created.
+.IP <\ \fIword\fP 12
+The standard input (file descriptor 0)
+is taken from the file \fIword.\fP
+.IP \*(HE\ \fIword\fP 12
+The standard input is taken from the lines
+of shell input that follow up to but not
+including a line consisting only of \fIword.\fP
+If \fIword\fP is quoted then no interpretation
+of the document occurs.
+If \fIword\fP is not quoted
+then parameter and command substitution
+occur and \fB\\\fP is used to quote
+the characters \fB\\\fP \fB$\fP \fB\`\fP and the first character
+of \fIword.\fP
+In the latter case \fB\\newline\fP is ignored (c.f. quoted strings).
+.IP >&\ \fIdigit\fP 12
+The file descriptor \fIdigit\fP is duplicated using the system
+call \fIdup\fP (2)
+and the result is used as the standard output.
+.IP <&\ \fIdigit\fP 12
+The standard input is duplicated from file descriptor \fIdigit.\fP
+.IP <&\(mi 12
+The standard input is closed.
+.IP >&\(mi 12
+The standard output is closed.
+.LP
+Any of the above may be preceded by a digit in which
+case the file descriptor created is that specified by the digit
+instead of the default 0 or 1.
+For example,
+.DS
+ \*(ZZ 2>file
+.DE
+runs a command with message output (file descriptor 2)
+directed to \fIfile.\fP
+.DS
+ \*(ZZ 2>&1
+.DE
+runs a command with its standard output and message output
+merged.
+(Strictly speaking file descriptor 2 is created
+by duplicating file descriptor 1 but the effect is usually to
+merge the two streams.)
+.\" Removed by Perry Metzger, most of this is now obsolete
+.\"
+.\" .LP
+.\" The environment for a command run in the background such as
+.\" .DS
+.\" list \*(ST.c \*(VT lpr &
+.\" .DE
+.\" is modified in two ways.
+.\" Firstly, the default standard input
+.\" for such a command is the empty file \fB/dev/null\|.\fR
+.\" This prevents two processes (the shell and the command),
+.\" which are running in parallel, from trying to
+.\" read the same input.
+.\" Chaos would ensue
+.\" if this were not the case.
+.\" For example,
+.\" .DS
+.\" ed file &
+.\" .DE
+.\" would allow both the editor and the shell
+.\" to read from the same input at the same time.
+.\" .LP
+.\" The other modification to the environment of a background
+.\" command is to turn off the QUIT and INTERRUPT signals
+.\" so that they are ignored by the command.
+.\" This allows these signals to be used
+.\" at the terminal without causing background
+.\" commands to terminate.
+.\" For this reason the UNIX convention
+.\" for a signal is that if it is set to 1
+.\" (ignored) then it is never changed
+.\" even for a short time.
+.\" Note that the shell command \fItrap\fP
+.\" has no effect for an ignored signal.
+.SH
+3.9\ Invoking\ the\ shell
+.LP
+The following flags are interpreted by the shell
+when it is invoked.
+If the first character of argument zero is a minus,
+then commands are read from the file \fB.profile\|.\fP
+.IP \fB\(mic\fP\ \fIstring\fP
+.br
+If the \fB\(mic\fP flag is present then
+commands are read from \fIstring\|.\fP
+.IP \fB\(mis\fP
+If the \fB\(mis\fP flag is present or if no
+arguments remain
+then commands are read from the standard input.
+Shell output is written to
+file descriptor 2.
+.IP \fB\(mii\fP
+If the \fB\(mii\fP flag is present or
+if the shell input and output are attached to a terminal (as told by \fIgtty\fP)
+then this shell is \fIinteractive.\fP
+In this case TERMINATE is ignored (so that \fBkill 0\fP
+does not kill an interactive shell) and INTERRUPT is caught and ignored
+(so that \fBwait\fP is interruptable).
+In all cases QUIT is ignored by the shell.
+.SH
+3.10\ Job\ Control
+.LP
+When a command or pipeline (also known as a \fIjob\fP) is running in
+the foreground, entering the stop character (typically
+\s-1CONTROL-Z\s0 but user settable with the \fIstty\fP(1) command)
+will usually cause the job to stop.
+.LP
+The jobs associated with the current shell may be listed by entering
+the \fIjobs\fP command.
+Each job has an associated \fIjob number\fP.
+Jobs that are stopped may be continued by entering
+.DS
+ bg %\fIjobnumber\fP
+.DE
+and jobs may be moved to the foreground by entering
+.DS
+ fg %\fIjobnumber\fP
+.DE
+If there is a sole job with a particular name (say only one instance
+of \fIcc\fP running), \fIfg\fP and \fIbg\fP may also use name of the
+command in place of the number, as in:
+.DS
+ bg %cc
+.DE
+If no `\fB%\fP' clause is entered, most recently stopped job
+(indicated with a `+' by the \fIjobs\fP command) will be assumed.
+See the manual page for the shell for more details.
+.SH
+3.11\ Aliases
+.LP
+The \fIalias\fP command creates a so-called shell alias, which is an
+abbreviation that macro-expands at run time into some other command.
+For example:
+.DS
+ alias ls="ls -F"
+.DE
+would cause the command sequence \fBls -F\fP to be executed whenever
+the user types \fBls\fP into the shell.
+Note that if the user types \fBls -a\fP, the shell will in fact
+execute \fBls -F -a\fP.
+The command \fBalias\fP on its own prints out all current aliases.
+The \fIunalias\fP command, as in:
+.DS
+ unalias ls
+.DE
+will remove an existing alias.
+Aliases can shadow pre-existing commands, as in the example above.
+They are typically used to override the interactive behavior of
+commands in small ways, for example to always invoke some program with
+a favorite option, and are almost never found in scripts.
+.SH
+3.12\ Command\ Line\ Editing\ and\ Recall
+.LP
+When working interactively with the shell, it is often tedious to
+retype previously entered commands, especially if they are complicated.
+The shell therefore maintains a so-called \fIhistory\fP, which is
+stored in the file specified by the \fB\s-1HISTFILE\s0\fP environment
+variable if it is set.
+Users may view, edit, and re-enter previous lines of input using
+a small subset of the commands of the \fIvi\fP(1) or
+\fIemacs\fP(1)\(dg editors.
+.FS
+Technically, vi command editing is standardized by POSIX while emacs
+is not.
+However, all modern shells support both styles.
+.FE
+Emacs style editing may be selected by entering
+.DS
+ set -o emacs
+.DE
+and vi style editing may be selected with
+.DS
+ set -o vi
+.DE
+The details of how command line editing works are beyond the scope of
+this document.
+See the shell manual page for details.
+.SH
+Acknowledgements
+.LP
+The design of the shell is
+based in part on the original UNIX shell
+.[
+unix command language thompson
+.]
+and the PWB/UNIX shell,
+.[
+pwb shell mashey unix
+.]
+some
+features having been taken from both.
+Similarities also exist with the
+command interpreters
+of the Cambridge Multiple Access System
+.[
+cambridge multiple access system hartley
+.]
+and of CTSS.
+.[
+ctss
+.]
+.LP
+I would like to thank Dennis Ritchie
+and John Mashey for many
+discussions during the design of the shell.
+I am also grateful to the members of the Computing Science Research Center
+and to Joe Maranzano for their
+comments on drafts of this document.
+.SH
+.[
+$LIST$
+.]
diff --git a/bin/sh/USD.doc/t4 b/bin/sh/USD.doc/t4
new file mode 100644
index 0000000..7719d6c
--- /dev/null
+++ b/bin/sh/USD.doc/t4
@@ -0,0 +1,180 @@
+.\" $NetBSD: t4,v 1.3 2010/08/22 02:19:07 perry Exp $
+.\"
+.\" Copyright (C) Caldera International Inc. 2001-2002. All rights reserved.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions are
+.\" met:
+.\"
+.\" Redistributions of source code and documentation must retain the above
+.\" copyright notice, this list of conditions and the following
+.\" disclaimer.
+.\"
+.\" Redistributions in binary form must reproduce the above copyright
+.\" notice, this list of conditions and the following disclaimer in the
+.\" documentation and/or other materials provided with the distribution.
+.\"
+.\" All advertising materials mentioning features or use of this software
+.\" must display the following acknowledgement:
+.\"
+.\" This product includes software developed or owned by Caldera
+.\" International, Inc. Neither the name of Caldera International, Inc.
+.\" nor the names of other contributors may be used to endorse or promote
+.\" products derived from this software without specific prior written
+.\" permission.
+.\"
+.\" USE OF THE SOFTWARE PROVIDED FOR UNDER THIS LICENSE BY CALDERA
+.\" INTERNATIONAL, INC. AND CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR
+.\" IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+.\" WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+.\" DISCLAIMED. IN NO EVENT SHALL CALDERA INTERNATIONAL, INC. BE LIABLE
+.\" FOR ANY DIRECT, INDIRECT INCIDENTAL, SPECIAL, EXEMPLARY, OR
+.\" CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+.\" SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+.\" BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+.\" WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+.\" OR OTHERWISE) RISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
+.\" IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+.\"
+.\" @(#)t4 8.1 (Berkeley) 8/14/93
+.\"
+.bp
+.SH
+Appendix\ A\ -\ Grammar
+.LP
+Note: This grammar needs updating, it is obsolete.
+.LP
+.LD
+\fIitem: word
+ input-output
+ name = value
+.sp 0.7
+simple-command: item
+ simple-command item
+.sp 0.7
+command: simple-command
+ \fB( \fIcommand-list \fB)
+ \fB{ \fIcommand-list \fB}
+ \fBfor \fIname \fBdo \fIcommand-list \fBdone
+ \fBfor \fIname \fBin \fIword \*(ZZ \fBdo \fIcommand-list \fBdone
+ \fBwhile \fIcommand-list \fBdo \fIcommand-list \fBdone
+ \fBuntil \fIcommand-list \fBdo \fIcommand-list \fBdone
+ \fBcase \fIword \fBin \fIcase-part \*(ZZ \fBesac
+ \fBif \fIcommand-list \fBthen \fIcommand-list \fIelse-part \fBfi
+.sp 0.7
+\fIpipeline: command
+ pipeline \fB\*(VT\fI command
+.sp 0.7
+andor: pipeline
+ andor \fB&&\fI pipeline
+ andor \fB\*(VT\*(VT\fI pipeline
+.sp 0.7
+command-list: andor
+ command-list \fB;\fI
+ command-list \fB&\fI
+ command-list \fB;\fI andor
+ command-list \fB&\fI andor
+.sp 0.7
+input-output: \fB> \fIfile
+ \fB< \fIfile
+ \fB\*(AP \fIword
+ \fB\*(HE \fIword
+.sp 0.7
+file: word
+ \fB&\fI digit
+ \fB&\fI \(mi
+.sp 0.7
+case-part: pattern\fB ) \fIcommand-list\fB ;;
+.sp 0.7
+\fIpattern: word
+ pattern \fB\*(VT\fI word
+.sp 0.7
+\fIelse-part: \fBelif \fIcommand-list\fB then\fI command-list else-part\fP
+ \fBelse \fIcommand-list\fI
+ empty
+.sp 0.7
+empty:
+.sp 0.7
+word: \fRa sequence of non-blank characters\fI
+.sp 0.7
+name: \fRa sequence of letters, digits or underscores starting with a letter\fI
+.sp 0.7
+digit: \fB0 1 2 3 4 5 6 7 8 9\fP
+.DE
+.LP
+.bp
+.SH
+Appendix\ B\ -\ Meta-characters\ and\ Reserved\ Words
+.LP
+a) syntactic
+.RS
+.IP \fB\*(VT\fR 6
+pipe symbol
+.IP \fB&&\fR 6
+`andf' symbol
+.IP \fB\*(VT\*(VT\fR 6
+`orf' symbol
+.IP \fB;\fP 8
+command separator
+.IP \fB;;\fP 8
+case delimiter
+.IP \fB&\fP 8
+background commands
+.IP \fB(\ )\fP 8
+command grouping
+.IP \fB<\fP 8
+input redirection
+.IP \fB\*(HE\fP 8
+input from a here document
+.IP \fB>\fP 8
+output creation
+.IP \fB\*(AP\fP 8
+output append
+.sp 2
+.RE
+.LP
+b) patterns
+.RS
+.IP \fB\*(ST\fP 8
+match any character(s) including none
+.IP \fB?\fP 8
+match any single character
+.IP \fB[...]\fP 8
+match any of the enclosed characters
+.sp 2
+.RE
+.LP
+c) substitution
+.RS
+.IP \fB${...}\fP 8
+substitute shell variable
+.IP \fB$(...)\fP 8
+substitute command output
+.IP \fB\`...\`\fP 8
+substitute command output
+.IP \fB$((...))\fP 8
+substitute arithmetic expression
+.sp 2
+.RE
+.LP
+d) quoting
+.RS
+.IP \fB\e\fP 8
+quote the next character
+.IP \fB\'...\'\fP 8
+quote the enclosed characters except for \'
+.IP \fB"\&..."\fP 8
+quote the enclosed characters except
+for \fB$ \` \e "\fP
+.sp 2
+.RE
+.LP
+e) reserved words
+.DS
+.ft B
+if then else elif fi
+case in esac
+for while until do done
+! { }
+.ft
+.DE
diff --git a/bin/sh/alias.c b/bin/sh/alias.c
new file mode 100644
index 0000000..2848b52
--- /dev/null
+++ b/bin/sh/alias.c
@@ -0,0 +1,314 @@
+/* $NetBSD: alias.c,v 1.20 2018/12/03 06:40:26 kre Exp $ */
+
+/*-
+ * Copyright (c) 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Kenneth Almquist.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+#ifndef lint
+#if 0
+static char sccsid[] = "@(#)alias.c 8.3 (Berkeley) 5/4/95";
+#else
+__RCSID("$NetBSD: alias.c,v 1.20 2018/12/03 06:40:26 kre Exp $");
+#endif
+#endif /* not lint */
+
+#include <stdlib.h>
+#include "shell.h"
+#include "input.h"
+#include "output.h"
+#include "error.h"
+#include "memalloc.h"
+#include "mystring.h"
+#include "alias.h"
+#include "options.h" /* XXX for argptr (should remove?) */
+#include "builtins.h"
+#include "var.h"
+
+#define ATABSIZE 39
+
+struct alias *atab[ATABSIZE];
+
+STATIC void setalias(char *, char *);
+STATIC int by_name(const void *, const void *);
+STATIC void list_aliases(void);
+STATIC int unalias(char *);
+STATIC struct alias **freealias(struct alias **, int);
+STATIC struct alias **hashalias(const char *);
+STATIC size_t countaliases(void);
+
+STATIC
+void
+setalias(char *name, char *val)
+{
+ struct alias *ap, **app;
+
+ (void) unalias(name); /* old one (if any) is now gone */
+ app = hashalias(name);
+
+ INTOFF;
+ ap = ckmalloc(sizeof (struct alias));
+ ap->name = savestr(name);
+ ap->flag = 0;
+ ap->val = savestr(val);
+ ap->next = *app;
+ *app = ap;
+ INTON;
+}
+
+STATIC struct alias **
+freealias(struct alias **app, int force)
+{
+ struct alias *ap = *app;
+
+ if (ap == NULL)
+ return app;
+
+ /*
+ * if the alias is currently in use (i.e. its
+ * buffer is being used by the input routine) we
+ * just null out the name instead of discarding it.
+ * If we encounter it later, when it is idle,
+ * we will finish freeing it then.
+ *
+ * Unless we want to simply free everything (INIT)
+ */
+ if (ap->flag & ALIASINUSE && !force) {
+ *ap->name = '\0';
+ return &ap->next;
+ }
+
+ INTOFF;
+ *app = ap->next;
+ ckfree(ap->name);
+ ckfree(ap->val);
+ ckfree(ap);
+ INTON;
+
+ return app;
+}
+
+STATIC int
+unalias(char *name)
+{
+ struct alias *ap, **app;
+
+ app = hashalias(name);
+ while ((ap = *app) != NULL) {
+ if (equal(name, ap->name)) {
+ (void) freealias(app, 0);
+ return 0;
+ }
+ app = &ap->next;
+ }
+
+ return 1;
+}
+
+#ifdef mkinit
+MKINIT void rmaliases(int);
+
+SHELLPROC {
+ rmaliases(1);
+}
+#endif
+
+void
+rmaliases(int force)
+{
+ struct alias **app;
+ int i;
+
+ INTOFF;
+ for (i = 0; i < ATABSIZE; i++) {
+ app = &atab[i];
+ while (*app)
+ app = freealias(app, force);
+ }
+ INTON;
+}
+
+struct alias *
+lookupalias(const char *name, int check)
+{
+ struct alias *ap = *hashalias(name);
+
+ while (ap != NULL) {
+ if (equal(name, ap->name)) {
+ if (check && (ap->flag & ALIASINUSE))
+ return NULL;
+ return ap;
+ }
+ ap = ap->next;
+ }
+
+ return NULL;
+}
+
+const char *
+alias_text(void *dummy __unused, const char *name)
+{
+ struct alias *ap;
+
+ ap = lookupalias(name, 0);
+ if (ap == NULL)
+ return NULL;
+ return ap->val;
+}
+
+STATIC int
+by_name(const void *a, const void *b)
+{
+
+ return strcmp(
+ (*(const struct alias * const *)a)->name,
+ (*(const struct alias * const *)b)->name);
+}
+
+STATIC void
+list_aliases(void)
+{
+ size_t i, j, n;
+ const struct alias **aliases;
+ const struct alias *ap;
+
+ INTOFF;
+ n = countaliases();
+ aliases = ckmalloc(n * sizeof aliases[0]);
+
+ j = 0;
+ for (i = 0; i < ATABSIZE; i++)
+ for (ap = atab[i]; ap != NULL; ap = ap->next)
+ if (ap->name[0] != '\0')
+ aliases[j++] = ap;
+ if (j != n)
+ error("Alias count botch");
+ INTON;
+
+ qsort(aliases, n, sizeof aliases[0], by_name);
+
+ for (i = 0; i < n; i++) {
+ out1fmt("alias %s=", aliases[i]->name);
+ print_quoted(aliases[i]->val);
+ out1c('\n');
+ }
+
+ ckfree(aliases);
+}
+
+/*
+ * Count how many aliases are defined (skipping any
+ * that have been deleted, but don't know it yet).
+ * Use this opportunity to clean up any of those
+ * zombies that are no longer needed.
+ */
+STATIC size_t
+countaliases(void)
+{
+ struct alias *ap, **app;
+ size_t n;
+ int i;
+
+ n = 0;
+ for (i = 0; i < ATABSIZE; i++)
+ for (app = &atab[i]; (ap = *app) != NULL;) {
+ if (ap->name[0] != '\0')
+ n++;
+ else {
+ app = freealias(app, 0);
+ continue;
+ }
+ app = &ap->next;
+ }
+
+ return n;
+}
+
+int
+aliascmd(int argc, char **argv)
+{
+ char *n, *v;
+ int ret = 0;
+ struct alias *ap;
+
+ if (argc == 1) {
+ list_aliases();
+ return 0;
+ }
+
+ while ((n = *++argv) != NULL) {
+ if ((v = strchr(n+1, '=')) == NULL) { /* n+1: funny ksh stuff */
+ if ((ap = lookupalias(n, 0)) == NULL) {
+ outfmt(out2, "alias: %s not found\n", n);
+ ret = 1;
+ } else {
+ out1fmt("alias %s=", n);
+ print_quoted(ap->val);
+ out1c('\n');
+ }
+ } else {
+ *v++ = '\0';
+ setalias(n, v);
+ }
+ }
+
+ return ret;
+}
+
+int
+unaliascmd(int argc, char **argv)
+{
+ int i;
+
+ while ((i = nextopt("a")) != '\0') {
+ if (i == 'a') {
+ rmaliases(0);
+ return 0;
+ }
+ }
+
+ (void)countaliases(); /* delete any dead ones */
+ for (i = 0; *argptr; argptr++)
+ i |= unalias(*argptr);
+
+ return i;
+}
+
+STATIC struct alias **
+hashalias(const char *p)
+{
+ unsigned int hashval;
+
+ hashval = *(const unsigned char *)p << 4;
+ while (*p)
+ hashval += *p++;
+ return &atab[hashval % ATABSIZE];
+}
diff --git a/bin/sh/alias.h b/bin/sh/alias.h
new file mode 100644
index 0000000..390df6d
--- /dev/null
+++ b/bin/sh/alias.h
@@ -0,0 +1,48 @@
+/* $NetBSD: alias.h,v 1.9 2018/12/03 06:40:26 kre Exp $ */
+
+/*-
+ * Copyright (c) 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Kenneth Almquist.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * @(#)alias.h 8.2 (Berkeley) 5/4/95
+ */
+
+#define ALIASINUSE 1
+
+struct alias {
+ struct alias *next;
+ char *name;
+ char *val;
+ int flag;
+};
+
+struct alias *lookupalias(const char *, int);
+const char *alias_text(void *, const char *);
+void rmaliases(int);
diff --git a/bin/sh/arith_token.c b/bin/sh/arith_token.c
new file mode 100644
index 0000000..cd91857
--- /dev/null
+++ b/bin/sh/arith_token.c
@@ -0,0 +1,262 @@
+/* $NetBSD: arith_token.c,v 1.7 2017/12/17 04:06:03 kre Exp $ */
+
+/*-
+ * Copyright (c) 2002
+ * Herbert Xu.
+ * Copyright (c) 1991, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Kenneth Almquist.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * From FreeBSD, from dash
+ */
+
+#include <sys/cdefs.h>
+
+#ifndef lint
+__RCSID("$NetBSD: arith_token.c,v 1.7 2017/12/17 04:06:03 kre Exp $");
+#endif /* not lint */
+
+#include <inttypes.h>
+#include <limits.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "shell.h"
+#include "arith_tokens.h"
+#include "expand.h"
+#include "error.h"
+#include "memalloc.h"
+#include "parser.h"
+#include "syntax.h"
+#include "show.h"
+
+#if ARITH_BOR + ARITH_ASS_GAP != ARITH_BORASS || \
+ ARITH_ASS + ARITH_ASS_GAP != ARITH_EQ
+#error Arithmetic tokens are out of order.
+#endif
+
+/*
+ * Scan next arithmetic token, return its type,
+ * leave its value (when applicable) in (global) a_t_val.
+ *
+ * Input text is in (global) arith_buf which is updated to
+ * refer to the next char after the token returned, except
+ * on error (ARITH_BAD returned) where arith_buf is not altered.
+ */
+int
+arith_token(void)
+{
+ int token;
+ const char *buf = arith_buf;
+ char *end;
+ const char *p;
+
+ for (;;) {
+ token = *buf;
+
+ if (isdigit(token)) {
+ /*
+ * Numbers all start with a digit, and nothing
+ * else does, the number ends wherever
+ * strtoimax() stops...
+ */
+ a_t_val.val = strtoimax(buf, &end, 0);
+ if (is_in_name(*end)) {
+ token = *end;
+ while (is_in_name(*++end))
+ continue;
+ error("arithmetic: unexpected '%c' "
+ "(out of range) in numeric constant: "
+ "%.*s", token, (int)(end - buf), buf);
+ }
+ arith_buf = end;
+ VTRACE(DBG_ARITH, ("Arith token ARITH_NUM=%jd\n",
+ a_t_val.val));
+ return ARITH_NUM;
+
+ } else if (is_name(token)) {
+ /*
+ * Variable names all start with an alpha (or '_')
+ * and nothing else does. They continue for the
+ * longest unbroken sequence of alphanumerics ( + _ )
+ */
+ arith_var_lno = arith_lno;
+ p = buf;
+ while (buf++, is_in_name(*buf))
+ ;
+ a_t_val.name = stalloc(buf - p + 1);
+ memcpy(a_t_val.name, p, buf - p);
+ a_t_val.name[buf - p] = '\0';
+ arith_buf = buf;
+ VTRACE(DBG_ARITH, ("Arith token ARITH_VAR=\"%s\"\n",
+ a_t_val.name));
+ return ARITH_VAR;
+
+ } else switch (token) {
+ /*
+ * everything else must be some kind of
+ * operator, white space, or an error.
+ */
+ case '\n':
+ arith_lno++;
+ VTRACE(DBG_ARITH, ("Arith: newline\n"));
+ /* FALLTHROUGH */
+ case ' ':
+ case '\t':
+ buf++;
+ continue;
+
+ default:
+ error("arithmetic: unexpected '%c' (%#x) in expression",
+ token, token);
+ /* NOTREACHED */
+
+ case '=':
+ token = ARITH_ASS;
+ checkeq:
+ buf++;
+ checkeqcur:
+ if (*buf != '=')
+ goto out;
+ token += ARITH_ASS_GAP;
+ break;
+
+ case '>':
+ switch (*++buf) {
+ case '=':
+ token = ARITH_GE;
+ break;
+ case '>':
+ token = ARITH_RSHIFT;
+ goto checkeq;
+ default:
+ token = ARITH_GT;
+ goto out;
+ }
+ break;
+
+ case '<':
+ switch (*++buf) {
+ case '=':
+ token = ARITH_LE;
+ break;
+ case '<':
+ token = ARITH_LSHIFT;
+ goto checkeq;
+ default:
+ token = ARITH_LT;
+ goto out;
+ }
+ break;
+
+ case '|':
+ if (*++buf != '|') {
+ token = ARITH_BOR;
+ goto checkeqcur;
+ }
+ token = ARITH_OR;
+ break;
+
+ case '&':
+ if (*++buf != '&') {
+ token = ARITH_BAND;
+ goto checkeqcur;
+ }
+ token = ARITH_AND;
+ break;
+
+ case '!':
+ if (*++buf != '=') {
+ token = ARITH_NOT;
+ goto out;
+ }
+ token = ARITH_NE;
+ break;
+
+ case 0:
+ goto out;
+
+ case '(':
+ token = ARITH_LPAREN;
+ break;
+ case ')':
+ token = ARITH_RPAREN;
+ break;
+
+ case '*':
+ token = ARITH_MUL;
+ goto checkeq;
+ case '/':
+ token = ARITH_DIV;
+ goto checkeq;
+ case '%':
+ token = ARITH_REM;
+ goto checkeq;
+
+ case '+':
+ if (buf[1] == '+') {
+ buf++;
+ token = ARITH_INCR;
+ break;
+ }
+ token = ARITH_ADD;
+ goto checkeq;
+ case '-':
+ if (buf[1] == '-') {
+ buf++;
+ token = ARITH_DECR;
+ break;
+ }
+ token = ARITH_SUB;
+ goto checkeq;
+ case '~':
+ token = ARITH_BNOT;
+ break;
+ case '^':
+ token = ARITH_BXOR;
+ goto checkeq;
+
+ case '?':
+ token = ARITH_QMARK;
+ break;
+ case ':':
+ token = ARITH_COLON;
+ break;
+ case ',':
+ token = ARITH_COMMA;
+ break;
+ }
+ break;
+ }
+ buf++;
+ out:
+ arith_buf = buf;
+ VTRACE(DBG_ARITH, ("Arith token: %d\n", token));
+ return token;
+}
diff --git a/bin/sh/arith_tokens.h b/bin/sh/arith_tokens.h
new file mode 100644
index 0000000..f655aa9
--- /dev/null
+++ b/bin/sh/arith_tokens.h
@@ -0,0 +1,120 @@
+/* $NetBSD: arith_tokens.h,v 1.3 2017/07/24 13:21:14 kre Exp $ */
+
+/*-
+ * Copyright (c) 1993
+ * The Regents of the University of California. All rights reserved.
+ * Copyright (c) 2007
+ * Herbert Xu <herbert@gondor.apana.org.au>. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Kenneth Almquist.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * From FreeBSD who obtained it from dash (modified both times.)
+ */
+
+/*
+ * Tokens returned from arith_token()
+ *
+ * Caution, values are not arbitrary.
+ */
+
+#define ARITH_BAD 0
+
+#define ARITH_ASS 1
+
+#define ARITH_OR 2
+#define ARITH_AND 3
+#define ARITH_NUM 5
+#define ARITH_VAR 6
+#define ARITH_NOT 7
+
+#define ARITH_BINOP_MIN 8
+
+#define ARITH_LE 8
+#define ARITH_GE 9
+#define ARITH_LT 10
+#define ARITH_GT 11
+#define ARITH_EQ 12 /* Must be ARITH_ASS + ARITH_ASS_GAP */
+
+#define ARITH_ASS_BASE 13
+
+#define ARITH_REM 13
+#define ARITH_BAND 14
+#define ARITH_LSHIFT 15
+#define ARITH_RSHIFT 16
+#define ARITH_MUL 17
+#define ARITH_ADD 18
+#define ARITH_BOR 19
+#define ARITH_SUB 20
+#define ARITH_BXOR 21
+#define ARITH_DIV 22
+
+#define ARITH_NE 23
+
+#define ARITH_BINOP_MAX 24
+
+#define ARITH_ASS_MIN ARITH_BINOP_MAX
+#define ARITH_ASS_GAP (ARITH_ASS_MIN - ARITH_ASS_BASE)
+
+#define ARITH_REMASS (ARITH_ASS_GAP + ARITH_REM)
+#define ARITH_BANDASS (ARITH_ASS_GAP + ARITH_BAND)
+#define ARITH_LSHIFTASS (ARITH_ASS_GAP + ARITH_LSHIFT)
+#define ARITH_RSHIFTASS (ARITH_ASS_GAP + ARITH_RSHIFT)
+#define ARITH_MULASS (ARITH_ASS_GAP + ARITH_MUL)
+#define ARITH_ADDASS (ARITH_ASS_GAP + ARITH_ADD)
+#define ARITH_BORASS (ARITH_ASS_GAP + ARITH_BOR)
+#define ARITH_SUBASS (ARITH_ASS_GAP + ARITH_SUB)
+#define ARITH_BXORASS (ARITH_ASS_GAP + ARITH_BXOR)
+#define ARITH_DIVASS (ARITH_ASS_GAP + ARITH_BXOR)
+
+#define ARITH_ASS_MAX 34
+
+#define ARITH_LPAREN 34
+#define ARITH_RPAREN 35
+#define ARITH_BNOT 36
+#define ARITH_QMARK 37
+#define ARITH_COLON 38
+#define ARITH_INCR 39
+#define ARITH_DECR 40
+#define ARITH_COMMA 41
+
+/*
+ * Globals shared between arith parser, and lexer
+ */
+
+extern const char *arith_buf;
+
+union a_token_val {
+ intmax_t val;
+ char *name;
+};
+
+extern union a_token_val a_t_val;
+
+extern int arith_lno, arith_var_lno;
+
+int arith_token(void);
diff --git a/bin/sh/arithmetic.c b/bin/sh/arithmetic.c
new file mode 100644
index 0000000..f9c91a4
--- /dev/null
+++ b/bin/sh/arithmetic.c
@@ -0,0 +1,502 @@
+/* $NetBSD: arithmetic.c,v 1.5 2018/04/21 23:01:29 kre Exp $ */
+
+/*-
+ * Copyright (c) 1993
+ * The Regents of the University of California. All rights reserved.
+ * Copyright (c) 2007
+ * Herbert Xu <herbert@gondor.apana.org.au>. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Kenneth Almquist.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * From FreeBSD, who obtained it from dash, modified both times...
+ */
+
+#include <sys/cdefs.h>
+
+#ifndef lint
+__RCSID("$NetBSD: arithmetic.c,v 1.5 2018/04/21 23:01:29 kre Exp $");
+#endif /* not lint */
+
+#include <limits.h>
+#include <errno.h>
+#include <inttypes.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+
+#include "shell.h"
+#include "arithmetic.h"
+#include "arith_tokens.h"
+#include "expand.h"
+#include "error.h"
+#include "memalloc.h"
+#include "output.h"
+#include "options.h"
+#include "var.h"
+#include "show.h"
+#include "syntax.h"
+
+#if ARITH_BOR + ARITH_ASS_GAP != ARITH_BORASS || \
+ ARITH_ASS + ARITH_ASS_GAP != ARITH_EQ
+#error Arithmetic tokens are out of order.
+#endif
+
+static const char *arith_startbuf;
+
+const char *arith_buf;
+union a_token_val a_t_val;
+
+static int last_token;
+
+int arith_lno, arith_var_lno;
+
+#define ARITH_PRECEDENCE(op, prec) [op - ARITH_BINOP_MIN] = prec
+
+static const char prec[ARITH_BINOP_MAX - ARITH_BINOP_MIN] = {
+ ARITH_PRECEDENCE(ARITH_MUL, 0),
+ ARITH_PRECEDENCE(ARITH_DIV, 0),
+ ARITH_PRECEDENCE(ARITH_REM, 0),
+ ARITH_PRECEDENCE(ARITH_ADD, 1),
+ ARITH_PRECEDENCE(ARITH_SUB, 1),
+ ARITH_PRECEDENCE(ARITH_LSHIFT, 2),
+ ARITH_PRECEDENCE(ARITH_RSHIFT, 2),
+ ARITH_PRECEDENCE(ARITH_LT, 3),
+ ARITH_PRECEDENCE(ARITH_LE, 3),
+ ARITH_PRECEDENCE(ARITH_GT, 3),
+ ARITH_PRECEDENCE(ARITH_GE, 3),
+ ARITH_PRECEDENCE(ARITH_EQ, 4),
+ ARITH_PRECEDENCE(ARITH_NE, 4),
+ ARITH_PRECEDENCE(ARITH_BAND, 5),
+ ARITH_PRECEDENCE(ARITH_BXOR, 6),
+ ARITH_PRECEDENCE(ARITH_BOR, 7),
+};
+
+#define ARITH_MAX_PREC 8
+
+int expcmd(int, char **);
+
+static void __dead
+arith_err(const char *s)
+{
+ error("arithmetic expression: %s: \"%s\"", s, arith_startbuf);
+ /* NOTREACHED */
+}
+
+static intmax_t
+arith_lookupvarint(char *varname)
+{
+ const char *str;
+ char *p;
+ intmax_t result;
+ const int oln = line_number;
+
+ VTRACE(DBG_ARITH, ("Arith var lookup(\"%s\") with lno=%d\n", varname,
+ arith_var_lno));
+
+ line_number = arith_var_lno;
+ str = lookupvar(varname);
+ line_number = oln;
+
+ if (uflag && str == NULL)
+ arith_err("variable not set");
+ if (str == NULL || *str == '\0')
+ str = "0";
+ errno = 0;
+ result = strtoimax(str, &p, 0);
+ if (errno != 0 || *p != '\0') {
+ if (errno == 0) {
+ while (*p != '\0' && is_space(*p))
+ p++;
+ if (*p == '\0')
+ return result;
+ }
+ arith_err("variable contains non-numeric value");
+ }
+ return result;
+}
+
+static inline int
+arith_prec(int op)
+{
+
+ return prec[op - ARITH_BINOP_MIN];
+}
+
+static inline int
+higher_prec(int op1, int op2)
+{
+
+ return arith_prec(op1) < arith_prec(op2);
+}
+
+static intmax_t
+do_binop(int op, intmax_t a, intmax_t b)
+{
+
+ VTRACE(DBG_ARITH, ("Arith do binop %d (%jd, %jd)\n", op, a, b));
+ switch (op) {
+ default:
+ arith_err("token error");
+ case ARITH_REM:
+ case ARITH_DIV:
+ if (b == 0)
+ arith_err("division by zero");
+ if (a == INTMAX_MIN && b == -1)
+ arith_err("divide error");
+ return op == ARITH_REM ? a % b : a / b;
+ case ARITH_MUL:
+ return (uintmax_t)a * (uintmax_t)b;
+ case ARITH_ADD:
+ return (uintmax_t)a + (uintmax_t)b;
+ case ARITH_SUB:
+ return (uintmax_t)a - (uintmax_t)b;
+ case ARITH_LSHIFT:
+ return (uintmax_t)a << (b & (sizeof(uintmax_t) * CHAR_BIT - 1));
+ case ARITH_RSHIFT:
+ return a >> (b & (sizeof(uintmax_t) * CHAR_BIT - 1));
+ case ARITH_LT:
+ return a < b;
+ case ARITH_LE:
+ return a <= b;
+ case ARITH_GT:
+ return a > b;
+ case ARITH_GE:
+ return a >= b;
+ case ARITH_EQ:
+ return a == b;
+ case ARITH_NE:
+ return a != b;
+ case ARITH_BAND:
+ return a & b;
+ case ARITH_BXOR:
+ return a ^ b;
+ case ARITH_BOR:
+ return a | b;
+ }
+}
+
+static intmax_t assignment(int, int);
+static intmax_t comma_list(int, int);
+
+static intmax_t
+primary(int token, union a_token_val *val, int op, int noeval)
+{
+ intmax_t result;
+ char sresult[DIGITS(result) + 1];
+
+ VTRACE(DBG_ARITH, ("Arith primary: token %d op %d%s\n",
+ token, op, noeval ? " noeval" : ""));
+
+ switch (token) {
+ case ARITH_LPAREN:
+ result = comma_list(op, noeval);
+ if (last_token != ARITH_RPAREN)
+ arith_err("expecting ')'");
+ last_token = arith_token();
+ return result;
+ case ARITH_NUM:
+ last_token = op;
+ return val->val;
+ case ARITH_VAR:
+ result = noeval ? val->val : arith_lookupvarint(val->name);
+ if (op == ARITH_INCR || op == ARITH_DECR) {
+ last_token = arith_token();
+ if (noeval)
+ return val->val;
+
+ snprintf(sresult, sizeof(sresult), ARITH_FORMAT_STR,
+ result + (op == ARITH_INCR ? 1 : -1));
+ setvar(val->name, sresult, 0);
+ } else
+ last_token = op;
+ return result;
+ case ARITH_ADD:
+ *val = a_t_val;
+ return primary(op, val, arith_token(), noeval);
+ case ARITH_SUB:
+ *val = a_t_val;
+ return -primary(op, val, arith_token(), noeval);
+ case ARITH_NOT:
+ *val = a_t_val;
+ return !primary(op, val, arith_token(), noeval);
+ case ARITH_BNOT:
+ *val = a_t_val;
+ return ~primary(op, val, arith_token(), noeval);
+ case ARITH_INCR:
+ case ARITH_DECR:
+ if (op != ARITH_VAR)
+ arith_err("incr/decr require var name");
+ last_token = arith_token();
+ if (noeval)
+ return val->val;
+ result = arith_lookupvarint(a_t_val.name);
+ snprintf(sresult, sizeof(sresult), ARITH_FORMAT_STR,
+ result += (token == ARITH_INCR ? 1 : -1));
+ setvar(a_t_val.name, sresult, 0);
+ return result;
+ default:
+ arith_err("expecting primary");
+ }
+ return 0; /* never reached */
+}
+
+static intmax_t
+binop2(intmax_t a, int op, int precedence, int noeval)
+{
+ union a_token_val val;
+ intmax_t b;
+ int op2;
+ int token;
+
+ VTRACE(DBG_ARITH, ("Arith: binop2 %jd op %d (P:%d)%s\n",
+ a, op, precedence, noeval ? " noeval" : ""));
+
+ for (;;) {
+ token = arith_token();
+ val = a_t_val;
+
+ b = primary(token, &val, arith_token(), noeval);
+
+ op2 = last_token;
+ if (op2 >= ARITH_BINOP_MIN && op2 < ARITH_BINOP_MAX &&
+ higher_prec(op2, op)) {
+ b = binop2(b, op2, arith_prec(op), noeval);
+ op2 = last_token;
+ }
+
+ a = noeval ? b : do_binop(op, a, b);
+
+ if (op2 < ARITH_BINOP_MIN || op2 >= ARITH_BINOP_MAX ||
+ arith_prec(op2) >= precedence)
+ return a;
+
+ op = op2;
+ }
+}
+
+static intmax_t
+binop(int token, union a_token_val *val, int op, int noeval)
+{
+ intmax_t a = primary(token, val, op, noeval);
+
+ op = last_token;
+ if (op < ARITH_BINOP_MIN || op >= ARITH_BINOP_MAX)
+ return a;
+
+ return binop2(a, op, ARITH_MAX_PREC, noeval);
+}
+
+static intmax_t
+and(int token, union a_token_val *val, int op, int noeval)
+{
+ intmax_t a = binop(token, val, op, noeval);
+ intmax_t b;
+
+ op = last_token;
+ if (op != ARITH_AND)
+ return a;
+
+ VTRACE(DBG_ARITH, ("Arith: AND %jd%s\n", a, noeval ? " noeval" : ""));
+
+ token = arith_token();
+ *val = a_t_val;
+
+ b = and(token, val, arith_token(), noeval | !a);
+
+ return a && b;
+}
+
+static intmax_t
+or(int token, union a_token_val *val, int op, int noeval)
+{
+ intmax_t a = and(token, val, op, noeval);
+ intmax_t b;
+
+ op = last_token;
+ if (op != ARITH_OR)
+ return a;
+
+ VTRACE(DBG_ARITH, ("Arith: OR %jd%s\n", a, noeval ? " noeval" : ""));
+
+ token = arith_token();
+ *val = a_t_val;
+
+ b = or(token, val, arith_token(), noeval | !!a);
+
+ return a || b;
+}
+
+static intmax_t
+cond(int token, union a_token_val *val, int op, int noeval)
+{
+ intmax_t a = or(token, val, op, noeval);
+ intmax_t b;
+ intmax_t c;
+
+ if (last_token != ARITH_QMARK)
+ return a;
+
+ VTRACE(DBG_ARITH, ("Arith: ?: %jd%s\n", a, noeval ? " noeval" : ""));
+
+ b = assignment(arith_token(), noeval | !a);
+
+ if (last_token != ARITH_COLON)
+ arith_err("expecting ':'");
+
+ token = arith_token();
+ *val = a_t_val;
+
+ c = cond(token, val, arith_token(), noeval | !!a);
+
+ return a ? b : c;
+}
+
+static intmax_t
+assignment(int var, int noeval)
+{
+ union a_token_val val = a_t_val;
+ int op = arith_token();
+ intmax_t result;
+ char sresult[DIGITS(result) + 1];
+
+
+ if (var != ARITH_VAR)
+ return cond(var, &val, op, noeval);
+
+ if (op != ARITH_ASS && (op < ARITH_ASS_MIN || op >= ARITH_ASS_MAX))
+ return cond(var, &val, op, noeval);
+
+ VTRACE(DBG_ARITH, ("Arith: %s ASSIGN %d%s\n", val.name, op,
+ noeval ? " noeval" : ""));
+
+ result = assignment(arith_token(), noeval);
+ if (noeval)
+ return result;
+
+ if (op != ARITH_ASS)
+ result = do_binop(op - ARITH_ASS_GAP,
+ arith_lookupvarint(val.name), result);
+ snprintf(sresult, sizeof(sresult), ARITH_FORMAT_STR, result);
+ setvar(val.name, sresult, 0);
+ return result;
+}
+
+static intmax_t
+comma_list(int token, int noeval)
+{
+ intmax_t result = assignment(token, noeval);
+
+ while (last_token == ARITH_COMMA) {
+ VTRACE(DBG_ARITH, ("Arith: comma discarding %jd%s\n", result,
+ noeval ? " noeval" : ""));
+ result = assignment(arith_token(), noeval);
+ }
+
+ return result;
+}
+
+intmax_t
+arith(const char *s, int lno)
+{
+ struct stackmark smark;
+ intmax_t result;
+ const char *p;
+ int nls = 0;
+
+ setstackmark(&smark);
+
+ arith_lno = lno;
+
+ CTRACE(DBG_ARITH, ("Arith(\"%s\", %d) @%d\n", s, lno, arith_lno));
+
+ /* check if it is possible we might reference LINENO */
+ p = s;
+ while ((p = strchr(p, 'L')) != NULL) {
+ if (p[1] == 'I' && p[2] == 'N') {
+ /* if it is possible, we need to correct airth_lno */
+ p = s;
+ while ((p = strchr(p, '\n')) != NULL)
+ nls++, p++;
+ VTRACE(DBG_ARITH, ("Arith found %d newlines\n", nls));
+ arith_lno -= nls;
+ break;
+ }
+ p++;
+ }
+
+ arith_buf = arith_startbuf = s;
+
+ result = comma_list(arith_token(), 0);
+
+ if (last_token)
+ arith_err("expecting end of expression");
+
+ popstackmark(&smark);
+
+ CTRACE(DBG_ARITH, ("Arith result=%jd\n", result));
+
+ return result;
+}
+
+/*
+ * The let(1)/exp(1) builtin.
+ */
+int
+expcmd(int argc, char **argv)
+{
+ const char *p;
+ char *concat;
+ char **ap;
+ intmax_t i;
+
+ if (argc > 1) {
+ p = argv[1];
+ if (argc > 2) {
+ /*
+ * Concatenate arguments.
+ */
+ STARTSTACKSTR(concat);
+ ap = argv + 2;
+ for (;;) {
+ while (*p)
+ STPUTC(*p++, concat);
+ if ((p = *ap++) == NULL)
+ break;
+ STPUTC(' ', concat);
+ }
+ STPUTC('\0', concat);
+ p = grabstackstr(concat);
+ }
+ } else
+ p = "";
+
+ i = arith(p, line_number);
+
+ out1fmt(ARITH_FORMAT_STR "\n", i);
+ return !i;
+}
diff --git a/bin/sh/arithmetic.h b/bin/sh/arithmetic.h
new file mode 100644
index 0000000..51808d3
--- /dev/null
+++ b/bin/sh/arithmetic.h
@@ -0,0 +1,42 @@
+/* $NetBSD: arithmetic.h,v 1.2 2017/06/07 05:08:32 kre Exp $ */
+
+/*-
+ * Copyright (c) 1995
+ * The Regents of the University of California. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 4. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * @(#)arith.h 1.1 (Berkeley) 5/4/95
+ *
+ * From FreeBSD, from dash
+ */
+
+#include "shell.h"
+
+#define DIGITS(var) (3 + (2 + CHAR_BIT * sizeof((var))) / 3)
+
+#define ARITH_FORMAT_STR "%" PRIdMAX
+
+intmax_t arith(const char *, int);
diff --git a/bin/sh/bltin/bltin.h b/bin/sh/bltin/bltin.h
new file mode 100644
index 0000000..b5669f1
--- /dev/null
+++ b/bin/sh/bltin/bltin.h
@@ -0,0 +1,105 @@
+/* $NetBSD: bltin.h,v 1.15 2017/06/26 22:09:16 kre Exp $ */
+
+/*-
+ * Copyright (c) 1991, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Kenneth Almquist.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * @(#)bltin.h 8.1 (Berkeley) 5/31/93
+ */
+
+/*
+ * This file is included by programs which are optionally built into the
+ * shell.
+ *
+ * We always define SHELL_BUILTIN, to allow other included headers to
+ * hide some of their symbols if appropriate.
+ *
+ * If SHELL is defined, we try to map the standard UNIX library routines
+ * to ash routines using defines.
+ */
+
+#define SHELL_BUILTIN
+#include "../shell.h"
+#include "../mystring.h"
+#ifdef SHELL
+#include "../output.h"
+#include "../error.h"
+#include "../var.h"
+#undef stdout
+#undef stderr
+#undef putc
+#undef putchar
+#undef fileno
+#undef ferror
+#define FILE struct output
+#define stdout out1
+#define stderr out2
+#ifdef __GNUC__
+#define _RETURN_INT(x) ({(x); 0;}) /* map from void foo() to int bar() */
+#else
+#define _RETURN_INT(x) ((x), 0) /* map from void foo() to int bar() */
+#endif
+#define fprintf(...) _RETURN_INT(outfmt(__VA_ARGS__))
+#define printf(...) _RETURN_INT(out1fmt(__VA_ARGS__))
+#define putc(c, file) _RETURN_INT(outc(c, file))
+#define putchar(c) _RETURN_INT(out1c(c))
+#define fputs(...) _RETURN_INT(outstr(__VA_ARGS__))
+#define fflush(f) _RETURN_INT(flushout(f))
+#define fileno(f) ((f)->fd)
+#define ferror(f) ((f)->flags & OUTPUT_ERR)
+#define INITARGS(argv)
+#define err sh_err
+#define verr sh_verr
+#define errx sh_errx
+#define verrx sh_verrx
+#define warn sh_warn
+#define vwarn sh_vwarn
+#define warnx sh_warnx
+#define vwarnx sh_vwarnx
+#define exit sh_exit
+#define setprogname(s)
+#define getprogname() commandname
+#define setlocate(l,s) 0
+
+#define getenv(p) bltinlookup((p),0)
+
+#else /* ! SHELL */
+#undef NULL
+#include <stdio.h>
+#undef main
+#define INITARGS(argv) if ((commandname = argv[0]) == NULL) {fputs("Argc is zero\n", stderr); exit(2);} else
+#endif /* ! SHELL */
+
+pointer stalloc(int);
+
+int echocmd(int, char **);
+
+
+extern const char *commandname;
diff --git a/bin/sh/bltin/echo.1 b/bin/sh/bltin/echo.1
new file mode 100644
index 0000000..13b3c6a
--- /dev/null
+++ b/bin/sh/bltin/echo.1
@@ -0,0 +1,109 @@
+.\" $NetBSD: echo.1,v 1.14 2017/07/03 21:33:24 wiz Exp $
+.\"
+.\" Copyright (c) 1991, 1993
+.\" The Regents of the University of California. All rights reserved.
+.\"
+.\" This code is derived from software contributed to Berkeley by
+.\" Kenneth Almquist.
+.\" Copyright 1989 by Kenneth Almquist
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\" notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must reproduce the above copyright
+.\" notice, this list of conditions and the following disclaimer in the
+.\" documentation and/or other materials provided with the distribution.
+.\" 3. Neither the name of the University nor the names of its contributors
+.\" may be used to endorse or promote products derived from this software
+.\" without specific prior written permission.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+.\" ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+.\" SUCH DAMAGE.
+.\"
+.\" @(#)echo.1 8.1 (Berkeley) 5/31/93
+.\"
+.Dd May 31, 1993
+.Dt ECHO 1
+.Os
+.Sh NAME
+.Nm echo
+.Nd produce message in a shell script
+.Sh SYNOPSIS
+.Nm
+.Op Fl n | Fl e
+.Ar args ...
+.Sh DESCRIPTION
+.Nm
+prints its arguments on the standard output, separated by spaces.
+Unless the
+.Fl n
+option is present, a newline is output following the arguments.
+The
+.Fl e
+option causes
+.Nm
+to treat the escape sequences specially, as described in the following
+paragraph.
+The
+.Fl e
+option is the default, and is provided solely for compatibility with
+other systems.
+Only one of the options
+.Fl n
+and
+.Fl e
+may be given.
+.Pp
+If any of the following sequences of characters is encountered during
+output, the sequence is not output. Instead, the specified action is
+performed:
+.Bl -tag -width indent
+.It Li \eb
+A backspace character is output.
+.It Li \ec
+Subsequent output is suppressed. This is normally used at the end of the
+last argument to suppress the trailing newline that
+.Nm
+would otherwise output.
+.It Li \ef
+Output a form feed.
+.It Li \en
+Output a newline character.
+.It Li \er
+Output a carriage return.
+.It Li \et
+Output a (horizontal) tab character.
+.It Li \ev
+Output a vertical tab.
+.It Li \e0 Ns Ar digits
+Output the character whose value is given by zero to three digits.
+If there are zero digits, a nul character is output.
+.It Li \e\e
+Output a backslash.
+.El
+.Sh HINTS
+Remember that backslash is special to the shell and needs to be escaped.
+To output a message to standard error, say
+.Pp
+.D1 echo message >&2
+.Sh BUGS
+The octal character escape mechanism
+.Pq Li \e0 Ns Ar digits
+differs from the
+C language mechanism.
+.Pp
+There is no way to force
+.Nm
+to treat its arguments literally, rather than interpreting them as
+options and escape sequences.
diff --git a/bin/sh/bltin/echo.c b/bin/sh/bltin/echo.c
new file mode 100644
index 0000000..440e01b
--- /dev/null
+++ b/bin/sh/bltin/echo.c
@@ -0,0 +1,122 @@
+/* $NetBSD: echo.c,v 1.14 2008/10/12 01:40:37 dholland Exp $ */
+
+/*-
+ * Copyright (c) 1991, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Kenneth Almquist.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * @(#)echo.c 8.1 (Berkeley) 5/31/93
+ */
+
+/*
+ * Echo command.
+ *
+ * echo is steeped in tradition - several of them!
+ * netbsd has supported 'echo [-n | -e] args' in spite of -e not being
+ * documented anywhere.
+ * Posix requires that -n be supported, output from strings containing
+ * \ is implementation defined
+ * The Single Unix Spec requires that \ escapes be treated as if -e
+ * were set, but that -n not be treated as an option.
+ * (ksh supports 'echo [-eEn] args', but not -- so that it is actually
+ * impossible to actually output '-n')
+ *
+ * It is suggested that 'printf "%b" "string"' be used to get \ sequences
+ * expanded. printf is now a builtin of netbsd's sh and csh.
+ */
+
+#include <sys/cdefs.h>
+__RCSID("$NetBSD: echo.c,v 1.14 2008/10/12 01:40:37 dholland Exp $");
+
+#define main echocmd
+
+#include "bltin.h"
+
+int
+main(int argc, char **argv)
+{
+ char **ap;
+ char *p;
+ char c;
+ int count;
+ int nflag = 0;
+ int eflag = 0;
+
+ ap = argv;
+ if (argc)
+ ap++;
+
+ if ((p = *ap) != NULL) {
+ if (equal(p, "-n")) {
+ nflag = 1;
+ ap++;
+ } else if (equal(p, "-e")) {
+ eflag = 1;
+ ap++;
+ }
+ }
+
+ while ((p = *ap++) != NULL) {
+ while ((c = *p++) != '\0') {
+ if (c == '\\' && eflag) {
+ switch (*p++) {
+ case 'a': c = '\a'; break; /* bell */
+ case 'b': c = '\b'; break;
+ case 'c': return 0; /* exit */
+ case 'e': c = 033; break; /* escape */
+ case 'f': c = '\f'; break;
+ case 'n': c = '\n'; break;
+ case 'r': c = '\r'; break;
+ case 't': c = '\t'; break;
+ case 'v': c = '\v'; break;
+ case '\\': break; /* c = '\\' */
+ case '0':
+ c = 0;
+ count = 3;
+ while (--count >= 0 && (unsigned)(*p - '0') < 8)
+ c = (c << 3) + (*p++ - '0');
+ break;
+ default:
+ /* Output the '/' and char following */
+ p--;
+ break;
+ }
+ }
+ putchar(c);
+ }
+ if (*ap)
+ putchar(' ');
+ }
+ if (! nflag)
+ putchar('\n');
+ fflush(stdout);
+ if (ferror(stdout))
+ return 1;
+ return 0;
+}
diff --git a/bin/sh/builtins.def b/bin/sh/builtins.def
new file mode 100644
index 0000000..d702707
--- /dev/null
+++ b/bin/sh/builtins.def
@@ -0,0 +1,98 @@
+#!/bin/sh -
+# $NetBSD: builtins.def,v 1.25 2017/05/15 20:00:36 kre Exp $
+#
+# Copyright (c) 1991, 1993
+# The Regents of the University of California. All rights reserved.
+#
+# This code is derived from software contributed to Berkeley by
+# Kenneth Almquist.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1. Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+# 3. Neither the name of the University nor the names of its contributors
+# may be used to endorse or promote products derived from this software
+# without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+# ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+# SUCH DAMAGE.
+#
+# @(#)builtins.def 8.4 (Berkeley) 5/4/95
+
+#
+# This file lists all the builtin commands. The first column is the name
+# of a C routine.
+# The -j flag specifies that this command is to be excluded from systems
+# without job control.
+# The -h flag specifies that this command is to be excluded from systems
+# based on the SMALL compile-time symbol.
+# The -s flag specifies that this is a posix 'special builtin' command.
+# The -u flag specifies that this is a posix 'standard utility'.
+# The rest of the line specifies the command name or names used to run
+# the command.
+
+bltincmd -u command
+bgcmd -j -u bg
+breakcmd -s break -s continue
+cdcmd -u cd chdir
+dotcmd -s .
+echocmd echo
+evalcmd -s eval
+execcmd -s exec
+exitcmd -s exit
+expcmd exp let
+exportcmd -s export -s readonly
+falsecmd -u false
+histcmd -h -u fc
+inputrc inputrc
+fgcmd -j -u fg
+fgcmd_percent -j -u %
+getoptscmd -u getopts
+hashcmd hash
+jobidcmd jobid
+jobscmd -u jobs
+localcmd local
+#ifndef TINY
+printfcmd printf
+#endif
+pwdcmd -u pwd
+readcmd -u read
+returncmd -s return
+setcmd -s set
+fdflagscmd fdflags
+setvarcmd setvar
+shiftcmd -s shift
+timescmd -s times
+trapcmd -s trap
+truecmd -s : -u true
+typecmd type
+umaskcmd -u umask
+unaliascmd -u unalias
+unsetcmd -s unset
+waitcmd -u wait
+aliascmd -u alias
+ulimitcmd ulimit
+testcmd test [
+killcmd -u kill # mandated by posix for 'kill %job'
+wordexpcmd wordexp
+#newgrp -u newgrp # optional command in posix
+
+#ifdef DEBUG
+debugcmd debug
+#endif
+
+#exprcmd expr
diff --git a/bin/sh/cd.c b/bin/sh/cd.c
new file mode 100644
index 0000000..2b1046d
--- /dev/null
+++ b/bin/sh/cd.c
@@ -0,0 +1,463 @@
+/* $NetBSD: cd.c,v 1.50 2017/07/05 20:00:27 kre Exp $ */
+
+/*-
+ * Copyright (c) 1991, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Kenneth Almquist.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+#ifndef lint
+#if 0
+static char sccsid[] = "@(#)cd.c 8.2 (Berkeley) 5/4/95";
+#else
+__RCSID("$NetBSD: cd.c,v 1.50 2017/07/05 20:00:27 kre Exp $");
+#endif
+#endif /* not lint */
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <errno.h>
+
+/*
+ * The cd and pwd commands.
+ */
+
+#include "shell.h"
+#include "var.h"
+#include "nodes.h" /* for jobs.h */
+#include "jobs.h"
+#include "options.h"
+#include "builtins.h"
+#include "output.h"
+#include "memalloc.h"
+#include "error.h"
+#include "exec.h"
+#include "redir.h"
+#include "mystring.h"
+#include "show.h"
+#include "cd.h"
+
+STATIC int docd(const char *, int);
+STATIC char *getcomponent(void);
+STATIC void updatepwd(const char *);
+STATIC void find_curdir(int noerror);
+
+char *curdir = NULL; /* current working directory */
+char *prevdir; /* previous working directory */
+STATIC char *cdcomppath;
+
+int
+cdcmd(int argc, char **argv)
+{
+ const char *dest;
+ const char *path, *cp;
+ char *p;
+ char *d;
+ struct stat statb;
+ int print = cdprint; /* set -o cdprint to enable */
+
+ while (nextopt("P") != '\0')
+ ;
+
+ /*
+ * Try (quite hard) to have 'curdir' defined, nothing has set
+ * it on entry to the shell, but we want 'cd fred; cd -' to work.
+ */
+ getpwd(1);
+ dest = *argptr;
+ if (dest == NULL) {
+ dest = bltinlookup("HOME", 1);
+ if (dest == NULL)
+ error("HOME not set");
+ } else if (argptr[1]) {
+ /* Do 'ksh' style substitution */
+ if (!curdir)
+ error("PWD not set");
+ p = strstr(curdir, dest);
+ if (!p)
+ error("bad substitution");
+ d = stalloc(strlen(curdir) + strlen(argptr[1]) + 1);
+ memcpy(d, curdir, p - curdir);
+ strcpy(d + (p - curdir), argptr[1]);
+ strcat(d, p + strlen(dest));
+ dest = d;
+ print = 1;
+ } else if (dest[0] == '-' && dest[1] == '\0') {
+ dest = prevdir ? prevdir : curdir;
+ print = 1;
+ }
+ if (*dest == '\0')
+ dest = ".";
+
+ cp = dest;
+ if (*cp == '.' && *++cp == '.')
+ cp++;
+ if (*cp == 0 || *cp == '/' || (path = bltinlookup("CDPATH", 1)) == NULL)
+ path = nullstr;
+ while ((p = padvance(&path, dest, 0)) != NULL) {
+ stunalloc(p);
+ if (stat(p, &statb) >= 0 && S_ISDIR(statb.st_mode)) {
+ int dopr = print;
+
+ if (!print) {
+ /*
+ * XXX - rethink
+ */
+ if (p[0] == '.' && p[1] == '/' && p[2] != '\0')
+ dopr = strcmp(p + 2, dest);
+ else
+ dopr = strcmp(p, dest);
+ }
+ if (docd(p, dopr) >= 0)
+ return 0;
+
+ }
+ }
+ error("can't cd to %s", dest);
+ /* NOTREACHED */
+}
+
+
+/*
+ * Actually do the chdir. In an interactive shell, print the
+ * directory name if "print" is nonzero.
+ */
+
+STATIC int
+docd(const char *dest, int print)
+{
+#if 0 /* no "cd -L" (ever) so all this is just a waste of time ... */
+ char *p;
+ char *q;
+ char *component;
+ struct stat statb;
+ int first;
+ int badstat;
+
+ CTRACE(DBG_CMDS, ("docd(\"%s\", %d) called\n", dest, print));
+
+ /*
+ * Check each component of the path. If we find a symlink or
+ * something we can't stat, clear curdir to force a getcwd()
+ * next time we get the value of the current directory.
+ */
+ badstat = 0;
+ cdcomppath = stalloc(strlen(dest) + 1);
+ scopy(dest, cdcomppath);
+ STARTSTACKSTR(p);
+ if (*dest == '/') {
+ STPUTC('/', p);
+ cdcomppath++;
+ }
+ first = 1;
+ while ((q = getcomponent()) != NULL) {
+ if (q[0] == '\0' || (q[0] == '.' && q[1] == '\0'))
+ continue;
+ if (! first)
+ STPUTC('/', p);
+ first = 0;
+ component = q;
+ while (*q)
+ STPUTC(*q++, p);
+ if (equal(component, ".."))
+ continue;
+ STACKSTRNUL(p);
+ if (lstat(stackblock(), &statb) < 0) {
+ badstat = 1;
+ break;
+ }
+ }
+#endif
+
+ INTOFF;
+ if (chdir(dest) < 0) {
+ INTON;
+ return -1;
+ }
+ updatepwd(NULL); /* only do cd -P, no "pretend" -L mode */
+ INTON;
+ if (print && iflag == 1 && curdir)
+ out1fmt("%s\n", curdir);
+ return 0;
+}
+
+
+/*
+ * Get the next component of the path name pointed to by cdcomppath.
+ * This routine overwrites the string pointed to by cdcomppath.
+ */
+
+STATIC char *
+getcomponent(void)
+{
+ char *p;
+ char *start;
+
+ if ((p = cdcomppath) == NULL)
+ return NULL;
+ start = cdcomppath;
+ while (*p != '/' && *p != '\0')
+ p++;
+ if (*p == '\0') {
+ cdcomppath = NULL;
+ } else {
+ *p++ = '\0';
+ cdcomppath = p;
+ }
+ return start;
+}
+
+
+
+/*
+ * Update curdir (the name of the current directory) in response to a
+ * cd command. We also call hashcd to let the routines in exec.c know
+ * that the current directory has changed.
+ */
+
+STATIC void
+updatepwd(const char *dir)
+{
+ char *new;
+ char *p;
+
+ hashcd(); /* update command hash table */
+
+ /*
+ * If our argument is NULL, we don't know the current directory
+ * any more because we traversed a symbolic link or something
+ * we couldn't stat().
+ */
+ if (dir == NULL || curdir == NULL) {
+ if (prevdir)
+ ckfree(prevdir);
+ INTOFF;
+ prevdir = curdir;
+ curdir = NULL;
+ getpwd(1);
+ INTON;
+ if (curdir) {
+ setvar("OLDPWD", prevdir, VEXPORT);
+ setvar("PWD", curdir, VEXPORT);
+ } else
+ unsetvar("PWD", 0);
+ return;
+ }
+ cdcomppath = stalloc(strlen(dir) + 1);
+ scopy(dir, cdcomppath);
+ STARTSTACKSTR(new);
+ if (*dir != '/') {
+ p = curdir;
+ while (*p)
+ STPUTC(*p++, new);
+ if (p[-1] == '/')
+ STUNPUTC(new);
+ }
+ while ((p = getcomponent()) != NULL) {
+ if (equal(p, "..")) {
+ while (new > stackblock() && (STUNPUTC(new), *new) != '/');
+ } else if (*p != '\0' && ! equal(p, ".")) {
+ STPUTC('/', new);
+ while (*p)
+ STPUTC(*p++, new);
+ }
+ }
+ if (new == stackblock())
+ STPUTC('/', new);
+ STACKSTRNUL(new);
+ INTOFF;
+ if (prevdir)
+ ckfree(prevdir);
+ prevdir = curdir;
+ curdir = savestr(stackblock());
+ setvar("OLDPWD", prevdir, VEXPORT);
+ setvar("PWD", curdir, VEXPORT);
+ INTON;
+}
+
+/*
+ * Posix says the default should be 'pwd -L' (as below), however
+ * the 'cd' command (above) does something much nearer to the
+ * posix 'cd -P' (not the posix default of 'cd -L').
+ * If 'cd' is changed to support -P/L then the default here
+ * needs to be revisited if the historic behaviour is to be kept.
+ */
+
+int
+pwdcmd(int argc, char **argv)
+{
+ int i;
+ char opt = 'L';
+
+ while ((i = nextopt("LP")) != '\0')
+ opt = i;
+ if (*argptr)
+ error("unexpected argument");
+
+ if (opt == 'L')
+ getpwd(0);
+ else
+ find_curdir(0);
+
+ setvar("OLDPWD", prevdir, VEXPORT);
+ setvar("PWD", curdir, VEXPORT);
+ out1str(curdir);
+ out1c('\n');
+ return 0;
+}
+
+
+
+void
+initpwd(void)
+{
+ getpwd(1);
+ if (curdir)
+ setvar("PWD", curdir, VEXPORT);
+ else
+ sh_warnx("Cannot determine current working directory");
+}
+
+#define MAXPWD 256
+
+/*
+ * Find out what the current directory is. If we already know the current
+ * directory, this routine returns immediately.
+ */
+void
+getpwd(int noerror)
+{
+ char *pwd;
+ struct stat stdot, stpwd;
+ static int first = 1;
+
+ if (curdir)
+ return;
+
+ if (first) {
+ first = 0;
+ pwd = getenv("PWD");
+ if (pwd && *pwd == '/' && stat(".", &stdot) != -1 &&
+ stat(pwd, &stpwd) != -1 &&
+ stdot.st_dev == stpwd.st_dev &&
+ stdot.st_ino == stpwd.st_ino) {
+ curdir = savestr(pwd);
+ return;
+ }
+ }
+
+ find_curdir(noerror);
+
+ return;
+}
+
+STATIC void
+find_curdir(int noerror)
+{
+ int i;
+ char *pwd;
+
+ /*
+ * Things are a bit complicated here; we could have just used
+ * getcwd, but traditionally getcwd is implemented using popen
+ * to /bin/pwd. This creates a problem for us, since we cannot
+ * keep track of the job if it is being ran behind our backs.
+ * So we re-implement getcwd(), and we suppress interrupts
+ * throughout the process. This is not completely safe, since
+ * the user can still break out of it by killing the pwd program.
+ * We still try to use getcwd for systems that we know have a
+ * c implementation of getcwd, that does not open a pipe to
+ * /bin/pwd.
+ */
+#if defined(__NetBSD__) || defined(__SVR4)
+
+ for (i = MAXPWD;; i *= 2) {
+ pwd = stalloc(i);
+ if (getcwd(pwd, i) != NULL) {
+ curdir = savestr(pwd);
+ stunalloc(pwd);
+ return;
+ }
+ stunalloc(pwd);
+ if (errno == ERANGE)
+ continue;
+ if (!noerror)
+ error("getcwd() failed: %s", strerror(errno));
+ return;
+ }
+#else
+ {
+ char *p;
+ int status;
+ struct job *jp;
+ int pip[2];
+
+ pwd = stalloc(MAXPWD);
+ INTOFF;
+ if (pipe(pip) < 0)
+ error("Pipe call failed");
+ jp = makejob(NULL, 1);
+ if (forkshell(jp, NULL, FORK_NOJOB) == 0) {
+ (void) close(pip[0]);
+ movefd(pip[1], 1);
+ (void) execl("/bin/pwd", "pwd", (char *)0);
+ error("Cannot exec /bin/pwd");
+ }
+ (void) close(pip[1]);
+ pip[1] = -1;
+ p = pwd;
+ while ((i = read(pip[0], p, pwd + MAXPWD - p)) > 0
+ || (i == -1 && errno == EINTR)) {
+ if (i > 0)
+ p += i;
+ }
+ (void) close(pip[0]);
+ pip[0] = -1;
+ status = waitforjob(jp);
+ if (status != 0)
+ error((char *)0);
+ if (i < 0 || p == pwd || p[-1] != '\n') {
+ if (noerror) {
+ INTON;
+ return;
+ }
+ error("pwd command failed");
+ }
+ p[-1] = '\0';
+ INTON;
+ curdir = savestr(pwd);
+ stunalloc(pwd);
+ return;
+ }
+#endif
+}
diff --git a/bin/sh/cd.h b/bin/sh/cd.h
new file mode 100644
index 0000000..7600955
--- /dev/null
+++ b/bin/sh/cd.h
@@ -0,0 +1,34 @@
+/* $NetBSD: cd.h,v 1.6 2011/06/18 21:18:46 christos Exp $ */
+
+/*-
+ * Copyright (c) 1995
+ * The Regents of the University of California. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ */
+
+void initpwd(void);
+void getpwd(int);
diff --git a/bin/sh/error.c b/bin/sh/error.c
new file mode 100644
index 0000000..db42926
--- /dev/null
+++ b/bin/sh/error.c
@@ -0,0 +1,378 @@
+/* $NetBSD: error.c,v 1.42 2019/01/21 14:29:12 kre Exp $ */
+
+/*-
+ * Copyright (c) 1991, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Kenneth Almquist.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+#ifndef lint
+#if 0
+static char sccsid[] = "@(#)error.c 8.2 (Berkeley) 5/4/95";
+#else
+__RCSID("$NetBSD: error.c,v 1.42 2019/01/21 14:29:12 kre Exp $");
+#endif
+#endif /* not lint */
+
+/*
+ * Errors and exceptions.
+ */
+
+#include <signal.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <errno.h>
+#include <stdio.h>
+#include <string.h>
+
+#include "shell.h"
+#include "eval.h" /* for commandname */
+#include "main.h"
+#include "options.h"
+#include "output.h"
+#include "error.h"
+#include "show.h"
+
+
+/*
+ * Code to handle exceptions in C.
+ */
+
+struct jmploc *handler;
+int exception;
+volatile int suppressint;
+volatile int intpending;
+
+
+static void exverror(int, const char *, va_list) __dead;
+
+/*
+ * Called to raise an exception. Since C doesn't include exceptions, we
+ * just do a longjmp to the exception handler. The type of exception is
+ * stored in the global variable "exception".
+ */
+
+void
+exraise(int e)
+{
+ CTRACE(DBG_ERRS, ("exraise(%d)\n", e));
+ if (handler == NULL)
+ abort();
+ exception = e;
+ longjmp(handler->loc, 1);
+}
+
+
+/*
+ * Called from trap.c when a SIGINT is received. (If the user specifies
+ * that SIGINT is to be trapped or ignored using the trap builtin, then
+ * this routine is not called.) Suppressint is nonzero when interrupts
+ * are held using the INTOFF macro. The call to _exit is necessary because
+ * there is a short period after a fork before the signal handlers are
+ * set to the appropriate value for the child. (The test for iflag is
+ * just defensive programming.)
+ */
+
+void
+onint(void)
+{
+ sigset_t nsigset;
+
+ if (suppressint) {
+ intpending = 1;
+ return;
+ }
+ intpending = 0;
+ sigemptyset(&nsigset);
+ sigprocmask(SIG_SETMASK, &nsigset, NULL);
+ if (rootshell && iflag)
+ exraise(EXINT);
+ else {
+ signal(SIGINT, SIG_DFL);
+ raise(SIGINT);
+ }
+ /* NOTREACHED */
+}
+
+static __printflike(2, 0) void
+exvwarning(int sv_errno, const char *msg, va_list ap)
+{
+ /* Partially emulate line buffered output so that:
+ * printf '%d\n' 1 a 2
+ * and
+ * printf '%d %d %d\n' 1 a 2
+ * both generate sensible text when stdout and stderr are merged.
+ */
+ if (output.buf != NULL && output.nextc != output.buf &&
+ output.nextc[-1] == '\n')
+ flushout(&output);
+ if (commandname)
+ outfmt(&errout, "%s: ", commandname);
+ else
+ outfmt(&errout, "%s: ", getprogname());
+ if (msg != NULL) {
+ doformat(&errout, msg, ap);
+ if (sv_errno >= 0)
+ outfmt(&errout, ": ");
+ }
+ if (sv_errno >= 0)
+ outfmt(&errout, "%s", strerror(sv_errno));
+ out2c('\n');
+ flushout(&errout);
+}
+
+/*
+ * Exverror is called to raise the error exception. If the second argument
+ * is not NULL then error prints an error message using printf style
+ * formatting. It then raises the error exception.
+ */
+static __printflike(2, 0) void
+exverror(int cond, const char *msg, va_list ap)
+{
+ CLEAR_PENDING_INT;
+ INTOFF;
+
+#ifdef DEBUG
+ if (msg) {
+ CTRACE(DBG_ERRS, ("exverror(%d, \"", cond));
+ CTRACEV(DBG_ERRS, (msg, ap));
+ CTRACE(DBG_ERRS, ("\") pid=%d\n", getpid()));
+ } else
+ CTRACE(DBG_ERRS, ("exverror(%d, NULL) pid=%d\n", cond,
+ getpid()));
+#endif
+ if (msg)
+ exvwarning(-1, msg, ap);
+
+ flushall();
+ exraise(cond);
+ /* NOTREACHED */
+}
+
+
+void
+error(const char *msg, ...)
+{
+ va_list ap;
+
+ /*
+ * On error, we certainly never want exit(0)...
+ */
+ if (exerrno == 0)
+ exerrno = 1;
+ va_start(ap, msg);
+ exverror(EXERROR, msg, ap);
+ /* NOTREACHED */
+ va_end(ap);
+}
+
+
+void
+exerror(int cond, const char *msg, ...)
+{
+ va_list ap;
+
+ va_start(ap, msg);
+ exverror(cond, msg, ap);
+ /* NOTREACHED */
+ va_end(ap);
+}
+
+/*
+ * error/warning routines for external builtins
+ */
+
+void
+sh_exit(int rval)
+{
+ exerrno = rval & 255;
+ exraise(EXEXEC);
+}
+
+void
+sh_err(int status, const char *fmt, ...)
+{
+ va_list ap;
+
+ va_start(ap, fmt);
+ exvwarning(errno, fmt, ap);
+ va_end(ap);
+ sh_exit(status);
+}
+
+void
+sh_verr(int status, const char *fmt, va_list ap)
+{
+ exvwarning(errno, fmt, ap);
+ sh_exit(status);
+}
+
+void
+sh_errx(int status, const char *fmt, ...)
+{
+ va_list ap;
+
+ va_start(ap, fmt);
+ exvwarning(-1, fmt, ap);
+ va_end(ap);
+ sh_exit(status);
+}
+
+void
+sh_verrx(int status, const char *fmt, va_list ap)
+{
+ exvwarning(-1, fmt, ap);
+ sh_exit(status);
+}
+
+void
+sh_warn(const char *fmt, ...)
+{
+ va_list ap;
+
+ va_start(ap, fmt);
+ exvwarning(errno, fmt, ap);
+ va_end(ap);
+}
+
+void
+sh_vwarn(const char *fmt, va_list ap)
+{
+ exvwarning(errno, fmt, ap);
+}
+
+void
+sh_warnx(const char *fmt, ...)
+{
+ va_list ap;
+
+ va_start(ap, fmt);
+ exvwarning(-1, fmt, ap);
+ va_end(ap);
+}
+
+void
+sh_vwarnx(const char *fmt, va_list ap)
+{
+ exvwarning(-1, fmt, ap);
+}
+
+
+/*
+ * Table of error messages.
+ */
+
+struct errname {
+ short errcode; /* error number */
+ short action; /* operation which encountered the error */
+ const char *msg; /* text describing the error */
+};
+
+
+#define ALL (E_OPEN|E_CREAT|E_EXEC)
+
+STATIC const struct errname errormsg[] = {
+ { EINTR, ALL, "interrupted" },
+ { EACCES, ALL, "permission denied" },
+ { EIO, ALL, "I/O error" },
+ { EEXIST, ALL, "file exists" },
+ { ENOENT, E_OPEN, "no such file" },
+ { ENOENT, E_CREAT,"directory nonexistent" },
+ { ENOENT, E_EXEC, "not found" },
+ { ENOTDIR, E_OPEN, "no such file" },
+ { ENOTDIR, E_CREAT,"directory nonexistent" },
+ { ENOTDIR, E_EXEC, "not found" },
+ { EISDIR, ALL, "is a directory" },
+#ifdef EMFILE
+ { EMFILE, ALL, "too many open files" },
+#endif
+ { ENFILE, ALL, "file table overflow" },
+ { ENOSPC, ALL, "file system full" },
+#ifdef EDQUOT
+ { EDQUOT, ALL, "disk quota exceeded" },
+#endif
+#ifdef ENOSR
+ { ENOSR, ALL, "no streams resources" },
+#endif
+ { ENXIO, ALL, "no such device or address" },
+ { EROFS, ALL, "read-only file system" },
+ { ETXTBSY, ALL, "text busy" },
+#ifdef EAGAIN
+ { EAGAIN, E_EXEC, "not enough memory" },
+#endif
+ { ENOMEM, ALL, "not enough memory" },
+#ifdef ENOLINK
+ { ENOLINK, ALL, "remote access failed" },
+#endif
+#ifdef EMULTIHOP
+ { EMULTIHOP, ALL, "remote access failed" },
+#endif
+#ifdef ECOMM
+ { ECOMM, ALL, "remote access failed" },
+#endif
+#ifdef ESTALE
+ { ESTALE, ALL, "remote access failed" },
+#endif
+#ifdef ETIMEDOUT
+ { ETIMEDOUT, ALL, "remote access failed" },
+#endif
+#ifdef ELOOP
+ { ELOOP, ALL, "symbolic link loop" },
+#endif
+#ifdef ENAMETOOLONG
+ { ENAMETOOLONG, ALL, "file name too long" },
+#endif
+ { E2BIG, E_EXEC, "argument list too long" },
+#ifdef ELIBACC
+ { ELIBACC, E_EXEC, "shared library missing" },
+#endif
+ { 0, 0, NULL },
+};
+
+
+/*
+ * Return a string describing an error. The returned string may be a
+ * pointer to a static buffer that will be overwritten on the next call.
+ * Action describes the operation that got the error.
+ */
+
+const char *
+errmsg(int e, int action)
+{
+ struct errname const *ep;
+ static char buf[12];
+
+ for (ep = errormsg ; ep->errcode ; ep++) {
+ if (ep->errcode == e && (ep->action & action) != 0)
+ return ep->msg;
+ }
+ fmtstr(buf, sizeof buf, "error %d", e);
+ return buf;
+}
diff --git a/bin/sh/error.h b/bin/sh/error.h
new file mode 100644
index 0000000..8dcf737
--- /dev/null
+++ b/bin/sh/error.h
@@ -0,0 +1,120 @@
+/* $NetBSD: error.h,v 1.21 2018/08/19 23:50:27 kre Exp $ */
+
+/*-
+ * Copyright (c) 1991, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Kenneth Almquist.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * @(#)error.h 8.2 (Berkeley) 5/4/95
+ */
+
+#include <stdarg.h>
+
+/*
+ * Types of operations (passed to the errmsg routine).
+ */
+
+#define E_OPEN 01 /* opening a file */
+#define E_CREAT 02 /* creating a file */
+#define E_EXEC 04 /* executing a program */
+
+
+/*
+ * We enclose jmp_buf in a structure so that we can declare pointers to
+ * jump locations. The global variable handler contains the location to
+ * jump to when an exception occurs, and the global variable exception
+ * contains a code identifying the exeception. To implement nested
+ * exception handlers, the user should save the value of handler on entry
+ * to an inner scope, set handler to point to a jmploc structure for the
+ * inner scope, and restore handler on exit from the scope.
+ */
+
+#include <setjmp.h>
+
+struct jmploc {
+ jmp_buf loc;
+};
+
+extern struct jmploc *handler;
+extern int exception;
+extern int exerrno; /* error for EXEXEC */
+
+/* exceptions */
+#define EXINT 0 /* SIGINT received */
+#define EXERROR 1 /* a generic error */
+#define EXSHELLPROC 2 /* execute a shell procedure */
+#define EXEXEC 3 /* command execution failed */
+#define EXEXIT 4 /* shell wants to exit(exitstatus) */
+
+
+/*
+ * These macros allow the user to suspend the handling of interrupt signals
+ * over a period of time. This is similar to SIGHOLD to or sigblock, but
+ * much more efficient and portable. (But hacking the kernel is so much
+ * more fun than worrying about efficiency and portability. :-))
+ */
+
+extern volatile int suppressint;
+extern volatile int intpending;
+
+#define INTOFF suppressint++
+#define INTON do { if (--suppressint == 0 && intpending) onint(); } while (0)
+#define FORCEINTON do { suppressint = 0; if (intpending) onint(); } while (0)
+#define CLEAR_PENDING_INT (intpending = 0)
+#define int_pending() intpending
+
+#if ! defined(SHELL_BUILTIN)
+void exraise(int) __dead;
+void onint(void);
+void error(const char *, ...) __dead __printflike(1, 2);
+void exerror(int, const char *, ...) __dead __printflike(2, 3);
+const char *errmsg(int, int);
+#endif /* ! SHELL_BUILTIN */
+
+void sh_err(int, const char *, ...) __dead __printflike(2, 3);
+void sh_verr(int, const char *, va_list) __dead __printflike(2, 0);
+void sh_errx(int, const char *, ...) __dead __printflike(2, 3);
+void sh_verrx(int, const char *, va_list) __dead __printflike(2, 0);
+void sh_warn(const char *, ...) __printflike(1, 2);
+void sh_vwarn(const char *, va_list) __printflike(1, 0);
+void sh_warnx(const char *, ...) __printflike(1, 2);
+void sh_vwarnx(const char *, va_list) __printflike(1, 0);
+
+void sh_exit(int) __dead;
+
+
+/*
+ * BSD setjmp saves the signal mask, which violates ANSI C and takes time,
+ * so we use _setjmp instead.
+ */
+
+#if defined(BSD) && !defined(__SVR4)
+#define setjmp(jmploc) _setjmp(jmploc)
+#define longjmp(jmploc, val) _longjmp(jmploc, val)
+#endif
diff --git a/bin/sh/eval.c b/bin/sh/eval.c
new file mode 100644
index 0000000..2bda702
--- /dev/null
+++ b/bin/sh/eval.c
@@ -0,0 +1,1680 @@
+/* $NetBSD: eval.c,v 1.170 2019/01/21 14:18:59 kre Exp $ */
+
+/*-
+ * Copyright (c) 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Kenneth Almquist.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+#ifndef lint
+#if 0
+static char sccsid[] = "@(#)eval.c 8.9 (Berkeley) 6/8/95";
+#else
+__RCSID("$NetBSD: eval.c,v 1.170 2019/01/21 14:18:59 kre Exp $");
+#endif
+#endif /* not lint */
+
+#include <stdbool.h>
+#include <stdlib.h>
+#include <signal.h>
+#include <stdio.h>
+#include <string.h>
+#include <errno.h>
+#include <limits.h>
+#include <unistd.h>
+#include <sys/fcntl.h>
+#include <sys/stat.h>
+#include <sys/times.h>
+#include <sys/param.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <sys/sysctl.h>
+
+/*
+ * Evaluate a command.
+ */
+
+#include "shell.h"
+#include "nodes.h"
+#include "syntax.h"
+#include "expand.h"
+#include "parser.h"
+#include "jobs.h"
+#include "eval.h"
+#include "builtins.h"
+#include "options.h"
+#include "exec.h"
+#include "redir.h"
+#include "input.h"
+#include "output.h"
+#include "trap.h"
+#include "var.h"
+#include "memalloc.h"
+#include "error.h"
+#include "show.h"
+#include "mystring.h"
+#include "main.h"
+#ifndef SMALL
+#include "nodenames.h"
+#include "myhistedit.h"
+#endif
+
+
+STATIC struct skipsave s_k_i_p;
+#define evalskip (s_k_i_p.state)
+#define skipcount (s_k_i_p.count)
+
+STATIC int loopnest; /* current loop nesting level */
+STATIC int funcnest; /* depth of function calls */
+STATIC int builtin_flags; /* evalcommand flags for builtins */
+/*
+ * Base function nesting level inside a dot command. Set to 0 initially
+ * and to (funcnest + 1) before every dot command to enable
+ * 1) detection of being in a file sourced by a dot command and
+ * 2) counting of function nesting in that file for the implementation
+ * of the return command.
+ * The value is reset to its previous value after the dot command.
+ */
+STATIC int dot_funcnest;
+
+
+const char *commandname;
+struct strlist *cmdenviron;
+int exitstatus; /* exit status of last command */
+int back_exitstatus; /* exit status of backquoted command */
+
+
+STATIC void evalloop(union node *, int);
+STATIC void evalfor(union node *, int);
+STATIC void evalcase(union node *, int);
+STATIC void evalsubshell(union node *, int);
+STATIC void expredir(union node *);
+STATIC void evalredir(union node *, int);
+STATIC void evalpipe(union node *);
+STATIC void evalcommand(union node *, int, struct backcmd *);
+STATIC void prehash(union node *);
+
+STATIC char *find_dot_file(char *);
+
+/*
+ * Called to reset things after an exception.
+ */
+
+#ifdef mkinit
+INCLUDE "eval.h"
+
+RESET {
+ reset_eval();
+}
+
+SHELLPROC {
+ exitstatus = 0;
+}
+#endif
+
+void
+reset_eval(void)
+{
+ evalskip = SKIPNONE;
+ dot_funcnest = 0;
+ loopnest = 0;
+ funcnest = 0;
+}
+
+static int
+sh_pipe(int fds[2])
+{
+ int nfd;
+
+ if (pipe(fds))
+ return -1;
+
+ if (fds[0] < 3) {
+ nfd = fcntl(fds[0], F_DUPFD, 3);
+ if (nfd != -1) {
+ close(fds[0]);
+ fds[0] = nfd;
+ }
+ }
+
+ if (fds[1] < 3) {
+ nfd = fcntl(fds[1], F_DUPFD, 3);
+ if (nfd != -1) {
+ close(fds[1]);
+ fds[1] = nfd;
+ }
+ }
+ return 0;
+}
+
+
+/*
+ * The eval commmand.
+ */
+
+int
+evalcmd(int argc, char **argv)
+{
+ char *p;
+ char *concat;
+ char **ap;
+
+ if (argc > 1) {
+ p = argv[1];
+ if (argc > 2) {
+ STARTSTACKSTR(concat);
+ ap = argv + 2;
+ for (;;) {
+ while (*p)
+ STPUTC(*p++, concat);
+ if ((p = *ap++) == NULL)
+ break;
+ STPUTC(' ', concat);
+ }
+ STPUTC('\0', concat);
+ p = grabstackstr(concat);
+ }
+ evalstring(p, builtin_flags & EV_TESTED);
+ } else
+ exitstatus = 0;
+ return exitstatus;
+}
+
+
+/*
+ * Execute a command or commands contained in a string.
+ */
+
+void
+evalstring(char *s, int flag)
+{
+ union node *n;
+ struct stackmark smark;
+ int last;
+ int any;
+
+ last = flag & EV_EXIT;
+ flag &= ~EV_EXIT;
+
+ setstackmark(&smark);
+ setinputstring(s, 1, line_number);
+
+ any = 0; /* to determine if exitstatus will have been set */
+ while ((n = parsecmd(0)) != NEOF) {
+ XTRACE(DBG_EVAL, ("evalstring: "), showtree(n));
+ if (n && nflag == 0) {
+ if (last && at_eof())
+ evaltree(n, flag | EV_EXIT);
+ else
+ evaltree(n, flag);
+ any = 1;
+ if (evalskip)
+ break;
+ }
+ rststackmark(&smark);
+ }
+ popfile();
+ popstackmark(&smark);
+ if (!any)
+ exitstatus = 0;
+ if (last)
+ exraise(EXEXIT);
+}
+
+
+
+/*
+ * Evaluate a parse tree. The value is left in the global variable
+ * exitstatus.
+ */
+
+void
+evaltree(union node *n, int flags)
+{
+ bool do_etest;
+ int sflags = flags & ~EV_EXIT;
+ union node *next;
+ struct stackmark smark;
+
+ do_etest = false;
+ if (n == NULL || nflag) {
+ VTRACE(DBG_EVAL, ("evaltree(%s) called\n",
+ n == NULL ? "NULL" : "-n"));
+ if (nflag == 0)
+ exitstatus = 0;
+ goto out2;
+ }
+
+ setstackmark(&smark);
+ do {
+#ifndef SMALL
+ displayhist = 1; /* show history substitutions done with fc */
+#endif
+ next = NULL;
+ CTRACE(DBG_EVAL, ("pid %d, evaltree(%p: %s(%d), %#x) called\n",
+ getpid(), n, NODETYPENAME(n->type), n->type, flags));
+ if (n->type != NCMD && traps_invalid)
+ free_traps();
+ switch (n->type) {
+ case NSEMI:
+ evaltree(n->nbinary.ch1, sflags);
+ if (nflag || evalskip)
+ goto out1;
+ next = n->nbinary.ch2;
+ break;
+ case NAND:
+ evaltree(n->nbinary.ch1, EV_TESTED);
+ if (nflag || evalskip || exitstatus != 0)
+ goto out1;
+ next = n->nbinary.ch2;
+ break;
+ case NOR:
+ evaltree(n->nbinary.ch1, EV_TESTED);
+ if (nflag || evalskip || exitstatus == 0)
+ goto out1;
+ next = n->nbinary.ch2;
+ break;
+ case NREDIR:
+ evalredir(n, flags);
+ break;
+ case NSUBSHELL:
+ evalsubshell(n, flags);
+ do_etest = !(flags & EV_TESTED);
+ break;
+ case NBACKGND:
+ evalsubshell(n, flags);
+ break;
+ case NIF: {
+ evaltree(n->nif.test, EV_TESTED);
+ if (nflag || evalskip)
+ goto out1;
+ if (exitstatus == 0)
+ next = n->nif.ifpart;
+ else if (n->nif.elsepart)
+ next = n->nif.elsepart;
+ else
+ exitstatus = 0;
+ break;
+ }
+ case NWHILE:
+ case NUNTIL:
+ evalloop(n, sflags);
+ break;
+ case NFOR:
+ evalfor(n, sflags);
+ break;
+ case NCASE:
+ evalcase(n, sflags);
+ break;
+ case NDEFUN:
+ CTRACE(DBG_EVAL, ("Defining fn %s @%d%s\n",
+ n->narg.text, n->narg.lineno,
+ fnline1 ? " LINENO=1" : ""));
+ defun(n->narg.text, n->narg.next, n->narg.lineno);
+ exitstatus = 0;
+ break;
+ case NNOT:
+ evaltree(n->nnot.com, EV_TESTED);
+ exitstatus = !exitstatus;
+ break;
+ case NDNOT:
+ evaltree(n->nnot.com, EV_TESTED);
+ if (exitstatus != 0)
+ exitstatus = 1;
+ break;
+ case NPIPE:
+ evalpipe(n);
+ do_etest = !(flags & EV_TESTED);
+ break;
+ case NCMD:
+ evalcommand(n, flags, NULL);
+ do_etest = !(flags & EV_TESTED);
+ break;
+ default:
+#ifdef NODETYPENAME
+ out1fmt("Node type = %d(%s)\n",
+ n->type, NODETYPENAME(n->type));
+#else
+ out1fmt("Node type = %d\n", n->type);
+#endif
+ flushout(&output);
+ break;
+ }
+ n = next;
+ rststackmark(&smark);
+ } while(n != NULL);
+ out1:
+ popstackmark(&smark);
+ out2:
+ if (pendingsigs)
+ dotrap();
+ if (eflag && exitstatus != 0 && do_etest)
+ exitshell(exitstatus);
+ if (flags & EV_EXIT)
+ exraise(EXEXIT);
+}
+
+
+STATIC void
+evalloop(union node *n, int flags)
+{
+ int status;
+
+ loopnest++;
+ status = 0;
+
+ CTRACE(DBG_EVAL, ("evalloop %s:", NODETYPENAME(n->type)));
+ VXTRACE(DBG_EVAL, (" "), showtree(n->nbinary.ch1));
+ VXTRACE(DBG_EVAL, ("evalloop do: "), showtree(n->nbinary.ch2));
+ VTRACE(DBG_EVAL, ("evalloop done\n"));
+ CTRACE(DBG_EVAL, ("\n"));
+
+ for (;;) {
+ evaltree(n->nbinary.ch1, EV_TESTED);
+ if (nflag)
+ break;
+ if (evalskip) {
+ skipping: if (evalskip == SKIPCONT && --skipcount <= 0) {
+ evalskip = SKIPNONE;
+ continue;
+ }
+ if (evalskip == SKIPBREAK && --skipcount <= 0)
+ evalskip = SKIPNONE;
+ break;
+ }
+ if (n->type == NWHILE) {
+ if (exitstatus != 0)
+ break;
+ } else {
+ if (exitstatus == 0)
+ break;
+ }
+ evaltree(n->nbinary.ch2, flags & EV_TESTED);
+ status = exitstatus;
+ if (evalskip)
+ goto skipping;
+ }
+ loopnest--;
+ exitstatus = status;
+}
+
+
+
+STATIC void
+evalfor(union node *n, int flags)
+{
+ struct arglist arglist;
+ union node *argp;
+ struct strlist *sp;
+ struct stackmark smark;
+ int status;
+
+ status = nflag ? exitstatus : 0;
+
+ setstackmark(&smark);
+ arglist.lastp = &arglist.list;
+ for (argp = n->nfor.args ; argp ; argp = argp->narg.next) {
+ expandarg(argp, &arglist, EXP_FULL | EXP_TILDE);
+ if (evalskip)
+ goto out;
+ }
+ *arglist.lastp = NULL;
+
+ loopnest++;
+ for (sp = arglist.list ; sp ; sp = sp->next) {
+ if (xflag) {
+ outxstr(expandstr(ps4val(), line_number));
+ outxstr("for ");
+ outxstr(n->nfor.var);
+ outxc('=');
+ outxshstr(sp->text);
+ outxc('\n');
+ flushout(outx);
+ }
+
+ setvar(n->nfor.var, sp->text, 0);
+ evaltree(n->nfor.body, flags & EV_TESTED);
+ status = exitstatus;
+ if (nflag)
+ break;
+ if (evalskip) {
+ if (evalskip == SKIPCONT && --skipcount <= 0) {
+ evalskip = SKIPNONE;
+ continue;
+ }
+ if (evalskip == SKIPBREAK && --skipcount <= 0)
+ evalskip = SKIPNONE;
+ break;
+ }
+ }
+ loopnest--;
+ exitstatus = status;
+ out:
+ popstackmark(&smark);
+}
+
+
+
+STATIC void
+evalcase(union node *n, int flags)
+{
+ union node *cp, *ncp;
+ union node *patp;
+ struct arglist arglist;
+ struct stackmark smark;
+ int status = 0;
+
+ setstackmark(&smark);
+ arglist.lastp = &arglist.list;
+ line_number = n->ncase.lineno;
+ expandarg(n->ncase.expr, &arglist, EXP_TILDE);
+ for (cp = n->ncase.cases; cp && evalskip == 0; cp = cp->nclist.next) {
+ for (patp = cp->nclist.pattern; patp; patp = patp->narg.next) {
+ line_number = patp->narg.lineno;
+ if (casematch(patp, arglist.list->text)) {
+ while (cp != NULL && evalskip == 0 &&
+ nflag == 0) {
+ if (cp->type == NCLISTCONT)
+ ncp = cp->nclist.next;
+ else
+ ncp = NULL;
+ line_number = cp->nclist.lineno;
+ evaltree(cp->nclist.body, flags);
+ status = exitstatus;
+ cp = ncp;
+ }
+ goto out;
+ }
+ }
+ }
+ out:
+ exitstatus = status;
+ popstackmark(&smark);
+}
+
+
+
+/*
+ * Kick off a subshell to evaluate a tree.
+ */
+
+STATIC void
+evalsubshell(union node *n, int flags)
+{
+ struct job *jp= NULL;
+ int backgnd = (n->type == NBACKGND);
+
+ expredir(n->nredir.redirect);
+ if (xflag && n->nredir.redirect) {
+ union node *rn;
+
+ outxstr(expandstr(ps4val(), line_number));
+ outxstr("using redirections:");
+ for (rn = n->nredir.redirect; rn; rn = rn->nfile.next)
+ (void) outredir(outx, rn, ' ');
+ outxstr(" do subshell ("/*)*/);
+ if (backgnd)
+ outxstr(/*(*/") &");
+ outxc('\n');
+ flushout(outx);
+ }
+ if ((!backgnd && flags & EV_EXIT && !have_traps()) ||
+ forkshell(jp = makejob(n, 1), n, backgnd?FORK_BG:FORK_FG) == 0) {
+ INTON;
+ if (backgnd)
+ flags &=~ EV_TESTED;
+ redirect(n->nredir.redirect, REDIR_KEEP);
+ evaltree(n->nredir.n, flags | EV_EXIT); /* never returns */
+ } else if (!backgnd) {
+ INTOFF;
+ exitstatus = waitforjob(jp);
+ INTON;
+ } else
+ exitstatus = 0;
+
+ if (!backgnd && xflag && n->nredir.redirect) {
+ outxstr(expandstr(ps4val(), line_number));
+ outxstr(/*(*/") done subshell\n");
+ flushout(outx);
+ }
+}
+
+
+
+/*
+ * Compute the names of the files in a redirection list.
+ */
+
+STATIC void
+expredir(union node *n)
+{
+ union node *redir;
+
+ for (redir = n ; redir ; redir = redir->nfile.next) {
+ struct arglist fn;
+
+ fn.lastp = &fn.list;
+ switch (redir->type) {
+ case NFROMTO:
+ case NFROM:
+ case NTO:
+ case NCLOBBER:
+ case NAPPEND:
+ expandarg(redir->nfile.fname, &fn, EXP_TILDE | EXP_REDIR);
+ redir->nfile.expfname = fn.list->text;
+ break;
+ case NFROMFD:
+ case NTOFD:
+ if (redir->ndup.vname) {
+ expandarg(redir->ndup.vname, &fn, EXP_TILDE | EXP_REDIR);
+ fixredir(redir, fn.list->text, 1);
+ }
+ break;
+ }
+ }
+}
+
+/*
+ * Perform redirections for a compound command, and then do it (and restore)
+ */
+STATIC void
+evalredir(union node *n, int flags)
+{
+ struct jmploc jmploc;
+ struct jmploc * const savehandler = handler;
+ volatile int in_redirect = 1;
+ const char * volatile PS4 = NULL;
+
+ expredir(n->nredir.redirect);
+
+ if (xflag && n->nredir.redirect) {
+ union node *rn;
+
+ outxstr(PS4 = expandstr(ps4val(), line_number));
+ outxstr("using redirections:");
+ for (rn = n->nredir.redirect; rn != NULL; rn = rn->nfile.next)
+ (void) outredir(outx, rn, ' ');
+ outxstr(" do {\n"); /* } */
+ flushout(outx);
+ }
+
+ if (setjmp(jmploc.loc)) {
+ int e;
+
+ handler = savehandler;
+ e = exception;
+ popredir();
+ if (PS4 != NULL) {
+ outxstr(PS4);
+ /* { */ outxstr("} failed\n");
+ flushout(outx);
+ }
+ if (e == EXERROR || e == EXEXEC) {
+ if (in_redirect) {
+ exitstatus = 2;
+ return;
+ }
+ }
+ longjmp(handler->loc, 1);
+ } else {
+ INTOFF;
+ handler = &jmploc;
+ redirect(n->nredir.redirect, REDIR_PUSH | REDIR_KEEP);
+ in_redirect = 0;
+ INTON;
+ evaltree(n->nredir.n, flags);
+ }
+ INTOFF;
+ handler = savehandler;
+ popredir();
+ INTON;
+
+ if (PS4 != NULL) {
+ outxstr(PS4);
+ /* { */ outxstr("} done\n");
+ flushout(outx);
+ }
+}
+
+
+/*
+ * Evaluate a pipeline. All the processes in the pipeline are children
+ * of the process creating the pipeline. (This differs from some versions
+ * of the shell, which make the last process in a pipeline the parent
+ * of all the rest.)
+ */
+
+STATIC void
+evalpipe(union node *n)
+{
+ struct job *jp;
+ struct nodelist *lp;
+ int pipelen;
+ int prevfd;
+ int pip[2];
+
+ CTRACE(DBG_EVAL, ("evalpipe(%p) called\n", n));
+ pipelen = 0;
+ for (lp = n->npipe.cmdlist ; lp ; lp = lp->next)
+ pipelen++;
+ INTOFF;
+ jp = makejob(n, pipelen);
+ prevfd = -1;
+ for (lp = n->npipe.cmdlist ; lp ; lp = lp->next) {
+ prehash(lp->n);
+ pip[1] = -1;
+ if (lp->next) {
+ if (sh_pipe(pip) < 0) {
+ if (prevfd >= 0)
+ close(prevfd);
+ error("Pipe call failed: %s", strerror(errno));
+ }
+ }
+ if (forkshell(jp, lp->n,
+ n->npipe.backgnd ? FORK_BG : FORK_FG) == 0) {
+ INTON;
+ if (prevfd > 0)
+ movefd(prevfd, 0);
+ if (pip[1] >= 0) {
+ close(pip[0]);
+ movefd(pip[1], 1);
+ }
+ evaltree(lp->n, EV_EXIT);
+ }
+ if (prevfd >= 0)
+ close(prevfd);
+ prevfd = pip[0];
+ close(pip[1]);
+ }
+ if (n->npipe.backgnd == 0) {
+ INTOFF;
+ exitstatus = waitforjob(jp);
+ CTRACE(DBG_EVAL, ("evalpipe: job done exit status %d\n",
+ exitstatus));
+ INTON;
+ } else
+ exitstatus = 0;
+ INTON;
+}
+
+
+
+/*
+ * Execute a command inside back quotes. If it's a builtin command, we
+ * want to save its output in a block obtained from malloc. Otherwise
+ * we fork off a subprocess and get the output of the command via a pipe.
+ * Should be called with interrupts off.
+ */
+
+void
+evalbackcmd(union node *n, struct backcmd *result)
+{
+ int pip[2];
+ struct job *jp;
+ struct stackmark smark; /* unnecessary (because we fork) */
+
+ result->fd = -1;
+ result->buf = NULL;
+ result->nleft = 0;
+ result->jp = NULL;
+
+ if (nflag || n == NULL)
+ goto out;
+
+ setstackmark(&smark);
+
+#ifdef notyet
+ /*
+ * For now we disable executing builtins in the same
+ * context as the shell, because we are not keeping
+ * enough state to recover from changes that are
+ * supposed only to affect subshells. eg. echo "`cd /`"
+ */
+ if (n->type == NCMD) {
+ exitstatus = oexitstatus; /* XXX o... no longer exists */
+ evalcommand(n, EV_BACKCMD, result);
+ } else
+#endif
+ {
+ INTOFF;
+ if (sh_pipe(pip) < 0)
+ error("Pipe call failed");
+ jp = makejob(n, 1);
+ if (forkshell(jp, n, FORK_NOJOB) == 0) {
+ FORCEINTON;
+ close(pip[0]);
+ movefd(pip[1], 1);
+ eflag = 0;
+ evaltree(n, EV_EXIT);
+ /* NOTREACHED */
+ }
+ close(pip[1]);
+ result->fd = pip[0];
+ result->jp = jp;
+ INTON;
+ }
+ popstackmark(&smark);
+ out:
+ CTRACE(DBG_EVAL, ("evalbackcmd done: fd=%d buf=0x%x nleft=%d jp=0x%x\n",
+ result->fd, result->buf, result->nleft, result->jp));
+}
+
+const char *
+syspath(void)
+{
+ static char *sys_path = NULL;
+ static int mib[] = {CTL_USER, USER_CS_PATH};
+ static char def_path[] = "PATH=/usr/bin:/bin:/usr/sbin:/sbin";
+ size_t len;
+
+ if (sys_path == NULL) {
+ if (sysctl(mib, 2, 0, &len, 0, 0) != -1 &&
+ (sys_path = ckmalloc(len + 5)) != NULL &&
+ sysctl(mib, 2, sys_path + 5, &len, 0, 0) != -1) {
+ memcpy(sys_path, "PATH=", 5);
+ } else {
+ ckfree(sys_path);
+ /* something to keep things happy */
+ sys_path = def_path;
+ }
+ }
+ return sys_path;
+}
+
+static int
+parse_command_args(int argc, char **argv, int *use_syspath)
+{
+ int sv_argc = argc;
+ char *cp, c;
+
+ *use_syspath = 0;
+
+ for (;;) {
+ argv++;
+ if (--argc == 0)
+ break;
+ cp = *argv;
+ if (*cp++ != '-')
+ break;
+ if (*cp == '-' && cp[1] == 0) {
+ argv++;
+ argc--;
+ break;
+ }
+ while ((c = *cp++)) {
+ switch (c) {
+ case 'p':
+ *use_syspath = 1;
+ break;
+ default:
+ /* run 'typecmd' for other options */
+ return 0;
+ }
+ }
+ }
+ return sv_argc - argc;
+}
+
+int vforked = 0;
+extern char *trap[];
+
+/*
+ * Execute a simple command.
+ */
+
+STATIC void
+evalcommand(union node *cmd, int flgs, struct backcmd *backcmd)
+{
+ struct stackmark smark;
+ union node *argp;
+ struct arglist arglist;
+ struct arglist varlist;
+ volatile int flags = flgs;
+ char ** volatile argv;
+ volatile int argc;
+ char **envp;
+ int varflag;
+ struct strlist *sp;
+ volatile int mode;
+ int pip[2];
+ struct cmdentry cmdentry;
+ struct job * volatile jp;
+ struct jmploc jmploc;
+ struct jmploc *volatile savehandler = NULL;
+ const char *volatile savecmdname;
+ volatile struct shparam saveparam;
+ struct localvar *volatile savelocalvars;
+ struct parsefile *volatile savetopfile;
+ volatile int e;
+ char * volatile lastarg;
+ const char * volatile path = pathval();
+ volatile int temp_path;
+ const int savefuncline = funclinebase;
+ const int savefuncabs = funclineabs;
+ volatile int cmd_flags = 0;
+
+ vforked = 0;
+ /* First expand the arguments. */
+ CTRACE(DBG_EVAL, ("evalcommand(%p, %d) called [%s]\n", cmd, flags,
+ cmd->ncmd.args ? cmd->ncmd.args->narg.text : ""));
+ setstackmark(&smark);
+ back_exitstatus = 0;
+
+ line_number = cmd->ncmd.lineno;
+
+ arglist.lastp = &arglist.list;
+ varflag = 1;
+ /* Expand arguments, ignoring the initial 'name=value' ones */
+ for (argp = cmd->ncmd.args ; argp ; argp = argp->narg.next) {
+ if (varflag && isassignment(argp->narg.text))
+ continue;
+ varflag = 0;
+ line_number = argp->narg.lineno;
+ expandarg(argp, &arglist, EXP_FULL | EXP_TILDE);
+ }
+ *arglist.lastp = NULL;
+
+ expredir(cmd->ncmd.redirect);
+
+ /* Now do the initial 'name=value' ones we skipped above */
+ varlist.lastp = &varlist.list;
+ for (argp = cmd->ncmd.args ; argp ; argp = argp->narg.next) {
+ line_number = argp->narg.lineno;
+ if (!isassignment(argp->narg.text))
+ break;
+ expandarg(argp, &varlist, EXP_VARTILDE);
+ }
+ *varlist.lastp = NULL;
+
+ argc = 0;
+ for (sp = arglist.list ; sp ; sp = sp->next)
+ argc++;
+ argv = stalloc(sizeof (char *) * (argc + 1));
+
+ for (sp = arglist.list ; sp ; sp = sp->next) {
+ VTRACE(DBG_EVAL, ("evalcommand arg: %s\n", sp->text));
+ *argv++ = sp->text;
+ }
+ *argv = NULL;
+ lastarg = NULL;
+ if (iflag && funcnest == 0 && argc > 0)
+ lastarg = argv[-1];
+ argv -= argc;
+
+ /* Print the command if xflag is set. */
+ if (xflag) {
+ char sep = 0;
+ union node *rn;
+
+ outxstr(expandstr(ps4val(), line_number));
+ for (sp = varlist.list ; sp ; sp = sp->next) {
+ char *p;
+
+ if (sep != 0)
+ outxc(sep);
+
+ /*
+ * The "var=" part should not be quoted, regardless
+ * of the value, or it would not represent an
+ * assignment, but rather a command
+ */
+ p = strchr(sp->text, '=');
+ if (p != NULL) {
+ *p = '\0'; /*XXX*/
+ outxshstr(sp->text);
+ outxc('=');
+ *p++ = '='; /*XXX*/
+ } else
+ p = sp->text;
+ outxshstr(p);
+ sep = ' ';
+ }
+ for (sp = arglist.list ; sp ; sp = sp->next) {
+ if (sep != 0)
+ outxc(sep);
+ outxshstr(sp->text);
+ sep = ' ';
+ }
+ for (rn = cmd->ncmd.redirect; rn; rn = rn->nfile.next)
+ if (outredir(outx, rn, sep))
+ sep = ' ';
+ outxc('\n');
+ flushout(outx);
+ }
+
+ /* Now locate the command. */
+ if (argc == 0) {
+ /*
+ * the empty command begins as a normal builtin, and
+ * remains that way while redirects are processed, then
+ * will become special before we get to doing the
+ * var assigns.
+ */
+ cmdentry.cmdtype = CMDBUILTIN;
+ cmdentry.u.bltin = bltincmd;
+ } else {
+ static const char PATH[] = "PATH=";
+
+ /*
+ * Modify the command lookup path, if a PATH= assignment
+ * is present
+ */
+ for (sp = varlist.list; sp; sp = sp->next)
+ if (strncmp(sp->text, PATH, sizeof(PATH) - 1) == 0)
+ path = sp->text + sizeof(PATH) - 1;
+
+ do {
+ int argsused, use_syspath;
+
+ find_command(argv[0], &cmdentry, cmd_flags, path);
+#if 0
+ /*
+ * This short circuits all of the processing that
+ * should be done (including processing the
+ * redirects), so just don't ...
+ *
+ * (eventually this whole #if'd block will vanish)
+ */
+ if (cmdentry.cmdtype == CMDUNKNOWN) {
+ exitstatus = 127;
+ flushout(&errout);
+ goto out;
+ }
+#endif
+
+ /* implement the 'command' builtin here */
+ if (cmdentry.cmdtype != CMDBUILTIN ||
+ cmdentry.u.bltin != bltincmd)
+ break;
+ cmd_flags |= DO_NOFUNC;
+ argsused = parse_command_args(argc, argv, &use_syspath);
+ if (argsused == 0) {
+ /* use 'type' builtin to display info */
+ cmdentry.u.bltin = typecmd;
+ break;
+ }
+ argc -= argsused;
+ argv += argsused;
+ if (use_syspath)
+ path = syspath() + 5;
+ } while (argc != 0);
+ if (cmdentry.cmdtype == CMDSPLBLTIN && cmd_flags & DO_NOFUNC)
+ /* posix mandates that 'command <splbltin>' act as if
+ <splbltin> was a normal builtin */
+ cmdentry.cmdtype = CMDBUILTIN;
+ }
+
+ /*
+ * When traps are invalid, we permit the following:
+ * trap
+ * command trap
+ * eval trap
+ * command eval trap
+ * eval command trap
+ * without zapping the traps completely, in all other cases we do.
+ *
+ * The test here permits eval "anything" but when evalstring() comes
+ * back here again, the "anything" will be validated.
+ * This means we can actually do:
+ * eval eval eval command eval eval command trap
+ * as long as we end up with just "trap"
+ *
+ * We permit "command" by allowing CMDBUILTIN as well as CMDSPLBLTIN
+ *
+ * trapcmd() takes care of doing free_traps() if it is needed there.
+ */
+ if (traps_invalid &&
+ ((cmdentry.cmdtype!=CMDSPLBLTIN && cmdentry.cmdtype!=CMDBUILTIN) ||
+ (cmdentry.u.bltin != trapcmd && cmdentry.u.bltin != evalcmd)))
+ free_traps();
+
+ /* Fork off a child process if necessary. */
+ if (cmd->ncmd.backgnd || (have_traps() && (flags & EV_EXIT) != 0)
+ || ((cmdentry.cmdtype == CMDNORMAL || cmdentry.cmdtype == CMDUNKNOWN)
+ && (flags & EV_EXIT) == 0)
+ || ((flags & EV_BACKCMD) != 0 &&
+ ((cmdentry.cmdtype != CMDBUILTIN && cmdentry.cmdtype != CMDSPLBLTIN)
+ || cmdentry.u.bltin == dotcmd
+ || cmdentry.u.bltin == evalcmd))) {
+ INTOFF;
+ jp = makejob(cmd, 1);
+ mode = cmd->ncmd.backgnd;
+ if (flags & EV_BACKCMD) {
+ mode = FORK_NOJOB;
+ if (sh_pipe(pip) < 0)
+ error("Pipe call failed");
+ }
+#ifdef DO_SHAREDVFORK
+ /* It is essential that if DO_SHAREDVFORK is defined that the
+ * child's address space is actually shared with the parent as
+ * we rely on this.
+ */
+ if (usefork == 0 && cmdentry.cmdtype == CMDNORMAL) {
+ pid_t pid;
+ int serrno;
+
+ savelocalvars = localvars;
+ localvars = NULL;
+ vforked = 1;
+ VFORK_BLOCK
+ switch (pid = vfork()) {
+ case -1:
+ serrno = errno;
+ VTRACE(DBG_EVAL, ("vfork() failed, errno=%d\n",
+ serrno));
+ INTON;
+ error("Cannot vfork (%s)", strerror(serrno));
+ break;
+ case 0:
+ /* Make sure that exceptions only unwind to
+ * after the vfork(2)
+ */
+ SHELL_FORKED();
+ if (setjmp(jmploc.loc)) {
+ if (exception == EXSHELLPROC) {
+ /*
+ * We can't progress with the
+ * vfork, so, set vforked = 2
+ * so the parent knows,
+ * and _exit();
+ */
+ vforked = 2;
+ _exit(0);
+ } else {
+ _exit(exception == EXEXIT ?
+ exitstatus : exerrno);
+ }
+ }
+ savehandler = handler;
+ handler = &jmploc;
+ listmklocal(varlist.list,
+ VDOEXPORT | VEXPORT | VNOFUNC);
+ forkchild(jp, cmd, mode, vforked);
+ break;
+ default:
+ VFORK_UNDO();
+ /* restore from vfork(2) */
+ handler = savehandler;
+ poplocalvars();
+ localvars = savelocalvars;
+ if (vforked == 2) {
+ vforked = 0;
+
+ (void)waitpid(pid, NULL, 0);
+ /*
+ * We need to progress in a
+ * normal fork fashion
+ */
+ goto normal_fork;
+ }
+ /*
+ * Here the child has left home,
+ * getting on with its life, so
+ * so must we...
+ */
+ vforked = 0;
+ forkparent(jp, cmd, mode, pid);
+ goto parent;
+ }
+ VFORK_END
+ } else {
+ normal_fork:
+#endif
+ if (forkshell(jp, cmd, mode) != 0)
+ goto parent; /* at end of routine */
+ flags |= EV_EXIT;
+ FORCEINTON;
+#ifdef DO_SHAREDVFORK
+ }
+#endif
+ if (flags & EV_BACKCMD) {
+ if (!vforked) {
+ FORCEINTON;
+ }
+ close(pip[0]);
+ movefd(pip[1], 1);
+ }
+ flags |= EV_EXIT;
+ }
+
+ /* This is the child process if a fork occurred. */
+ /* Execute the command. */
+ switch (cmdentry.cmdtype) {
+ volatile int saved;
+
+ case CMDFUNCTION:
+ VXTRACE(DBG_EVAL, ("Shell function%s: ",vforked?" VF":""),
+ trargs(argv));
+ redirect(cmd->ncmd.redirect, saved =
+ !(flags & EV_EXIT) || have_traps() ? REDIR_PUSH : 0);
+ saveparam = shellparam;
+ shellparam.malloc = 0;
+ shellparam.reset = 1;
+ shellparam.nparam = argc - 1;
+ shellparam.p = argv + 1;
+ shellparam.optnext = NULL;
+ INTOFF;
+ savelocalvars = localvars;
+ localvars = NULL;
+ reffunc(cmdentry.u.func);
+ INTON;
+ if (setjmp(jmploc.loc)) {
+ if (exception == EXSHELLPROC) {
+ freeparam((volatile struct shparam *)
+ &saveparam);
+ } else {
+ freeparam(&shellparam);
+ shellparam = saveparam;
+ }
+ if (saved)
+ popredir();;
+ unreffunc(cmdentry.u.func);
+ poplocalvars();
+ localvars = savelocalvars;
+ funclinebase = savefuncline;
+ funclineabs = savefuncabs;
+ handler = savehandler;
+ longjmp(handler->loc, 1);
+ }
+ savehandler = handler;
+ handler = &jmploc;
+ if (cmdentry.u.func) {
+ if (cmdentry.lno_frel)
+ funclinebase = cmdentry.lineno - 1;
+ else
+ funclinebase = 0;
+ funclineabs = cmdentry.lineno;
+
+ VTRACE(DBG_EVAL,
+ ("function: node: %d '%s' # %d%s; funclinebase=%d\n",
+ getfuncnode(cmdentry.u.func)->type,
+ NODETYPENAME(getfuncnode(cmdentry.u.func)->type),
+ cmdentry.lineno, cmdentry.lno_frel?" (=1)":"",
+ funclinebase));
+ }
+ listmklocal(varlist.list, VDOEXPORT | VEXPORT);
+ /* stop shell blowing its stack */
+ if (++funcnest > 1000)
+ error("too many nested function calls");
+ evaltree(getfuncnode(cmdentry.u.func),
+ flags & (EV_TESTED|EV_EXIT));
+ funcnest--;
+ INTOFF;
+ unreffunc(cmdentry.u.func);
+ poplocalvars();
+ localvars = savelocalvars;
+ funclinebase = savefuncline;
+ funclineabs = savefuncabs;
+ freeparam(&shellparam);
+ shellparam = saveparam;
+ handler = savehandler;
+ if (saved)
+ popredir();
+ INTON;
+ if (evalskip == SKIPFUNC) {
+ evalskip = SKIPNONE;
+ skipcount = 0;
+ }
+ if (flags & EV_EXIT)
+ exitshell(exitstatus);
+ break;
+
+ case CMDSPLBLTIN:
+ VTRACE(DBG_EVAL, ("special "));
+ case CMDBUILTIN:
+ VXTRACE(DBG_EVAL, ("builtin command [%d]%s: ", argc,
+ vforked ? " VF" : ""), trargs(argv));
+ mode = (cmdentry.u.bltin == execcmd) ? 0 : REDIR_PUSH;
+ if (flags == EV_BACKCMD) {
+ memout.nleft = 0;
+ memout.nextc = memout.buf;
+ memout.bufsize = 64;
+ mode |= REDIR_BACKQ;
+ }
+ e = -1;
+ savecmdname = commandname;
+ savetopfile = getcurrentfile();
+ savehandler = handler;
+ temp_path = 0;
+ if (!setjmp(jmploc.loc)) {
+ handler = &jmploc;
+
+ /*
+ * We need to ensure the command hash table isn't
+ * corrupted by temporary PATH assignments.
+ * However we must ensure the 'local' command works!
+ */
+ if (path != pathval() && (cmdentry.u.bltin == hashcmd ||
+ cmdentry.u.bltin == typecmd)) {
+ savelocalvars = localvars;
+ localvars = 0;
+ temp_path = 1;
+ mklocal(path - 5 /* PATH= */, 0);
+ }
+ redirect(cmd->ncmd.redirect, mode);
+
+ /*
+ * the empty command is regarded as a normal
+ * builtin for the purposes of redirects, but
+ * is a special builtin for var assigns.
+ * (unless we are the "command" command.)
+ */
+ if (argc == 0 && !(cmd_flags & DO_NOFUNC))
+ cmdentry.cmdtype = CMDSPLBLTIN;
+
+ /* exec is a special builtin, but needs this list... */
+ cmdenviron = varlist.list;
+ /* we must check 'readonly' flag for all builtins */
+ listsetvar(varlist.list,
+ cmdentry.cmdtype == CMDSPLBLTIN ? 0 : VNOSET);
+ commandname = argv[0];
+ /* initialize nextopt */
+ argptr = argv + 1;
+ optptr = NULL;
+ /* and getopt */
+ optreset = 1;
+ optind = 1;
+ builtin_flags = flags;
+ exitstatus = cmdentry.u.bltin(argc, argv);
+ } else {
+ e = exception;
+ if (e == EXINT)
+ exitstatus = SIGINT + 128;
+ else if (e == EXEXEC)
+ exitstatus = exerrno;
+ else if (e != EXEXIT)
+ exitstatus = 2;
+ }
+ handler = savehandler;
+ flushall();
+ out1 = &output;
+ out2 = &errout;
+ freestdout();
+ if (temp_path) {
+ poplocalvars();
+ localvars = savelocalvars;
+ }
+ cmdenviron = NULL;
+ if (e != EXSHELLPROC) {
+ commandname = savecmdname;
+ if (flags & EV_EXIT)
+ exitshell(exitstatus);
+ }
+ if (e != -1) {
+ if ((e != EXERROR && e != EXEXEC)
+ || cmdentry.cmdtype == CMDSPLBLTIN)
+ exraise(e);
+ popfilesupto(savetopfile);
+ FORCEINTON;
+ }
+ if (cmdentry.u.bltin != execcmd)
+ popredir();
+ if (flags == EV_BACKCMD) {
+ backcmd->buf = memout.buf;
+ backcmd->nleft = memout.nextc - memout.buf;
+ memout.buf = NULL;
+ }
+ break;
+
+ default:
+ VXTRACE(DBG_EVAL, ("normal command%s: ", vforked?" VF":""),
+ trargs(argv));
+ redirect(cmd->ncmd.redirect,
+ (vforked ? REDIR_VFORK : 0) | REDIR_KEEP);
+ if (!vforked)
+ for (sp = varlist.list ; sp ; sp = sp->next)
+ setvareq(sp->text, VDOEXPORT|VEXPORT|VSTACK);
+ envp = environment();
+ shellexec(argv, envp, path, cmdentry.u.index, vforked);
+ break;
+ }
+ goto out;
+
+ parent: /* parent process gets here (if we forked) */
+
+ exitstatus = 0; /* if not altered just below */
+ if (mode == FORK_FG) { /* argument to fork */
+ exitstatus = waitforjob(jp);
+ } else if (mode == FORK_NOJOB) {
+ backcmd->fd = pip[0];
+ close(pip[1]);
+ backcmd->jp = jp;
+ }
+ FORCEINTON;
+
+ out:
+ if (lastarg)
+ /* implement $_ for whatever use that really is */
+ (void) setvarsafe("_", lastarg, VNOERROR);
+ popstackmark(&smark);
+}
+
+
+/*
+ * Search for a command. This is called before we fork so that the
+ * location of the command will be available in the parent as well as
+ * the child. The check for "goodname" is an overly conservative
+ * check that the name will not be subject to expansion.
+ */
+
+STATIC void
+prehash(union node *n)
+{
+ struct cmdentry entry;
+
+ if (n && n->type == NCMD && n->ncmd.args)
+ if (goodname(n->ncmd.args->narg.text))
+ find_command(n->ncmd.args->narg.text, &entry, 0,
+ pathval());
+}
+
+int
+in_function(void)
+{
+ return funcnest;
+}
+
+enum skipstate
+current_skipstate(void)
+{
+ return evalskip;
+}
+
+void
+save_skipstate(struct skipsave *p)
+{
+ *p = s_k_i_p;
+}
+
+void
+restore_skipstate(const struct skipsave *p)
+{
+ s_k_i_p = *p;
+}
+
+void
+stop_skipping(void)
+{
+ evalskip = SKIPNONE;
+ skipcount = 0;
+}
+
+/*
+ * Builtin commands. Builtin commands whose functions are closely
+ * tied to evaluation are implemented here.
+ */
+
+/*
+ * No command given.
+ */
+
+int
+bltincmd(int argc, char **argv)
+{
+ /*
+ * Preserve exitstatus of a previous possible redirection
+ * as POSIX mandates
+ */
+ return back_exitstatus;
+}
+
+
+/*
+ * Handle break and continue commands. Break, continue, and return are
+ * all handled by setting the evalskip flag. The evaluation routines
+ * above all check this flag, and if it is set they start skipping
+ * commands rather than executing them. The variable skipcount is
+ * the number of loops to break/continue, or the number of function
+ * levels to return. (The latter is always 1.) It should probably
+ * be an error to break out of more loops than exist, but it isn't
+ * in the standard shell so we don't make it one here.
+ */
+
+int
+breakcmd(int argc, char **argv)
+{
+ int n = argc > 1 ? number(argv[1]) : 1;
+
+ if (n <= 0)
+ error("invalid count: %d", n);
+ if (n > loopnest)
+ n = loopnest;
+ if (n > 0) {
+ evalskip = (**argv == 'c')? SKIPCONT : SKIPBREAK;
+ skipcount = n;
+ }
+ return 0;
+}
+
+int
+dotcmd(int argc, char **argv)
+{
+ exitstatus = 0;
+
+ if (argc >= 2) { /* That's what SVR2 does */
+ char *fullname;
+ /*
+ * dot_funcnest needs to be 0 when not in a dotcmd, so it
+ * cannot be restored with (funcnest + 1).
+ */
+ int dot_funcnest_old;
+ struct stackmark smark;
+
+ setstackmark(&smark);
+ fullname = find_dot_file(argv[1]);
+ setinputfile(fullname, 1);
+ commandname = fullname;
+ dot_funcnest_old = dot_funcnest;
+ dot_funcnest = funcnest + 1;
+ cmdloop(0);
+ dot_funcnest = dot_funcnest_old;
+ popfile();
+ popstackmark(&smark);
+ }
+ return exitstatus;
+}
+
+/*
+ * Take commands from a file. To be compatible we should do a path
+ * search for the file, which is necessary to find sub-commands.
+ */
+
+STATIC char *
+find_dot_file(char *basename)
+{
+ char *fullname;
+ const char *path = pathval();
+ struct stat statb;
+
+ /* don't try this for absolute or relative paths */
+ if (strchr(basename, '/')) {
+ if (stat(basename, &statb) == 0) {
+ if (S_ISDIR(statb.st_mode))
+ error("%s: is a directory", basename);
+ if (S_ISBLK(statb.st_mode))
+ error("%s: is a block device", basename);
+ return basename;
+ }
+ } else while ((fullname = padvance(&path, basename, 1)) != NULL) {
+ if ((stat(fullname, &statb) == 0)) {
+ /* weird format is to ease future code... */
+ if (S_ISDIR(statb.st_mode) || S_ISBLK(statb.st_mode))
+ ;
+#if notyet
+ else if (unreadable()) {
+ /*
+ * testing this via st_mode is ugly to get
+ * correct (and would ignore ACLs).
+ * better way is just to open the file.
+ * But doing that here would (currently)
+ * mean opening the file twice, which
+ * might not be safe. So, defer this
+ * test until code is restructures so
+ * we can return a fd. Then we also
+ * get to fix the mem leak just below...
+ */
+ }
+#endif
+ else {
+ /*
+ * Don't bother freeing here, since
+ * it will be freed by the caller.
+ * XXX no it won't - a bug for later.
+ */
+ return fullname;
+ }
+ }
+ stunalloc(fullname);
+ }
+
+ /* not found in the PATH */
+ error("%s: not found", basename);
+ /* NOTREACHED */
+}
+
+
+
+/*
+ * The return command.
+ *
+ * Quoth the POSIX standard:
+ * The return utility shall cause the shell to stop executing the current
+ * function or dot script. If the shell is not currently executing
+ * a function or dot script, the results are unspecified.
+ *
+ * As for the unspecified part, there seems to be no de-facto standard: bash
+ * ignores the return with a warning, zsh ignores the return in interactive
+ * mode but seems to liken it to exit in a script. (checked May 2014)
+ *
+ * We choose to silently ignore the return. Older versions of this shell
+ * set evalskip to SKIPFILE causing the shell to (indirectly) exit. This
+ * had at least the problem of circumventing the check for stopped jobs,
+ * which would occur for exit or ^D.
+ */
+
+int
+returncmd(int argc, char **argv)
+{
+ int ret = argc > 1 ? number(argv[1]) : exitstatus;
+
+ if ((dot_funcnest == 0 && funcnest)
+ || (dot_funcnest > 0 && funcnest - (dot_funcnest - 1) > 0)) {
+ evalskip = SKIPFUNC;
+ skipcount = 1;
+ } else if (dot_funcnest > 0) {
+ evalskip = SKIPFILE;
+ skipcount = 1;
+ } else {
+ /* XXX: should a warning be issued? */
+ ret = 0;
+ }
+
+ return ret;
+}
+
+
+int
+falsecmd(int argc, char **argv)
+{
+ return 1;
+}
+
+
+int
+truecmd(int argc, char **argv)
+{
+ return 0;
+}
+
+
+int
+execcmd(int argc, char **argv)
+{
+ if (argc > 1) {
+ struct strlist *sp;
+
+ iflag = 0; /* exit on error */
+ mflag = 0;
+ optschanged();
+ for (sp = cmdenviron; sp; sp = sp->next)
+ setvareq(sp->text, VDOEXPORT|VEXPORT|VSTACK);
+ shellexec(argv + 1, environment(), pathval(), 0, 0);
+ }
+ return 0;
+}
+
+static int
+conv_time(clock_t ticks, char *seconds, size_t l)
+{
+ static clock_t tpm = 0;
+ clock_t mins;
+ int i;
+
+ if (!tpm)
+ tpm = sysconf(_SC_CLK_TCK) * 60;
+
+ mins = ticks / tpm;
+ snprintf(seconds, l, "%.4f", (ticks - mins * tpm) * 60.0 / tpm );
+
+ if (seconds[0] == '6' && seconds[1] == '0') {
+ /* 59.99995 got rounded up... */
+ mins++;
+ strlcpy(seconds, "0.0", l);
+ return mins;
+ }
+
+ /* suppress trailing zeros */
+ i = strlen(seconds) - 1;
+ for (; seconds[i] == '0' && seconds[i - 1] != '.'; i--)
+ seconds[i] = 0;
+ return mins;
+}
+
+int
+timescmd(int argc, char **argv)
+{
+ struct tms tms;
+ int u, s, cu, cs;
+ char us[8], ss[8], cus[8], css[8];
+
+ nextopt("");
+
+ times(&tms);
+
+ u = conv_time(tms.tms_utime, us, sizeof(us));
+ s = conv_time(tms.tms_stime, ss, sizeof(ss));
+ cu = conv_time(tms.tms_cutime, cus, sizeof(cus));
+ cs = conv_time(tms.tms_cstime, css, sizeof(css));
+
+ outfmt(out1, "%dm%ss %dm%ss\n%dm%ss %dm%ss\n",
+ u, us, s, ss, cu, cus, cs, css);
+
+ return 0;
+}
diff --git a/bin/sh/eval.h b/bin/sh/eval.h
new file mode 100644
index 0000000..b18dd42
--- /dev/null
+++ b/bin/sh/eval.h
@@ -0,0 +1,87 @@
+/* $NetBSD: eval.h,v 1.22 2018/12/03 06:43:19 kre Exp $ */
+
+/*-
+ * Copyright (c) 1991, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Kenneth Almquist.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * @(#)eval.h 8.2 (Berkeley) 5/4/95
+ */
+
+extern const char *commandname; /* currently executing command */
+extern int exitstatus; /* exit status of last command */
+extern int back_exitstatus; /* exit status of backquoted command */
+extern struct strlist *cmdenviron; /* environment for builtin command */
+
+
+struct backcmd { /* result of evalbackcmd */
+ int fd; /* file descriptor to read from */
+ char *buf; /* buffer */
+ int nleft; /* number of chars in buffer */
+ struct job *jp; /* job structure for command */
+};
+
+void evalstring(char *, int);
+union node; /* BLETCH for ansi C */
+void evaltree(union node *, int);
+void evalbackcmd(union node *, struct backcmd *);
+
+const char *syspath(void);
+
+/* in_function returns nonzero if we are currently evaluating a function */
+int in_function(void); /* return non-zero, if evaluating a function */
+
+/* reasons for skipping commands (see comment on breakcmd routine) */
+enum skipstate {
+ SKIPNONE = 0, /* not skipping */
+ SKIPBREAK, /* break */
+ SKIPCONT, /* continue */
+ SKIPFUNC, /* return in a function */
+ SKIPFILE /* return in a dot command */
+};
+
+struct skipsave {
+ enum skipstate state; /* skipping or not */
+ int count; /* when break or continue, how many */
+};
+
+enum skipstate current_skipstate(void);
+void save_skipstate(struct skipsave *);
+void restore_skipstate(const struct skipsave *);
+void stop_skipping(void); /* reset internal skipping state to SKIPNONE */
+
+/*
+ * Only for use by reset() in init.c!
+ */
+void reset_eval(void);
+
+/* flags in argument to evaltree */
+#define EV_EXIT 0x01 /* exit after evaluating tree */
+#define EV_TESTED 0x02 /* exit status is checked; ignore -e flag */
+#define EV_BACKCMD 0x04 /* command executing within back quotes */
diff --git a/bin/sh/exec.c b/bin/sh/exec.c
new file mode 100644
index 0000000..674beb8
--- /dev/null
+++ b/bin/sh/exec.c
@@ -0,0 +1,1183 @@
+/* $NetBSD: exec.c,v 1.53 2018/07/25 14:42:50 kre Exp $ */
+
+/*-
+ * Copyright (c) 1991, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Kenneth Almquist.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+#ifndef lint
+#if 0
+static char sccsid[] = "@(#)exec.c 8.4 (Berkeley) 6/8/95";
+#else
+__RCSID("$NetBSD: exec.c,v 1.53 2018/07/25 14:42:50 kre Exp $");
+#endif
+#endif /* not lint */
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/wait.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+/*
+ * When commands are first encountered, they are entered in a hash table.
+ * This ensures that a full path search will not have to be done for them
+ * on each invocation.
+ *
+ * We should investigate converting to a linear search, even though that
+ * would make the command name "hash" a misnomer.
+ */
+
+#include "shell.h"
+#include "main.h"
+#include "nodes.h"
+#include "parser.h"
+#include "redir.h"
+#include "eval.h"
+#include "exec.h"
+#include "builtins.h"
+#include "var.h"
+#include "options.h"
+#include "input.h"
+#include "output.h"
+#include "syntax.h"
+#include "memalloc.h"
+#include "error.h"
+#include "init.h"
+#include "mystring.h"
+#include "show.h"
+#include "jobs.h"
+#include "alias.h"
+
+
+#define CMDTABLESIZE 31 /* should be prime */
+#define ARB 1 /* actual size determined at run time */
+
+
+
+struct tblentry {
+ struct tblentry *next; /* next entry in hash chain */
+ union param param; /* definition of builtin function */
+ short cmdtype; /* index identifying command */
+ char rehash; /* if set, cd done since entry created */
+ char fn_ln1; /* for functions, LINENO from 1 */
+ int lineno; /* for functions abs LINENO of definition */
+ char cmdname[ARB]; /* name of command */
+};
+
+
+STATIC struct tblentry *cmdtable[CMDTABLESIZE];
+STATIC int builtinloc = -1; /* index in path of %builtin, or -1 */
+int exerrno = 0; /* Last exec error */
+
+
+STATIC void tryexec(char *, char **, char **, int);
+STATIC void printentry(struct tblentry *, int);
+STATIC void addcmdentry(char *, struct cmdentry *);
+STATIC void clearcmdentry(int);
+STATIC struct tblentry *cmdlookup(const char *, int);
+STATIC void delete_cmd_entry(void);
+
+#ifndef BSD
+STATIC void execinterp(char **, char **);
+#endif
+
+
+extern const char *const parsekwd[];
+
+/*
+ * Exec a program. Never returns. If you change this routine, you may
+ * have to change the find_command routine as well.
+ */
+
+void
+shellexec(char **argv, char **envp, const char *path, int idx, int vforked)
+{
+ char *cmdname;
+ int e;
+
+ if (strchr(argv[0], '/') != NULL) {
+ tryexec(argv[0], argv, envp, vforked);
+ e = errno;
+ } else {
+ e = ENOENT;
+ while ((cmdname = padvance(&path, argv[0], 1)) != NULL) {
+ if (--idx < 0 && pathopt == NULL) {
+ tryexec(cmdname, argv, envp, vforked);
+ if (errno != ENOENT && errno != ENOTDIR)
+ e = errno;
+ }
+ stunalloc(cmdname);
+ }
+ }
+
+ /* Map to POSIX errors */
+ switch (e) {
+ case EACCES: /* particularly this (unless no search perm) */
+ /*
+ * should perhaps check if this EACCES is an exec()
+ * EACESS or a namei() EACESS - the latter should be 127
+ * - but not today
+ */
+ case EINVAL: /* also explicitly these */
+ case ENOEXEC:
+ default: /* and anything else */
+ exerrno = 126;
+ break;
+
+ case ENOENT: /* these are the "pathname lookup failed" errors */
+ case ELOOP:
+ case ENOTDIR:
+ case ENAMETOOLONG:
+ exerrno = 127;
+ break;
+ }
+ CTRACE(DBG_ERRS|DBG_CMDS|DBG_EVAL,
+ ("shellexec failed for %s, errno %d, vforked %d, suppressint %d\n",
+ argv[0], e, vforked, suppressint));
+ exerror(EXEXEC, "%s: %s", argv[0], errmsg(e, E_EXEC));
+ /* NOTREACHED */
+}
+
+
+STATIC void
+tryexec(char *cmd, char **argv, char **envp, int vforked)
+{
+ int e;
+#ifndef BSD
+ char *p;
+#endif
+
+#ifdef SYSV
+ do {
+ execve(cmd, argv, envp);
+ } while (errno == EINTR);
+#else
+ execve(cmd, argv, envp);
+#endif
+ e = errno;
+ if (e == ENOEXEC) {
+ if (vforked) {
+ /* We are currently vfork(2)ed, so raise an
+ * exception, and evalcommand will try again
+ * with a normal fork(2).
+ */
+ exraise(EXSHELLPROC);
+ }
+#ifdef DEBUG
+ VTRACE(DBG_CMDS, ("execve(cmd=%s) returned ENOEXEC\n", cmd));
+#endif
+ initshellproc();
+ setinputfile(cmd, 0);
+ commandname = arg0 = savestr(argv[0]);
+#ifndef BSD
+ pgetc(); pungetc(); /* fill up input buffer */
+ p = parsenextc;
+ if (parsenleft > 2 && p[0] == '#' && p[1] == '!') {
+ argv[0] = cmd;
+ execinterp(argv, envp);
+ }
+#endif
+ setparam(argv + 1);
+ exraise(EXSHELLPROC);
+ }
+ errno = e;
+}
+
+
+#ifndef BSD
+/*
+ * Execute an interpreter introduced by "#!", for systems where this
+ * feature has not been built into the kernel. If the interpreter is
+ * the shell, return (effectively ignoring the "#!"). If the execution
+ * of the interpreter fails, exit.
+ *
+ * This code peeks inside the input buffer in order to avoid actually
+ * reading any input. It would benefit from a rewrite.
+ */
+
+#define NEWARGS 5
+
+STATIC void
+execinterp(char **argv, char **envp)
+{
+ int n;
+ char *inp;
+ char *outp;
+ char c;
+ char *p;
+ char **ap;
+ char *newargs[NEWARGS];
+ int i;
+ char **ap2;
+ char **new;
+
+ n = parsenleft - 2;
+ inp = parsenextc + 2;
+ ap = newargs;
+ for (;;) {
+ while (--n >= 0 && (*inp == ' ' || *inp == '\t'))
+ inp++;
+ if (n < 0)
+ goto bad;
+ if ((c = *inp++) == '\n')
+ break;
+ if (ap == &newargs[NEWARGS])
+bad: error("Bad #! line");
+ STARTSTACKSTR(outp);
+ do {
+ STPUTC(c, outp);
+ } while (--n >= 0 && (c = *inp++) != ' ' && c != '\t' && c != '\n');
+ STPUTC('\0', outp);
+ n++, inp--;
+ *ap++ = grabstackstr(outp);
+ }
+ if (ap == newargs + 1) { /* if no args, maybe no exec is needed */
+ p = newargs[0];
+ for (;;) {
+ if (equal(p, "sh") || equal(p, "ash")) {
+ return;
+ }
+ while (*p != '/') {
+ if (*p == '\0')
+ goto break2;
+ p++;
+ }
+ p++;
+ }
+break2:;
+ }
+ i = (char *)ap - (char *)newargs; /* size in bytes */
+ if (i == 0)
+ error("Bad #! line");
+ for (ap2 = argv ; *ap2++ != NULL ; );
+ new = ckmalloc(i + ((char *)ap2 - (char *)argv));
+ ap = newargs, ap2 = new;
+ while ((i -= sizeof (char **)) >= 0)
+ *ap2++ = *ap++;
+ ap = argv;
+ while (*ap2++ = *ap++);
+ shellexec(new, envp, pathval(), 0);
+ /* NOTREACHED */
+}
+#endif
+
+
+
+/*
+ * Do a path search. The variable path (passed by reference) should be
+ * set to the start of the path before the first call; padvance will update
+ * this value as it proceeds. Successive calls to padvance will return
+ * the possible path expansions in sequence. If an option (indicated by
+ * a percent sign) appears in the path entry then the global variable
+ * pathopt will be set to point to it; otherwise pathopt will be set to
+ * NULL.
+ */
+
+const char *pathopt;
+
+char *
+padvance(const char **path, const char *name, int magic_percent)
+{
+ const char *p;
+ char *q;
+ const char *start;
+ int len;
+
+ if (*path == NULL)
+ return NULL;
+ if (magic_percent)
+ magic_percent = '%';
+
+ start = *path;
+ for (p = start ; *p && *p != ':' && *p != magic_percent ; p++)
+ ;
+ len = p - start + strlen(name) + 2; /* "2" is for '/' and '\0' */
+ while (stackblocksize() < len)
+ growstackblock();
+ q = stackblock();
+ if (p != start) {
+ memcpy(q, start, p - start);
+ q += p - start;
+ if (q[-1] != '/')
+ *q++ = '/';
+ }
+ strcpy(q, name);
+ pathopt = NULL;
+ if (*p == magic_percent) {
+ pathopt = ++p;
+ while (*p && *p != ':')
+ p++;
+ }
+ if (*p == ':')
+ *path = p + 1;
+ else
+ *path = NULL;
+ return grabstackstr(q + strlen(name) + 1);
+}
+
+
+/*** Command hashing code ***/
+
+
+int
+hashcmd(int argc, char **argv)
+{
+ struct tblentry **pp;
+ struct tblentry *cmdp;
+ int c;
+ struct cmdentry entry;
+ char *name;
+ int allopt=0, bopt=0, fopt=0, ropt=0, sopt=0, uopt=0, verbose=0;
+
+ while ((c = nextopt("bcfrsuv")) != '\0')
+ switch (c) {
+ case 'b': bopt = 1; break;
+ case 'c': uopt = 1; break; /* c == u */
+ case 'f': fopt = 1; break;
+ case 'r': ropt = 1; break;
+ case 's': sopt = 1; break;
+ case 'u': uopt = 1; break;
+ case 'v': verbose = 1; break;
+ }
+
+ if (ropt)
+ clearcmdentry(0);
+
+ if (bopt == 0 && fopt == 0 && sopt == 0 && uopt == 0)
+ allopt = bopt = fopt = sopt = uopt = 1;
+
+ if (*argptr == NULL) {
+ for (pp = cmdtable ; pp < &cmdtable[CMDTABLESIZE] ; pp++) {
+ for (cmdp = *pp ; cmdp ; cmdp = cmdp->next) {
+ switch (cmdp->cmdtype) {
+ case CMDNORMAL:
+ if (!uopt)
+ continue;
+ break;
+ case CMDBUILTIN:
+ if (!bopt)
+ continue;
+ break;
+ case CMDSPLBLTIN:
+ if (!sopt)
+ continue;
+ break;
+ case CMDFUNCTION:
+ if (!fopt)
+ continue;
+ break;
+ default: /* never happens */
+ continue;
+ }
+ if (!allopt || verbose ||
+ cmdp->cmdtype == CMDNORMAL)
+ printentry(cmdp, verbose);
+ }
+ }
+ return 0;
+ }
+
+ while ((name = *argptr++) != NULL) {
+ if ((cmdp = cmdlookup(name, 0)) != NULL) {
+ switch (cmdp->cmdtype) {
+ case CMDNORMAL:
+ if (!uopt)
+ continue;
+ delete_cmd_entry();
+ break;
+ case CMDBUILTIN:
+ if (!bopt)
+ continue;
+ if (builtinloc >= 0)
+ delete_cmd_entry();
+ break;
+ case CMDSPLBLTIN:
+ if (!sopt)
+ continue;
+ break;
+ case CMDFUNCTION:
+ if (!fopt)
+ continue;
+ break;
+ }
+ }
+ find_command(name, &entry, DO_ERR, pathval());
+ if (verbose) {
+ if (entry.cmdtype != CMDUNKNOWN) { /* if no error msg */
+ cmdp = cmdlookup(name, 0);
+ if (cmdp != NULL)
+ printentry(cmdp, verbose);
+ }
+ flushall();
+ }
+ }
+ return 0;
+}
+
+STATIC void
+printentry(struct tblentry *cmdp, int verbose)
+{
+ int idx;
+ const char *path;
+ char *name;
+
+ switch (cmdp->cmdtype) {
+ case CMDNORMAL:
+ idx = cmdp->param.index;
+ path = pathval();
+ do {
+ name = padvance(&path, cmdp->cmdname, 1);
+ stunalloc(name);
+ } while (--idx >= 0);
+ if (verbose)
+ out1fmt("Command from PATH[%d]: ",
+ cmdp->param.index);
+ out1str(name);
+ break;
+ case CMDSPLBLTIN:
+ if (verbose)
+ out1str("special ");
+ /* FALLTHROUGH */
+ case CMDBUILTIN:
+ if (verbose)
+ out1str("builtin ");
+ out1fmt("%s", cmdp->cmdname);
+ break;
+ case CMDFUNCTION:
+ if (verbose)
+ out1str("function ");
+ out1fmt("%s", cmdp->cmdname);
+ if (verbose) {
+ struct procstat ps;
+
+ INTOFF;
+ commandtext(&ps, getfuncnode(cmdp->param.func));
+ INTON;
+ out1str("() { ");
+ out1str(ps.cmd);
+ out1str("; }");
+ }
+ break;
+ default:
+ error("internal error: %s cmdtype %d",
+ cmdp->cmdname, cmdp->cmdtype);
+ }
+ if (cmdp->rehash)
+ out1c('*');
+ out1c('\n');
+}
+
+
+
+/*
+ * Resolve a command name. If you change this routine, you may have to
+ * change the shellexec routine as well.
+ */
+
+void
+find_command(char *name, struct cmdentry *entry, int act, const char *path)
+{
+ struct tblentry *cmdp, loc_cmd;
+ int idx;
+ int prev;
+ char *fullname;
+ struct stat statb;
+ int e;
+ int (*bltin)(int,char **);
+
+ /* If name contains a slash, don't use PATH or hash table */
+ if (strchr(name, '/') != NULL) {
+ if (act & DO_ABS) {
+ while (stat(name, &statb) < 0) {
+#ifdef SYSV
+ if (errno == EINTR)
+ continue;
+#endif
+ if (errno != ENOENT && errno != ENOTDIR)
+ e = errno;
+ entry->cmdtype = CMDUNKNOWN;
+ entry->u.index = -1;
+ return;
+ }
+ entry->cmdtype = CMDNORMAL;
+ entry->u.index = -1;
+ return;
+ }
+ entry->cmdtype = CMDNORMAL;
+ entry->u.index = 0;
+ return;
+ }
+
+ if (path != pathval())
+ act |= DO_ALTPATH;
+
+ if (act & DO_ALTPATH && strstr(path, "%builtin") != NULL)
+ act |= DO_ALTBLTIN;
+
+ /* If name is in the table, check answer will be ok */
+ if ((cmdp = cmdlookup(name, 0)) != NULL) {
+ do {
+ switch (cmdp->cmdtype) {
+ case CMDNORMAL:
+ if (act & DO_ALTPATH) {
+ cmdp = NULL;
+ continue;
+ }
+ break;
+ case CMDFUNCTION:
+ if (act & DO_NOFUNC) {
+ cmdp = NULL;
+ continue;
+ }
+ break;
+ case CMDBUILTIN:
+ if ((act & DO_ALTBLTIN) || builtinloc >= 0) {
+ cmdp = NULL;
+ continue;
+ }
+ break;
+ }
+ /* if not invalidated by cd, we're done */
+ if (cmdp->rehash == 0)
+ goto success;
+ } while (0);
+ }
+
+ /* If %builtin not in path, check for builtin next */
+ if ((act & DO_ALTPATH ? !(act & DO_ALTBLTIN) : builtinloc < 0) &&
+ (bltin = find_builtin(name)) != 0)
+ goto builtin_success;
+
+ /* We have to search path. */
+ prev = -1; /* where to start */
+ if (cmdp) { /* doing a rehash */
+ if (cmdp->cmdtype == CMDBUILTIN)
+ prev = builtinloc;
+ else
+ prev = cmdp->param.index;
+ }
+
+ e = ENOENT;
+ idx = -1;
+loop:
+ while ((fullname = padvance(&path, name, 1)) != NULL) {
+ stunalloc(fullname);
+ idx++;
+ if (pathopt) {
+ if (prefix("builtin", pathopt)) {
+ if ((bltin = find_builtin(name)) == 0)
+ goto loop;
+ goto builtin_success;
+ } else if (prefix("func", pathopt)) {
+ /* handled below */
+ } else {
+ /* ignore unimplemented options */
+ goto loop;
+ }
+ }
+ /* if rehash, don't redo absolute path names */
+ if (fullname[0] == '/' && idx <= prev) {
+ if (idx < prev)
+ goto loop;
+ VTRACE(DBG_CMDS, ("searchexec \"%s\": no change\n",
+ name));
+ goto success;
+ }
+ while (stat(fullname, &statb) < 0) {
+#ifdef SYSV
+ if (errno == EINTR)
+ continue;
+#endif
+ if (errno != ENOENT && errno != ENOTDIR)
+ e = errno;
+ goto loop;
+ }
+ e = EACCES; /* if we fail, this will be the error */
+ if (!S_ISREG(statb.st_mode))
+ goto loop;
+ if (pathopt) { /* this is a %func directory */
+ char *endname;
+
+ if (act & DO_NOFUNC)
+ goto loop;
+ endname = fullname + strlen(fullname) + 1;
+ grabstackstr(endname);
+ readcmdfile(fullname);
+ if ((cmdp = cmdlookup(name, 0)) == NULL ||
+ cmdp->cmdtype != CMDFUNCTION)
+ error("%s not defined in %s", name, fullname);
+ ungrabstackstr(fullname, endname);
+ goto success;
+ }
+#ifdef notdef
+ /* XXX this code stops root executing stuff, and is buggy
+ if you need a group from the group list. */
+ if (statb.st_uid == geteuid()) {
+ if ((statb.st_mode & 0100) == 0)
+ goto loop;
+ } else if (statb.st_gid == getegid()) {
+ if ((statb.st_mode & 010) == 0)
+ goto loop;
+ } else {
+ if ((statb.st_mode & 01) == 0)
+ goto loop;
+ }
+#endif
+ VTRACE(DBG_CMDS, ("searchexec \"%s\" returns \"%s\"\n", name,
+ fullname));
+ INTOFF;
+ if (act & DO_ALTPATH) {
+ /*
+ * this should be a grabstackstr() but is not needed:
+ * fullname is no longer needed for anything
+ stalloc(strlen(fullname) + 1);
+ */
+ cmdp = &loc_cmd;
+ } else
+ cmdp = cmdlookup(name, 1);
+ cmdp->cmdtype = CMDNORMAL;
+ cmdp->param.index = idx;
+ INTON;
+ goto success;
+ }
+
+ /* We failed. If there was an entry for this command, delete it */
+ if (cmdp)
+ delete_cmd_entry();
+ if (act & DO_ERR)
+ outfmt(out2, "%s: %s\n", name, errmsg(e, E_EXEC));
+ entry->cmdtype = CMDUNKNOWN;
+ return;
+
+builtin_success:
+ INTOFF;
+ if (act & DO_ALTPATH)
+ cmdp = &loc_cmd;
+ else
+ cmdp = cmdlookup(name, 1);
+ if (cmdp->cmdtype == CMDFUNCTION)
+ /* DO_NOFUNC must have been set */
+ cmdp = &loc_cmd;
+ cmdp->cmdtype = CMDBUILTIN;
+ cmdp->param.bltin = bltin;
+ INTON;
+success:
+ if (cmdp) {
+ cmdp->rehash = 0;
+ entry->cmdtype = cmdp->cmdtype;
+ entry->lineno = cmdp->lineno;
+ entry->lno_frel = cmdp->fn_ln1;
+ entry->u = cmdp->param;
+ } else
+ entry->cmdtype = CMDUNKNOWN;
+}
+
+
+
+/*
+ * Search the table of builtin commands.
+ */
+
+int
+(*find_builtin(char *name))(int, char **)
+{
+ const struct builtincmd *bp;
+
+ for (bp = builtincmd ; bp->name ; bp++) {
+ if (*bp->name == *name
+ && (*name == '%' || equal(bp->name, name)))
+ return bp->builtin;
+ }
+ return 0;
+}
+
+int
+(*find_splbltin(char *name))(int, char **)
+{
+ const struct builtincmd *bp;
+
+ for (bp = splbltincmd ; bp->name ; bp++) {
+ if (*bp->name == *name && equal(bp->name, name))
+ return bp->builtin;
+ }
+ return 0;
+}
+
+/*
+ * At shell startup put special builtins into hash table.
+ * ensures they are executed first (see posix).
+ * We stop functions being added with the same name
+ * (as they are impossible to call)
+ */
+
+void
+hash_special_builtins(void)
+{
+ const struct builtincmd *bp;
+ struct tblentry *cmdp;
+
+ for (bp = splbltincmd ; bp->name ; bp++) {
+ cmdp = cmdlookup(bp->name, 1);
+ cmdp->cmdtype = CMDSPLBLTIN;
+ cmdp->param.bltin = bp->builtin;
+ }
+}
+
+
+
+/*
+ * Called when a cd is done. Marks all commands so the next time they
+ * are executed they will be rehashed.
+ */
+
+void
+hashcd(void)
+{
+ struct tblentry **pp;
+ struct tblentry *cmdp;
+
+ for (pp = cmdtable ; pp < &cmdtable[CMDTABLESIZE] ; pp++) {
+ for (cmdp = *pp ; cmdp ; cmdp = cmdp->next) {
+ if (cmdp->cmdtype == CMDNORMAL
+ || (cmdp->cmdtype == CMDBUILTIN && builtinloc >= 0))
+ cmdp->rehash = 1;
+ }
+ }
+}
+
+
+
+/*
+ * Fix command hash table when PATH changed.
+ * Called before PATH is changed. The argument is the new value of PATH;
+ * pathval() still returns the old value at this point.
+ * Called with interrupts off.
+ */
+
+void
+changepath(const char *newval)
+{
+ const char *old, *new;
+ int idx;
+ int firstchange;
+ int bltin;
+
+ old = pathval();
+ new = newval;
+ firstchange = 9999; /* assume no change */
+ idx = 0;
+ bltin = -1;
+ for (;;) {
+ if (*old != *new) {
+ firstchange = idx;
+ if ((*old == '\0' && *new == ':')
+ || (*old == ':' && *new == '\0'))
+ firstchange++;
+ old = new; /* ignore subsequent differences */
+ }
+ if (*new == '\0')
+ break;
+ if (*new == '%' && bltin < 0 && prefix("builtin", new + 1))
+ bltin = idx;
+ if (*new == ':') {
+ idx++;
+ }
+ new++, old++;
+ }
+ if (builtinloc < 0 && bltin >= 0)
+ builtinloc = bltin; /* zap builtins */
+ if (builtinloc >= 0 && bltin < 0)
+ firstchange = 0;
+ clearcmdentry(firstchange);
+ builtinloc = bltin;
+}
+
+
+/*
+ * Clear out command entries. The argument specifies the first entry in
+ * PATH which has changed.
+ */
+
+STATIC void
+clearcmdentry(int firstchange)
+{
+ struct tblentry **tblp;
+ struct tblentry **pp;
+ struct tblentry *cmdp;
+
+ INTOFF;
+ for (tblp = cmdtable ; tblp < &cmdtable[CMDTABLESIZE] ; tblp++) {
+ pp = tblp;
+ while ((cmdp = *pp) != NULL) {
+ if ((cmdp->cmdtype == CMDNORMAL &&
+ cmdp->param.index >= firstchange)
+ || (cmdp->cmdtype == CMDBUILTIN &&
+ builtinloc >= firstchange)) {
+ *pp = cmdp->next;
+ ckfree(cmdp);
+ } else {
+ pp = &cmdp->next;
+ }
+ }
+ }
+ INTON;
+}
+
+
+/*
+ * Delete all functions.
+ */
+
+#ifdef mkinit
+MKINIT void deletefuncs(void);
+MKINIT void hash_special_builtins(void);
+
+INIT {
+ hash_special_builtins();
+}
+
+SHELLPROC {
+ deletefuncs();
+}
+#endif
+
+void
+deletefuncs(void)
+{
+ struct tblentry **tblp;
+ struct tblentry **pp;
+ struct tblentry *cmdp;
+
+ INTOFF;
+ for (tblp = cmdtable ; tblp < &cmdtable[CMDTABLESIZE] ; tblp++) {
+ pp = tblp;
+ while ((cmdp = *pp) != NULL) {
+ if (cmdp->cmdtype == CMDFUNCTION) {
+ *pp = cmdp->next;
+ freefunc(cmdp->param.func);
+ ckfree(cmdp);
+ } else {
+ pp = &cmdp->next;
+ }
+ }
+ }
+ INTON;
+}
+
+
+
+/*
+ * Locate a command in the command hash table. If "add" is nonzero,
+ * add the command to the table if it is not already present. The
+ * variable "lastcmdentry" is set to point to the address of the link
+ * pointing to the entry, so that delete_cmd_entry can delete the
+ * entry.
+ */
+
+struct tblentry **lastcmdentry;
+
+
+STATIC struct tblentry *
+cmdlookup(const char *name, int add)
+{
+ int hashval;
+ const char *p;
+ struct tblentry *cmdp;
+ struct tblentry **pp;
+
+ p = name;
+ hashval = *p << 4;
+ while (*p)
+ hashval += *p++;
+ hashval &= 0x7FFF;
+ pp = &cmdtable[hashval % CMDTABLESIZE];
+ for (cmdp = *pp ; cmdp ; cmdp = cmdp->next) {
+ if (equal(cmdp->cmdname, name))
+ break;
+ pp = &cmdp->next;
+ }
+ if (add && cmdp == NULL) {
+ INTOFF;
+ cmdp = *pp = ckmalloc(sizeof (struct tblentry) - ARB
+ + strlen(name) + 1);
+ cmdp->next = NULL;
+ cmdp->cmdtype = CMDUNKNOWN;
+ cmdp->rehash = 0;
+ strcpy(cmdp->cmdname, name);
+ INTON;
+ }
+ lastcmdentry = pp;
+ return cmdp;
+}
+
+/*
+ * Delete the command entry returned on the last lookup.
+ */
+
+STATIC void
+delete_cmd_entry(void)
+{
+ struct tblentry *cmdp;
+
+ INTOFF;
+ cmdp = *lastcmdentry;
+ *lastcmdentry = cmdp->next;
+ ckfree(cmdp);
+ INTON;
+}
+
+
+
+#ifdef notdef
+void
+getcmdentry(char *name, struct cmdentry *entry)
+{
+ struct tblentry *cmdp = cmdlookup(name, 0);
+
+ if (cmdp) {
+ entry->u = cmdp->param;
+ entry->cmdtype = cmdp->cmdtype;
+ } else {
+ entry->cmdtype = CMDUNKNOWN;
+ entry->u.index = 0;
+ }
+}
+#endif
+
+
+/*
+ * Add a new command entry, replacing any existing command entry for
+ * the same name - except special builtins.
+ */
+
+STATIC void
+addcmdentry(char *name, struct cmdentry *entry)
+{
+ struct tblentry *cmdp;
+
+ INTOFF;
+ cmdp = cmdlookup(name, 1);
+ if (cmdp->cmdtype != CMDSPLBLTIN) {
+ if (cmdp->cmdtype == CMDFUNCTION)
+ unreffunc(cmdp->param.func);
+ cmdp->cmdtype = entry->cmdtype;
+ cmdp->lineno = entry->lineno;
+ cmdp->fn_ln1 = entry->lno_frel;
+ cmdp->param = entry->u;
+ }
+ INTON;
+}
+
+
+/*
+ * Define a shell function.
+ */
+
+void
+defun(char *name, union node *func, int lineno)
+{
+ struct cmdentry entry;
+
+ INTOFF;
+ entry.cmdtype = CMDFUNCTION;
+ entry.lineno = lineno;
+ entry.lno_frel = fnline1;
+ entry.u.func = copyfunc(func);
+ addcmdentry(name, &entry);
+ INTON;
+}
+
+
+/*
+ * Delete a function if it exists.
+ */
+
+int
+unsetfunc(char *name)
+{
+ struct tblentry *cmdp;
+
+ if ((cmdp = cmdlookup(name, 0)) != NULL &&
+ cmdp->cmdtype == CMDFUNCTION) {
+ unreffunc(cmdp->param.func);
+ delete_cmd_entry();
+ }
+ return 0;
+}
+
+/*
+ * Locate and print what a word is...
+ * also used for 'command -[v|V]'
+ */
+
+int
+typecmd(int argc, char **argv)
+{
+ struct cmdentry entry;
+ struct tblentry *cmdp;
+ const char * const *pp;
+ struct alias *ap;
+ int err = 0;
+ char *arg;
+ int c;
+ int V_flag = 0;
+ int v_flag = 0;
+ int p_flag = 0;
+
+ while ((c = nextopt("vVp")) != 0) {
+ switch (c) {
+ case 'v': v_flag = 1; break;
+ case 'V': V_flag = 1; break;
+ case 'p': p_flag = 1; break;
+ }
+ }
+
+ if (argv[0][0] != 'c' && v_flag | V_flag | p_flag)
+ error("usage: %s name...", argv[0]);
+
+ if (v_flag && V_flag)
+ error("-v and -V cannot both be specified");
+
+ if (*argptr == NULL)
+ error("usage: %s%s name ...", argv[0],
+ argv[0][0] == 'c' ? " [-p] [-v|-V]" : "");
+
+ while ((arg = *argptr++)) {
+ if (!v_flag)
+ out1str(arg);
+ /* First look at the keywords */
+ for (pp = parsekwd; *pp; pp++)
+ if (**pp == *arg && equal(*pp, arg))
+ break;
+
+ if (*pp) {
+ if (v_flag)
+ out1fmt("%s\n", arg);
+ else
+ out1str(" is a shell keyword\n");
+ continue;
+ }
+
+ /* Then look at the aliases */
+ if ((ap = lookupalias(arg, 1)) != NULL) {
+ int ml = 0;
+
+ if (!v_flag) {
+ out1str(" is an alias ");
+ if (strchr(ap->val, '\n')) {
+ out1str("(multiline)...\n");
+ ml = 1;
+ } else
+ out1str("for: ");
+ }
+ out1fmt("%s\n", ap->val);
+ if (ml && *argptr != NULL)
+ out1c('\n');
+ continue;
+ }
+
+ /* Then check if it is a tracked alias */
+ if (!p_flag && (cmdp = cmdlookup(arg, 0)) != NULL) {
+ entry.cmdtype = cmdp->cmdtype;
+ entry.u = cmdp->param;
+ } else {
+ cmdp = NULL;
+ /* Finally use brute force */
+ find_command(arg, &entry, DO_ABS,
+ p_flag ? syspath() + 5 : pathval());
+ }
+
+ switch (entry.cmdtype) {
+ case CMDNORMAL: {
+ if (strchr(arg, '/') == NULL) {
+ const char *path;
+ char *name;
+ int j = entry.u.index;
+
+ path = p_flag ? syspath() + 5 : pathval();
+
+ do {
+ name = padvance(&path, arg, 1);
+ stunalloc(name);
+ } while (--j >= 0);
+ if (!v_flag)
+ out1fmt(" is%s ",
+ cmdp ? " a tracked alias for" : "");
+ out1fmt("%s\n", name);
+ } else {
+ if (access(arg, X_OK) == 0) {
+ if (!v_flag)
+ out1fmt(" is ");
+ out1fmt("%s\n", arg);
+ } else {
+ if (!v_flag)
+ out1fmt(": %s\n",
+ strerror(errno));
+ else
+ err = 126;
+ }
+ }
+ break;
+ }
+ case CMDFUNCTION:
+ if (!v_flag)
+ out1str(" is a shell function\n");
+ else
+ out1fmt("%s\n", arg);
+ break;
+
+ case CMDBUILTIN:
+ if (!v_flag)
+ out1str(" is a shell builtin\n");
+ else
+ out1fmt("%s\n", arg);
+ break;
+
+ case CMDSPLBLTIN:
+ if (!v_flag)
+ out1str(" is a special shell builtin\n");
+ else
+ out1fmt("%s\n", arg);
+ break;
+
+ default:
+ if (!v_flag)
+ out1str(": not found\n");
+ err = 127;
+ break;
+ }
+ }
+ return err;
+}
diff --git a/bin/sh/exec.h b/bin/sh/exec.h
new file mode 100644
index 0000000..90beba1
--- /dev/null
+++ b/bin/sh/exec.h
@@ -0,0 +1,78 @@
+/* $NetBSD: exec.h,v 1.27 2018/06/22 11:04:55 kre Exp $ */
+
+/*-
+ * Copyright (c) 1991, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Kenneth Almquist.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * @(#)exec.h 8.3 (Berkeley) 6/8/95
+ */
+
+/* values of cmdtype */
+#define CMDUNKNOWN -1 /* no entry in table for command */
+#define CMDNORMAL 0 /* command is an executable program */
+#define CMDFUNCTION 1 /* command is a shell function */
+#define CMDBUILTIN 2 /* command is a shell builtin */
+#define CMDSPLBLTIN 3 /* command is a special shell builtin */
+
+
+struct cmdentry {
+ short cmdtype;
+ short lno_frel; /* for functions: Line numbers count from 1 */
+ int lineno; /* for functions: Abs line number of defn */
+ union param {
+ int index;
+ int (*bltin)(int, char**);
+ struct funcdef *func;
+ } u;
+};
+
+
+/* action to find_command() */
+#define DO_ERR 0x01 /* prints errors */
+#define DO_ABS 0x02 /* checks absolute paths */
+#define DO_NOFUNC 0x04 /* don't return shell functions, for command */
+#define DO_ALTPATH 0x08 /* using alternate path */
+#define DO_ALTBLTIN 0x20 /* %builtin in alt. path */
+
+extern const char *pathopt; /* set by padvance */
+
+void shellexec(char **, char **, const char *, int, int) __dead;
+char *padvance(const char **, const char *, int);
+void find_command(char *, struct cmdentry *, int, const char *);
+int (*find_builtin(char *))(int, char **);
+int (*find_splbltin(char *))(int, char **);
+void hashcd(void);
+void changepath(const char *);
+void deletefuncs(void);
+void getcmdentry(char *, struct cmdentry *);
+union node;
+void defun(char *, union node *, int);
+int unsetfunc(char *);
+void hash_special_builtins(void);
diff --git a/bin/sh/expand.c b/bin/sh/expand.c
new file mode 100644
index 0000000..955185b
--- /dev/null
+++ b/bin/sh/expand.c
@@ -0,0 +1,2125 @@
+/* $NetBSD: expand.c,v 1.129 2018/12/03 06:41:30 kre Exp $ */
+
+/*-
+ * Copyright (c) 1991, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Kenneth Almquist.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+#ifndef lint
+#if 0
+static char sccsid[] = "@(#)expand.c 8.5 (Berkeley) 5/15/95";
+#else
+__RCSID("$NetBSD: expand.c,v 1.129 2018/12/03 06:41:30 kre Exp $");
+#endif
+#endif /* not lint */
+
+#include <sys/types.h>
+#include <sys/time.h>
+#include <sys/stat.h>
+#include <errno.h>
+#include <dirent.h>
+#include <unistd.h>
+#include <pwd.h>
+#include <limits.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <wctype.h>
+#include <wchar.h>
+
+/*
+ * Routines to expand arguments to commands. We have to deal with
+ * backquotes, shell variables, and file metacharacters.
+ */
+
+#include "shell.h"
+#include "main.h"
+#include "nodes.h"
+#include "eval.h"
+#include "expand.h"
+#include "syntax.h"
+#include "arithmetic.h"
+#include "parser.h"
+#include "jobs.h"
+#include "options.h"
+#include "builtins.h"
+#include "var.h"
+#include "input.h"
+#include "output.h"
+#include "memalloc.h"
+#include "error.h"
+#include "mystring.h"
+#include "show.h"
+
+/*
+ * Structure specifying which parts of the string should be searched
+ * for IFS characters.
+ */
+
+struct ifsregion {
+ struct ifsregion *next; /* next region in list */
+ int begoff; /* offset of start of region */
+ int endoff; /* offset of end of region */
+ int inquotes; /* search for nul bytes only */
+};
+
+
+char *expdest; /* output of current string */
+struct nodelist *argbackq; /* list of back quote expressions */
+struct ifsregion ifsfirst; /* first struct in list of ifs regions */
+struct ifsregion *ifslastp; /* last struct in list */
+struct arglist exparg; /* holds expanded arg list */
+
+STATIC const char *argstr(const char *, int);
+STATIC const char *exptilde(const char *, int);
+STATIC void expbackq(union node *, int, int);
+STATIC const char *expari(const char *);
+STATIC int subevalvar(const char *, const char *, int, int, int);
+STATIC int subevalvar_trim(const char *, int, int, int, int, int);
+STATIC const char *evalvar(const char *, int);
+STATIC int varisset(const char *, int);
+STATIC void varvalue(const char *, int, int, int);
+STATIC void recordregion(int, int, int);
+STATIC void removerecordregions(int);
+STATIC void ifsbreakup(char *, struct arglist *);
+STATIC void ifsfree(void);
+STATIC void expandmeta(struct strlist *, int);
+STATIC void expmeta(char *, char *);
+STATIC void addfname(char *);
+STATIC struct strlist *expsort(struct strlist *);
+STATIC struct strlist *msort(struct strlist *, int);
+STATIC int patmatch(const char *, const char *, int);
+STATIC char *cvtnum(int, char *);
+static int collate_range_cmp(wchar_t, wchar_t);
+STATIC void add_args(struct strlist *);
+STATIC void rmescapes_nl(char *);
+
+#ifdef DEBUG
+#define NULLTERM_4_TRACE(p) STACKSTRNUL(p)
+#else
+#define NULLTERM_4_TRACE(p) do { /* nothing */ } while (/*CONSTCOND*/0)
+#endif
+
+#define IS_BORING(_ch) \
+ ((_ch) == CTLQUOTEMARK || (_ch) == CTLQUOTEEND || (_ch) == CTLNONL)
+#define SKIP_BORING(p) \
+ do { \
+ char _ch; \
+ \
+ while ((_ch = *(p)), IS_BORING(_ch)) \
+ (p)++; \
+ } while (0)
+
+/*
+ * Expand shell variables and backquotes inside a here document.
+ */
+
+void
+expandhere(union node *arg, int fd)
+{
+
+ herefd = fd;
+ expandarg(arg, NULL, 0);
+ xwrite(fd, stackblock(), expdest - stackblock());
+}
+
+
+static int
+collate_range_cmp(wchar_t c1, wchar_t c2)
+{
+ wchar_t s1[2], s2[2];
+
+ s1[0] = c1;
+ s1[1] = L'\0';
+ s2[0] = c2;
+ s2[1] = L'\0';
+ return (wcscoll(s1, s2));
+}
+
+/*
+ * Perform variable substitution and command substitution on an argument,
+ * placing the resulting list of arguments in arglist. If EXP_FULL is true,
+ * perform splitting and file name expansion. When arglist is NULL, perform
+ * here document expansion.
+ */
+
+void
+expandarg(union node *arg, struct arglist *arglist, int flag)
+{
+ struct strlist *sp;
+ char *p;
+
+ CTRACE(DBG_EXPAND, ("expandarg(fl=%#x)\n", flag));
+ if (fflag) /* no filename expandsion */
+ flag &= ~EXP_GLOB;
+
+ argbackq = arg->narg.backquote;
+ STARTSTACKSTR(expdest);
+ ifsfirst.next = NULL;
+ ifslastp = NULL;
+ line_number = arg->narg.lineno;
+ argstr(arg->narg.text, flag);
+ if (arglist == NULL) {
+ STACKSTRNUL(expdest);
+ CTRACE(DBG_EXPAND, ("expandarg: no arglist, done (%d) \"%s\"\n",
+ expdest - stackblock(), stackblock()));
+ return; /* here document expanded */
+ }
+ STPUTC('\0', expdest);
+ CTRACE(DBG_EXPAND, ("expandarg: arglist got (%d) \"%s\"\n",
+ expdest - stackblock() - 1, stackblock()));
+ p = grabstackstr(expdest);
+ exparg.lastp = &exparg.list;
+ /*
+ * TODO - EXP_REDIR
+ */
+ if (flag & EXP_SPLIT) {
+ ifsbreakup(p, &exparg);
+ *exparg.lastp = NULL;
+ exparg.lastp = &exparg.list;
+ if (flag & EXP_GLOB)
+ expandmeta(exparg.list, flag);
+ else
+ add_args(exparg.list);
+ } else {
+ if (flag & EXP_REDIR) /*XXX - for now, just remove escapes */
+ rmescapes(p);
+ sp = stalloc(sizeof(*sp));
+ sp->text = p;
+ *exparg.lastp = sp;
+ exparg.lastp = &sp->next;
+ }
+ ifsfree();
+ *exparg.lastp = NULL;
+ if (exparg.list) {
+ *arglist->lastp = exparg.list;
+ arglist->lastp = exparg.lastp;
+ }
+}
+
+
+
+/*
+ * Perform variable and command substitution.
+ * If EXP_GLOB is set, output CTLESC characters to allow for further processing.
+ * If EXP_SPLIT is set, remember location of result for later,
+ * Otherwise treat $@ like $* since no splitting will be performed.
+ */
+
+STATIC const char *
+argstr(const char *p, int flag)
+{
+ char c;
+ const int quotes = flag & EXP_QNEEDED; /* do CTLESC */
+ int firsteq = 1;
+ const char *ifs = NULL;
+ int ifs_split = EXP_IFS_SPLIT;
+
+ if (flag & EXP_IFS_SPLIT)
+ ifs = ifsval();
+
+ CTRACE(DBG_EXPAND, ("argstr(\"%s\", %#x) quotes=%#x\n", p,flag,quotes));
+
+ if (*p == '~' && (flag & (EXP_TILDE | EXP_VARTILDE)))
+ p = exptilde(p, flag);
+ for (;;) {
+ switch (c = *p++) {
+ case '\0':
+ NULLTERM_4_TRACE(expdest);
+ VTRACE(DBG_EXPAND, ("argstr returning at \"\" "
+ "added \"%s\" to expdest\n", stackblock()));
+ return p - 1;
+ case CTLENDVAR: /* end of expanding yyy in ${xxx-yyy} */
+ case CTLENDARI: /* end of a $(( )) string */
+ NULLTERM_4_TRACE(expdest);
+ VTRACE(DBG_EXPAND, ("argstr returning at \"%.6s\"..."
+ " after %2.2X; added \"%s\" to expdest\n",
+ p, (c&0xff), stackblock()));
+ return p;
+ case CTLQUOTEMARK:
+ /* "$@" syntax adherence hack */
+ if (p[0] == CTLVAR && p[1] & VSQUOTE &&
+ p[2] == '@' && p[3] == '=')
+ break;
+ if ((flag & EXP_SPLIT) != 0)
+ STPUTC(c, expdest);
+ ifs_split = 0;
+ break;
+ case CTLNONL:
+ if (flag & EXP_NL)
+ STPUTC(c, expdest);
+ line_number++;
+ break;
+ case CTLCNL:
+ STPUTC('\n', expdest); /* no line_number++ */
+ break;
+ case CTLQUOTEEND:
+ if ((flag & EXP_SPLIT) != 0)
+ STPUTC(c, expdest);
+ ifs_split = EXP_IFS_SPLIT;
+ break;
+ case CTLESC:
+ if (quotes)
+ STPUTC(c, expdest);
+ c = *p++;
+ STPUTC(c, expdest);
+ if (c == '\n') /* should not happen, but ... */
+ line_number++;
+ break;
+ case CTLVAR: {
+#ifdef DEBUG
+ unsigned int pos = expdest - stackblock();
+ NULLTERM_4_TRACE(expdest);
+#endif
+ p = evalvar(p, (flag & ~EXP_IFS_SPLIT) | (flag & ifs_split));
+ NULLTERM_4_TRACE(expdest);
+ VTRACE(DBG_EXPAND, ("argstr evalvar "
+ "added %zd \"%s\" to expdest\n",
+ (size_t)(expdest - (stackblock() + pos)),
+ stackblock() + pos));
+ break;
+ }
+ case CTLBACKQ:
+ case CTLBACKQ|CTLQUOTE: {
+#ifdef DEBUG
+ unsigned int pos = expdest - stackblock();
+#endif
+ expbackq(argbackq->n, c & CTLQUOTE, flag);
+ argbackq = argbackq->next;
+ NULLTERM_4_TRACE(expdest);
+ VTRACE(DBG_EXPAND, ("argstr expbackq added \"%s\" "
+ "to expdest\n", stackblock() + pos));
+ break;
+ }
+ case CTLARI: {
+#ifdef DEBUG
+ unsigned int pos = expdest - stackblock();
+#endif
+ p = expari(p);
+ NULLTERM_4_TRACE(expdest);
+ VTRACE(DBG_EXPAND, ("argstr expari "
+ "+ \"%s\" to expdest p=\"%.5s...\"\n",
+ stackblock() + pos, p));
+ break;
+ }
+ case ':':
+ case '=':
+ /*
+ * sort of a hack - expand tildes in variable
+ * assignments (after the first '=' and after ':'s).
+ */
+ STPUTC(c, expdest);
+ if (flag & EXP_VARTILDE && *p == '~') {
+ if (c == '=') {
+ if (firsteq)
+ firsteq = 0;
+ else
+ break;
+ }
+ p = exptilde(p, flag);
+ }
+ break;
+ default:
+ if (c == '\n')
+ line_number++;
+ STPUTC(c, expdest);
+ if (flag & ifs_split && strchr(ifs, c) != NULL) {
+ /* We need to get the output split here... */
+ recordregion(expdest - stackblock() - 1,
+ expdest - stackblock(), 0);
+ }
+ break;
+ }
+ }
+}
+
+STATIC const char *
+exptilde(const char *p, int flag)
+{
+ char c;
+ const char *startp = p;
+ struct passwd *pw;
+ const char *home;
+ const int quotes = flag & EXP_QNEEDED;
+ char *user;
+ struct stackmark smark;
+#ifdef DEBUG
+ unsigned int offs = expdest - stackblock();
+#endif
+
+ setstackmark(&smark);
+ (void) grabstackstr(expdest);
+ user = stackblock(); /* we will just borrow top of stack */
+
+ while ((c = *++p) != '\0') {
+ switch(c) {
+ case CTLESC: /* any of these occurring */
+ case CTLVAR: /* means ~ expansion */
+ case CTLBACKQ: /* does not happen at all */
+ case CTLBACKQ | CTLQUOTE:
+ case CTLARI: /* just leave original unchanged */
+ case CTLENDARI:
+ case CTLQUOTEMARK:
+ case '\n':
+ popstackmark(&smark);
+ return (startp);
+ case CTLNONL:
+ continue;
+ case ':':
+ if (!posix || flag & EXP_VARTILDE)
+ goto done;
+ break;
+ case CTLENDVAR:
+ case '/':
+ goto done;
+ }
+ STPUTC(c, user);
+ }
+ done:
+ STACKSTRNUL(user);
+ user = stackblock(); /* to start of collected username */
+
+ CTRACE(DBG_EXPAND, ("exptilde, found \"~%s\"", user));
+ if (*user == '\0') {
+ home = lookupvar("HOME");
+ /*
+ * if HOME is unset, results are unspecified...
+ * we used to just leave the ~ unchanged, but
+ * (some) other shells do ... and this seems more useful.
+ */
+ if (home == NULL && (pw = getpwuid(getuid())) != NULL)
+ home = pw->pw_dir;
+ } else if ((pw = getpwnam(user)) == NULL) {
+ /*
+ * If user does not exist, results are undefined.
+ * so we can abort() here if we want, but let's not!
+ */
+ home = NULL;
+ } else
+ home = pw->pw_dir;
+
+ VTRACE(DBG_EXPAND, (" ->\"%s\"", home ? home : "<<NULL>>"));
+ popstackmark(&smark); /* now expdest is valid again */
+
+ /*
+ * Posix XCU 2.6.1: The value of $HOME (for ~) or the initial
+ * working directory from getpwnam() for ~user
+ * Nothing there about "except if a null string". So do what it wants.
+ */
+ if (home == NULL /* || *home == '\0' */) {
+ CTRACE(DBG_EXPAND, (": returning unused \"%s\"\n", startp));
+ return startp;
+ } while ((c = *home++) != '\0') {
+ if (quotes && NEEDESC(c))
+ STPUTC(CTLESC, expdest);
+ STPUTC(c, expdest);
+ }
+ CTRACE(DBG_EXPAND, (": added %d \"%.*s\" returning \"%s\"\n",
+ expdest - stackblock() - offs, expdest - stackblock() - offs,
+ stackblock() + offs, p));
+
+ return (p);
+}
+
+
+STATIC void
+removerecordregions(int endoff)
+{
+
+ VTRACE(DBG_EXPAND, ("removerecordregions(%d):", endoff));
+ if (ifslastp == NULL) {
+ VTRACE(DBG_EXPAND, (" none\n", endoff));
+ return;
+ }
+
+ if (ifsfirst.endoff > endoff) {
+ VTRACE(DBG_EXPAND, (" first(%d)", ifsfirst.endoff));
+ while (ifsfirst.next != NULL) {
+ struct ifsregion *ifsp;
+ INTOFF;
+ ifsp = ifsfirst.next->next;
+ ckfree(ifsfirst.next);
+ ifsfirst.next = ifsp;
+ INTON;
+ }
+ if (ifsfirst.begoff > endoff)
+ ifslastp = NULL;
+ else {
+ VTRACE(DBG_EXPAND,("->(%d,%d)",ifsfirst.begoff,endoff));
+ ifslastp = &ifsfirst;
+ ifsfirst.endoff = endoff;
+ }
+ VTRACE(DBG_EXPAND, ("\n"));
+ return;
+ }
+
+ ifslastp = &ifsfirst;
+ while (ifslastp->next && ifslastp->next->begoff < endoff)
+ ifslastp=ifslastp->next;
+ VTRACE(DBG_EXPAND, (" found(%d,%d)", ifslastp->begoff,ifslastp->endoff));
+ while (ifslastp->next != NULL) {
+ struct ifsregion *ifsp;
+ INTOFF;
+ ifsp = ifslastp->next->next;
+ ckfree(ifslastp->next);
+ ifslastp->next = ifsp;
+ INTON;
+ }
+ if (ifslastp->endoff > endoff)
+ ifslastp->endoff = endoff;
+ VTRACE(DBG_EXPAND, ("->(%d,%d)", ifslastp->begoff,ifslastp->endoff));
+}
+
+
+/*
+ * Expand arithmetic expression.
+ *
+ * In this incarnation, we start at the beginning (yes, "Let's start at the
+ * very beginning. A very good place to start.") and collect the expression
+ * until the end - which means expanding anything contained within.
+ *
+ * Fortunately, argstr() just happens to do that for us...
+ */
+STATIC const char *
+expari(const char *p)
+{
+ char *q, *start;
+ intmax_t result;
+ int adjustment;
+ int begoff;
+ int quoted;
+ struct stackmark smark;
+
+ /* ifsfree(); */
+
+ /*
+ * SPACE_NEEDED is enough for all possible digits (rounded up)
+ * plus possible "-", and the terminating '\0', hence, plus 2
+ *
+ * The calculation produces the number of bytes needed to
+ * represent the biggest possible value, in octal. We only
+ * generate decimal, which takes (often) less digits (never more)
+ * so this is safe, if occasionally slightly wasteful.
+ */
+#define SPACE_NEEDED ((int)((sizeof(intmax_t) * CHAR_BIT + 2) / 3 + 2))
+
+ quoted = *p++ == '"';
+ begoff = expdest - stackblock();
+ VTRACE(DBG_EXPAND, ("expari%s: \"%s\" begoff %d\n",
+ quoted ? "(quoted)" : "", p, begoff));
+
+ p = argstr(p, EXP_NL); /* expand $(( )) string */
+ STPUTC('\0', expdest);
+ start = stackblock() + begoff;
+
+ removerecordregions(begoff); /* nothing there is kept */
+ rmescapes_nl(start); /* convert CRTNONL back into \n's */
+
+ setstackmark(&smark);
+ q = grabstackstr(expdest); /* keep the expression while eval'ing */
+ result = arith(start, line_number);
+ popstackmark(&smark); /* return the stack to before grab */
+
+ start = stackblock() + begoff; /* block may have moved */
+ adjustment = expdest - start;
+ STADJUST(-adjustment, expdest); /* remove the argstr() result */
+
+ CHECKSTRSPACE(SPACE_NEEDED, expdest); /* nb: stack block might move */
+ fmtstr(expdest, SPACE_NEEDED, "%"PRIdMAX, result);
+
+ for (q = expdest; *q++ != '\0'; ) /* find end of what we added */
+ ;
+
+ if (quoted == 0) /* allow weird splitting */
+ recordregion(begoff, begoff + q - 1 - expdest, 0);
+ adjustment = q - expdest - 1;
+ STADJUST(adjustment, expdest); /* move expdest to end */
+ VTRACE(DBG_EXPAND, ("expari: adding %d \"%s\", returning \"%.5s...\"\n",
+ adjustment, stackblock() + begoff, p));
+
+ return p;
+}
+
+
+/*
+ * Expand stuff in backwards quotes (these days, any command substitution).
+ */
+
+STATIC void
+expbackq(union node *cmd, int quoted, int flag)
+{
+ struct backcmd in;
+ int i;
+ char buf[128];
+ char *p;
+ char *dest = expdest; /* expdest may be reused by eval, use an alt */
+ struct ifsregion saveifs, *savelastp;
+ struct nodelist *saveargbackq;
+ char lastc;
+ int startloc = dest - stackblock();
+ int saveherefd;
+ const int quotes = flag & EXP_QNEEDED;
+ int nnl;
+ struct stackmark smark;
+
+ VTRACE(DBG_EXPAND, ("expbackq( ..., q=%d flag=%#x) have %d\n",
+ quoted, flag, startloc));
+ INTOFF;
+ saveifs = ifsfirst;
+ savelastp = ifslastp;
+ saveargbackq = argbackq;
+ saveherefd = herefd;
+ herefd = -1;
+
+ setstackmark(&smark); /* preserve the stack */
+ p = grabstackstr(dest); /* save what we have there currently */
+ evalbackcmd(cmd, &in); /* evaluate the $( ) tree (using stack) */
+ popstackmark(&smark); /* and return stack to when we entered */
+
+ ifsfirst = saveifs;
+ ifslastp = savelastp;
+ argbackq = saveargbackq;
+ herefd = saveherefd;
+
+ p = in.buf; /* now extract the results */
+ nnl = 0; /* dropping trailing \n's */
+ for (;;) {
+ if (--in.nleft < 0) {
+ if (in.fd < 0)
+ break;
+ INTON;
+ while ((i = read(in.fd, buf, sizeof buf)) < 0 && errno == EINTR)
+ continue;
+ INTOFF;
+ VTRACE(DBG_EXPAND, ("expbackq: read returns %d\n", i));
+ if (i <= 0)
+ break;
+ p = buf;
+ in.nleft = i - 1;
+ }
+ lastc = *p++;
+ if (lastc != '\0') {
+ if (lastc == '\n') /* don't save \n yet */
+ nnl++; /* it might be trailing */
+ else {
+ /*
+ * We have something other than \n
+ *
+ * Before saving it, we need to insert
+ * any \n's that we have just skipped.
+ */
+
+ /* XXX
+ * this hack is just because our
+ * CHECKSTRSPACE() is lazy, and only
+ * ever grows the stack once, even
+ * if that does not allocate the space
+ * we requested. ie: safe for small
+ * requests, but not large ones.
+ * FIXME someday...
+ */
+ if (nnl < 20) {
+ CHECKSTRSPACE(nnl + 2, dest);
+ while (nnl > 0) {
+ nnl--;
+ USTPUTC('\n', dest);
+ }
+ } else {
+ /* The slower, safer, way */
+ while (nnl > 0) {
+ nnl--;
+ STPUTC('\n', dest);
+ }
+ CHECKSTRSPACE(2, dest);
+ }
+ if (quotes && quoted && NEEDESC(lastc))
+ USTPUTC(CTLESC, dest);
+ USTPUTC(lastc, dest);
+ }
+ }
+ }
+
+ if (in.fd >= 0)
+ close(in.fd);
+ if (in.buf)
+ ckfree(in.buf);
+ if (in.jp)
+ back_exitstatus = waitforjob(in.jp);
+ if (quoted == 0)
+ recordregion(startloc, dest - stackblock(), 0);
+ CTRACE(DBG_EXPAND, ("evalbackq: size=%d: \"%.*s\"\n",
+ (int)((dest - stackblock()) - startloc),
+ (int)((dest - stackblock()) - startloc),
+ stackblock() + startloc));
+
+ expdest = dest; /* all done, expdest is all ours again */
+ INTON;
+}
+
+
+STATIC int
+subevalvar(const char *p, const char *str, int subtype, int startloc,
+ int varflags)
+{
+ char *startp;
+ int saveherefd = herefd;
+ struct nodelist *saveargbackq = argbackq;
+ int amount;
+
+ herefd = -1;
+ VTRACE(DBG_EXPAND, ("subevalvar(%d) \"%.20s\" ${%.*s} sloc=%d vf=%x\n",
+ subtype, p, p-str, str, startloc, varflags));
+ argstr(p, subtype == VSASSIGN ? EXP_VARTILDE : EXP_TILDE);
+ STACKSTRNUL(expdest);
+ herefd = saveherefd;
+ argbackq = saveargbackq;
+ startp = stackblock() + startloc;
+
+ switch (subtype) {
+ case VSASSIGN:
+ setvar(str, startp, 0);
+ amount = startp - expdest; /* remove what argstr added */
+ STADJUST(amount, expdest);
+ varflags &= ~VSNUL; /*XXX Huh? What's that achieve? */
+ return 1; /* go back and eval var again */
+
+ case VSQUESTION:
+ if (*p != CTLENDVAR) {
+ outfmt(&errout, "%s\n", startp);
+ error(NULL);
+ }
+ error("%.*s: parameter %snot set",
+ (int)(p - str - 1),
+ str, (varflags & VSNUL) ? "null or "
+ : nullstr);
+ /* NOTREACHED */
+
+ default:
+ abort();
+ }
+}
+
+STATIC int
+subevalvar_trim(const char *p, int strloc, int subtype, int startloc,
+ int varflags, int quotes)
+{
+ char *startp;
+ char *str;
+ char *loc = NULL;
+ char *q;
+ int c = 0;
+ int saveherefd = herefd;
+ struct nodelist *saveargbackq = argbackq;
+ int amount;
+
+ herefd = -1;
+ switch (subtype) {
+ case VSTRIMLEFT:
+ case VSTRIMLEFTMAX:
+ case VSTRIMRIGHT:
+ case VSTRIMRIGHTMAX:
+ break;
+ default:
+ abort();
+ break;
+ }
+
+ VTRACE(DBG_EXPAND,
+ ("subevalvar_trim(\"%.9s\", STR@%d, SUBT=%d, start@%d, vf=%x, q=%x)\n",
+ p, strloc, subtype, startloc, varflags, quotes));
+
+ argstr(p, (varflags & (VSQUOTE|VSPATQ)) == VSQUOTE ? 0 : EXP_CASE);
+ STACKSTRNUL(expdest);
+ herefd = saveherefd;
+ argbackq = saveargbackq;
+ startp = stackblock() + startloc;
+ str = stackblock() + strloc;
+
+ switch (subtype) {
+
+ case VSTRIMLEFT:
+ for (loc = startp; loc < str; loc++) {
+ c = *loc;
+ *loc = '\0';
+ if (patmatch(str, startp, quotes))
+ goto recordleft;
+ *loc = c;
+ if (quotes && *loc == CTLESC)
+ loc++;
+ }
+ return 0;
+
+ case VSTRIMLEFTMAX:
+ for (loc = str - 1; loc >= startp;) {
+ c = *loc;
+ *loc = '\0';
+ if (patmatch(str, startp, quotes))
+ goto recordleft;
+ *loc = c;
+ loc--;
+ if (quotes && loc > startp &&
+ *(loc - 1) == CTLESC) {
+ for (q = startp; q < loc; q++)
+ if (*q == CTLESC)
+ q++;
+ if (q > loc)
+ loc--;
+ }
+ }
+ return 0;
+
+ case VSTRIMRIGHT:
+ for (loc = str - 1; loc >= startp;) {
+ if (patmatch(str, loc, quotes))
+ goto recordright;
+ loc--;
+ if (quotes && loc > startp &&
+ *(loc - 1) == CTLESC) {
+ for (q = startp; q < loc; q++)
+ if (*q == CTLESC)
+ q++;
+ if (q > loc)
+ loc--;
+ }
+ }
+ return 0;
+
+ case VSTRIMRIGHTMAX:
+ for (loc = startp; loc < str - 1; loc++) {
+ if (patmatch(str, loc, quotes))
+ goto recordright;
+ if (quotes && *loc == CTLESC)
+ loc++;
+ }
+ return 0;
+
+ default:
+ abort();
+ }
+
+ recordleft:
+ *loc = c;
+ amount = ((str - 1) - (loc - startp)) - expdest;
+ STADJUST(amount, expdest);
+ while (loc != str - 1)
+ *startp++ = *loc++;
+ return 1;
+
+ recordright:
+ amount = loc - expdest;
+ STADJUST(amount, expdest);
+ STPUTC('\0', expdest);
+ STADJUST(-1, expdest);
+ return 1;
+}
+
+
+/*
+ * Expand a variable, and return a pointer to the next character in the
+ * input string.
+ */
+
+STATIC const char *
+evalvar(const char *p, int flag)
+{
+ int subtype;
+ int varflags;
+ const char *var;
+ char *val;
+ int patloc;
+ int c;
+ int set;
+ int special;
+ int startloc;
+ int varlen;
+ int apply_ifs;
+ const int quotes = flag & EXP_QNEEDED;
+
+ varflags = (unsigned char)*p++;
+ subtype = varflags & VSTYPE;
+ var = p;
+ special = !is_name(*p);
+ p = strchr(p, '=') + 1;
+
+ CTRACE(DBG_EXPAND,
+ ("evalvar \"%.*s\", flag=%#X quotes=%#X vf=%#X subtype=%X\n",
+ p - var - 1, var, flag, quotes, varflags, subtype));
+
+ again: /* jump here after setting a variable with ${var=text} */
+ if (varflags & VSLINENO) {
+ if (line_num.flags & VUNSET) {
+ set = 0;
+ val = NULL;
+ } else {
+ set = 1;
+ special = p - var;
+ val = NULL;
+ }
+ } else if (special) {
+ set = varisset(var, varflags & VSNUL);
+ val = NULL;
+ } else {
+ val = lookupvar(var);
+ if (val == NULL || ((varflags & VSNUL) && val[0] == '\0')) {
+ val = NULL;
+ set = 0;
+ } else
+ set = 1;
+ }
+
+ varlen = 0;
+ startloc = expdest - stackblock();
+
+ if (!set && uflag && *var != '@' && *var != '*') {
+ switch (subtype) {
+ case VSNORMAL:
+ case VSTRIMLEFT:
+ case VSTRIMLEFTMAX:
+ case VSTRIMRIGHT:
+ case VSTRIMRIGHTMAX:
+ case VSLENGTH:
+ error("%.*s: parameter not set",
+ (int)(p - var - 1), var);
+ /* NOTREACHED */
+ }
+ }
+
+ if (!set && subtype != VSPLUS && special && *var == '@')
+ if (startloc > 0 && expdest[-1] == CTLQUOTEMARK)
+ expdest--, startloc--;
+
+ if (set && subtype != VSPLUS) {
+ /* insert the value of the variable */
+ if (special) {
+ if (varflags & VSLINENO) {
+ /*
+ * The LINENO hack (expansion part)
+ */
+ while (--special > 0) {
+/* not needed, it is a number...
+ if (quotes && NEEDESC(*var))
+ STPUTC(CTLESC, expdest);
+*/
+ STPUTC(*var++, expdest);
+ }
+ } else
+ varvalue(var, varflags&VSQUOTE, subtype, flag);
+ if (subtype == VSLENGTH) {
+ varlen = expdest - stackblock() - startloc;
+ STADJUST(-varlen, expdest);
+ }
+ } else {
+
+ if (subtype == VSLENGTH) {
+ for (;*val; val++)
+ varlen++;
+ } else if (quotes && varflags & VSQUOTE) {
+ for (; (c = *val) != '\0'; val++) {
+ if (NEEDESC(c))
+ STPUTC(CTLESC, expdest);
+ STPUTC(c, expdest);
+ }
+ } else {
+ while (*val)
+ STPUTC(*val++, expdest);
+ }
+ }
+ }
+
+
+ if (varflags & VSQUOTE) {
+ if (*var == '@' && shellparam.nparam != 1)
+ apply_ifs = 1;
+ else {
+ /*
+ * Mark so that we don't apply IFS if we recurse through
+ * here expanding $bar from "${foo-$bar}".
+ */
+ flag |= EXP_IN_QUOTES;
+ apply_ifs = 0;
+ }
+ } else if (flag & EXP_IN_QUOTES) {
+ apply_ifs = 0;
+ } else
+ apply_ifs = 1;
+
+ switch (subtype) {
+ case VSLENGTH:
+ expdest = cvtnum(varlen, expdest);
+ break;
+
+ case VSNORMAL:
+ break;
+
+ case VSPLUS:
+ set = !set;
+ /* FALLTHROUGH */
+ case VSMINUS:
+ if (!set) {
+ argstr(p, flag | (apply_ifs ? EXP_IFS_SPLIT : 0));
+ /*
+ * ${x-a b c} doesn't get split, but removing the
+ * 'apply_ifs = 0' apparently breaks ${1+"$@"}..
+ * ${x-'a b' c} should generate 2 args.
+ */
+ if (*p != CTLENDVAR)
+ /* We should have marked stuff already */
+ apply_ifs = 0;
+ }
+ break;
+
+ case VSTRIMLEFT:
+ case VSTRIMLEFTMAX:
+ case VSTRIMRIGHT:
+ case VSTRIMRIGHTMAX:
+ if (!set) {
+ set = 1; /* allow argbackq to be advanced if needed */
+ break;
+ }
+ /*
+ * Terminate the string and start recording the pattern
+ * right after it
+ */
+ STPUTC('\0', expdest);
+ patloc = expdest - stackblock();
+ if (subevalvar_trim(p, patloc, subtype, startloc, varflags,
+ quotes) == 0) {
+ int amount = (expdest - stackblock() - patloc) + 1;
+ STADJUST(-amount, expdest);
+ }
+ /* Remove any recorded regions beyond start of variable */
+ removerecordregions(startloc);
+ apply_ifs = 1;
+ break;
+
+ case VSASSIGN:
+ case VSQUESTION:
+ if (set)
+ break;
+ if (subevalvar(p, var, subtype, startloc, varflags)) {
+ /* if subevalvar() returns, it always returns 1 */
+
+ varflags &= ~VSNUL;
+ /*
+ * Remove any recorded regions beyond
+ * start of variable
+ */
+ removerecordregions(startloc);
+ goto again;
+ }
+ apply_ifs = 0; /* never executed */
+ break;
+
+ default:
+ abort();
+ }
+
+ if (apply_ifs)
+ recordregion(startloc, expdest - stackblock(),
+ varflags & VSQUOTE);
+
+ if (subtype != VSNORMAL) { /* skip to end of alternative */
+ int nesting = 1;
+ for (;;) {
+ if ((c = *p++) == CTLESC)
+ p++;
+ else if (c == CTLNONL)
+ ;
+ else if (c == CTLBACKQ || c == (CTLBACKQ|CTLQUOTE)) {
+ if (set)
+ argbackq = argbackq->next;
+ } else if (c == CTLVAR) {
+ if ((*p++ & VSTYPE) != VSNORMAL)
+ nesting++;
+ } else if (c == CTLENDVAR) {
+ if (--nesting == 0)
+ break;
+ }
+ }
+ }
+ return p;
+}
+
+
+
+/*
+ * Test whether a special parameter is set.
+ */
+
+STATIC int
+varisset(const char *name, int nulok)
+{
+ if (*name == '!')
+ return backgndpid != -1;
+ else if (*name == '@' || *name == '*') {
+ if (*shellparam.p == NULL)
+ return 0;
+
+ if (nulok) {
+ char **av;
+
+ for (av = shellparam.p; *av; av++)
+ if (**av != '\0')
+ return 1;
+ return 0;
+ }
+ } else if (is_digit(*name)) {
+ char *ap;
+ long num;
+
+ /*
+ * handle overflow sensibly (the *ap tests should never fail)
+ */
+ errno = 0;
+ num = strtol(name, &ap, 10);
+ if (errno != 0 || (*ap != '\0' && *ap != '='))
+ return 0;
+
+ if (num == 0)
+ ap = arg0;
+ else if (num > shellparam.nparam)
+ return 0;
+ else
+ ap = shellparam.p[num - 1];
+
+ if (nulok && (ap == NULL || *ap == '\0'))
+ return 0;
+ }
+ return 1;
+}
+
+
+
+/*
+ * Add the value of a specialized variable to the stack string.
+ */
+
+STATIC void
+varvalue(const char *name, int quoted, int subtype, int flag)
+{
+ int num;
+ char *p;
+ int i;
+ int sep;
+ char **ap;
+#ifdef DEBUG
+ char *start = expdest;
+#endif
+
+ VTRACE(DBG_EXPAND, ("varvalue(%c%s, sub=%d, fl=%#x)", *name,
+ quoted ? ", quoted" : "", subtype, flag));
+
+ if (subtype == VSLENGTH) /* no magic required ... */
+ flag &= ~EXP_FULL;
+
+#define STRTODEST(p) \
+ do {\
+ if ((flag & EXP_QNEEDED) && quoted) { \
+ while (*p) { \
+ if (NEEDESC(*p)) \
+ STPUTC(CTLESC, expdest); \
+ STPUTC(*p++, expdest); \
+ } \
+ } else \
+ while (*p) \
+ STPUTC(*p++, expdest); \
+ } while (0)
+
+
+ switch (*name) {
+ case '$':
+ num = rootpid;
+ break;
+ case '?':
+ num = exitstatus;
+ break;
+ case '#':
+ num = shellparam.nparam;
+ break;
+ case '!':
+ num = backgndpid;
+ break;
+ case '-':
+ for (i = 0; i < option_flags; i++) {
+ if (optlist[optorder[i]].val)
+ STPUTC(optlist[optorder[i]].letter, expdest);
+ }
+ VTRACE(DBG_EXPAND, (": %.*s\n", expdest-start, start));
+ return;
+ case '@':
+ if (flag & EXP_SPLIT && quoted) {
+ VTRACE(DBG_EXPAND, (": $@ split (%d)\n",
+ shellparam.nparam));
+ /* GROSS HACK */
+ if (shellparam.nparam == 0 &&
+ expdest[-1] == CTLQUOTEMARK)
+ expdest--;
+ /* KCAH SSORG */
+ for (ap = shellparam.p ; (p = *ap++) != NULL ; ) {
+ STRTODEST(p);
+ if (*ap)
+ /* A NUL separates args inside "" */
+ STPUTC('\0', expdest);
+ }
+ return;
+ }
+ /* fall through */
+ case '*':
+ sep = ifsval()[0];
+ for (ap = shellparam.p ; (p = *ap++) != NULL ; ) {
+ STRTODEST(p);
+ if (!*ap)
+ break;
+ if (sep) {
+ if (quoted && (flag & EXP_QNEEDED) &&
+ NEEDESC(sep))
+ STPUTC(CTLESC, expdest);
+ STPUTC(sep, expdest);
+ } else
+ if ((flag & (EXP_SPLIT|EXP_IN_QUOTES)) == EXP_SPLIT
+ && !quoted && **ap != '\0')
+ STPUTC('\0', expdest);
+ }
+ VTRACE(DBG_EXPAND, (": %.*s\n", expdest-start, start));
+ return;
+ default:
+ if (is_digit(*name)) {
+ long lnum;
+
+ errno = 0;
+ lnum = strtol(name, &p, 10);
+ if (errno != 0 || (*p != '\0' && *p != '='))
+ return;
+
+ if (lnum == 0)
+ p = arg0;
+ else if (lnum > 0 && lnum <= shellparam.nparam)
+ p = shellparam.p[lnum - 1];
+ else
+ return;
+ STRTODEST(p);
+ }
+ VTRACE(DBG_EXPAND, (": %.*s\n", expdest-start, start));
+ return;
+ }
+ /*
+ * only the specials with an int value arrive here
+ */
+ VTRACE(DBG_EXPAND, ("(%d)", num));
+ expdest = cvtnum(num, expdest);
+ VTRACE(DBG_EXPAND, (": %.*s\n", expdest-start, start));
+}
+
+
+
+/*
+ * Record the fact that we have to scan this region of the
+ * string for IFS characters.
+ */
+
+STATIC void
+recordregion(int start, int end, int inquotes)
+{
+ struct ifsregion *ifsp;
+
+ VTRACE(DBG_EXPAND, ("recordregion(%d,%d,%d)\n", start, end, inquotes));
+ if (ifslastp == NULL) {
+ ifsp = &ifsfirst;
+ } else {
+ if (ifslastp->endoff == start
+ && ifslastp->inquotes == inquotes) {
+ /* extend previous area */
+ ifslastp->endoff = end;
+ return;
+ }
+ ifsp = (struct ifsregion *)ckmalloc(sizeof (struct ifsregion));
+ ifslastp->next = ifsp;
+ }
+ ifslastp = ifsp;
+ ifslastp->next = NULL;
+ ifslastp->begoff = start;
+ ifslastp->endoff = end;
+ ifslastp->inquotes = inquotes;
+}
+
+
+
+/*
+ * Break the argument string into pieces based upon IFS and add the
+ * strings to the argument list. The regions of the string to be
+ * searched for IFS characters have been stored by recordregion.
+ */
+STATIC void
+ifsbreakup(char *string, struct arglist *arglist)
+{
+ struct ifsregion *ifsp;
+ struct strlist *sp;
+ char *start;
+ char *p;
+ char *q;
+ const char *ifs;
+ const char *ifsspc;
+ int had_param_ch = 0;
+
+ start = string;
+
+ VTRACE(DBG_EXPAND, ("ifsbreakup(\"%s\")", string)); /* misses \0's */
+ if (ifslastp == NULL) {
+ /* Return entire argument, IFS doesn't apply to any of it */
+ VTRACE(DBG_EXPAND, ("no regions\n", string));
+ sp = stalloc(sizeof(*sp));
+ sp->text = start;
+ *arglist->lastp = sp;
+ arglist->lastp = &sp->next;
+ return;
+ }
+
+ ifs = ifsval();
+
+ for (ifsp = &ifsfirst; ifsp != NULL; ifsp = ifsp->next) {
+ p = string + ifsp->begoff;
+ VTRACE(DBG_EXPAND, (" !%.*s!(%d)", ifsp->endoff-ifsp->begoff,
+ p, ifsp->endoff-ifsp->begoff));
+ while (p < string + ifsp->endoff) {
+ had_param_ch = 1;
+ q = p;
+ if (IS_BORING(*p)) {
+ p++;
+ continue;
+ }
+ if (*p == CTLESC)
+ p++;
+ if (ifsp->inquotes) {
+ /* Only NULs (should be from "$@") end args */
+ if (*p != 0) {
+ p++;
+ continue;
+ }
+ ifsspc = NULL;
+ VTRACE(DBG_EXPAND, (" \\0 nxt:\"%s\" ", p));
+ } else {
+ if (!strchr(ifs, *p)) {
+ p++;
+ continue;
+ }
+ had_param_ch = 0;
+ ifsspc = strchr(" \t\n", *p);
+
+ /* Ignore IFS whitespace at start */
+ if (q == start && ifsspc != NULL) {
+ p++;
+ start = p;
+ continue;
+ }
+ }
+
+ /* Save this argument... */
+ *q = '\0';
+ VTRACE(DBG_EXPAND, ("<%s>", start));
+ sp = stalloc(sizeof(*sp));
+ sp->text = start;
+ *arglist->lastp = sp;
+ arglist->lastp = &sp->next;
+ p++;
+
+ if (ifsspc != NULL) {
+ /* Ignore further trailing IFS whitespace */
+ for (; p < string + ifsp->endoff; p++) {
+ q = p;
+ if (*p == CTLNONL)
+ continue;
+ if (*p == CTLESC)
+ p++;
+ if (strchr(ifs, *p) == NULL) {
+ p = q;
+ break;
+ }
+ if (strchr(" \t\n", *p) == NULL) {
+ p++;
+ break;
+ }
+ }
+ }
+ start = p;
+ }
+ }
+
+ while (*start == CTLQUOTEEND)
+ start++;
+
+ /*
+ * Save anything left as an argument.
+ * Traditionally we have treated 'IFS=':'; set -- x$IFS' as
+ * generating 2 arguments, the second of which is empty.
+ * Some recent clarification of the Posix spec say that it
+ * should only generate one....
+ */
+ if (had_param_ch || *start != 0) {
+ VTRACE(DBG_EXPAND, (" T<%s>", start));
+ sp = stalloc(sizeof(*sp));
+ sp->text = start;
+ *arglist->lastp = sp;
+ arglist->lastp = &sp->next;
+ }
+ VTRACE(DBG_EXPAND, ("\n"));
+}
+
+STATIC void
+ifsfree(void)
+{
+ while (ifsfirst.next != NULL) {
+ struct ifsregion *ifsp;
+ INTOFF;
+ ifsp = ifsfirst.next->next;
+ ckfree(ifsfirst.next);
+ ifsfirst.next = ifsp;
+ INTON;
+ }
+ ifslastp = NULL;
+ ifsfirst.next = NULL;
+}
+
+
+
+/*
+ * Expand shell metacharacters. At this point, the only control characters
+ * should be escapes. The results are stored in the list exparg.
+ */
+
+char *expdir;
+
+
+STATIC void
+expandmeta(struct strlist *str, int flag)
+{
+ char *p;
+ struct strlist **savelastp;
+ struct strlist *sp;
+ char c;
+ /* TODO - EXP_REDIR */
+
+ while (str) {
+ p = str->text;
+ for (;;) { /* fast check for meta chars */
+ if ((c = *p++) == '\0')
+ goto nometa;
+ if (c == '*' || c == '?' || c == '[' /* || c == '!' */)
+ break;
+ }
+ savelastp = exparg.lastp;
+ INTOFF;
+ if (expdir == NULL) {
+ int i = strlen(str->text);
+ expdir = ckmalloc(i < 2048 ? 2048 : i); /* XXX */
+ }
+
+ expmeta(expdir, str->text);
+ ckfree(expdir);
+ expdir = NULL;
+ INTON;
+ if (exparg.lastp == savelastp) {
+ /*
+ * no matches
+ */
+ nometa:
+ *exparg.lastp = str;
+ rmescapes(str->text);
+ exparg.lastp = &str->next;
+ } else {
+ *exparg.lastp = NULL;
+ *savelastp = sp = expsort(*savelastp);
+ while (sp->next != NULL)
+ sp = sp->next;
+ exparg.lastp = &sp->next;
+ }
+ str = str->next;
+ }
+}
+
+STATIC void
+add_args(struct strlist *str)
+{
+ while (str) {
+ *exparg.lastp = str;
+ rmescapes(str->text);
+ exparg.lastp = &str->next;
+ str = str->next;
+ }
+}
+
+
+/*
+ * Do metacharacter (i.e. *, ?, [...]) expansion.
+ */
+
+STATIC void
+expmeta(char *enddir, char *name)
+{
+ char *p;
+ const char *cp;
+ char *q;
+ char *start;
+ char *endname;
+ int metaflag;
+ struct stat statb;
+ DIR *dirp;
+ struct dirent *dp;
+ int atend;
+ int matchdot;
+
+ CTRACE(DBG_EXPAND|DBG_MATCH, ("expmeta(\"%s\")\n", name));
+ metaflag = 0;
+ start = name;
+ for (p = name ; ; p++) {
+ if (*p == '*' || *p == '?')
+ metaflag = 1;
+ else if (*p == '[') {
+ q = p + 1;
+ if (*q == '!' || *q == '^')
+ q++;
+ for (;;) {
+ while (IS_BORING(*q))
+ q++;
+ if (*q == ']') {
+ q++;
+ metaflag = 1;
+ break;
+ }
+ if (*q == '[' && q[1] == ':') {
+ /*
+ * character class, look for :] ending
+ * also stop on ']' (end bracket expr)
+ * or '\0' or '/' (end pattern)
+ */
+ while (*++q != '\0' && *q != ']' &&
+ *q != '/') {
+ if (*q == CTLESC) {
+ if (*++q == '\0')
+ break;
+ if (*q == '/')
+ break;
+ } else if (*q == ':' &&
+ q[1] == ']')
+ break;
+ }
+ if (*q == ':') {
+ /*
+ * stopped at ':]'
+ * still in [...]
+ * skip ":]" and continue;
+ */
+ q += 2;
+ continue;
+ }
+
+ /* done at end of pattern, not [...] */
+ if (*q == '\0' || *q == '/')
+ break;
+
+ /* found the ']', we have a [...] */
+ metaflag = 1;
+ q++; /* skip ']' */
+ break;
+ }
+ if (*q == CTLESC)
+ q++;
+ /* end of pattern cannot be escaped */
+ if (*q == '/' || *q == '\0')
+ break;
+ q++;
+ }
+ } else if (*p == '\0')
+ break;
+ else if (IS_BORING(*p))
+ continue;
+ else if (*p == CTLESC)
+ p++;
+ if (*p == '/') {
+ if (metaflag)
+ break;
+ start = p + 1;
+ }
+ }
+ if (metaflag == 0) { /* we've reached the end of the file name */
+ if (enddir != expdir)
+ metaflag++;
+ for (p = name ; ; p++) {
+ if (IS_BORING(*p))
+ continue;
+ if (*p == CTLESC)
+ p++;
+ *enddir++ = *p;
+ if (*p == '\0')
+ break;
+ }
+ if (metaflag == 0 || lstat(expdir, &statb) >= 0)
+ addfname(expdir);
+ return;
+ }
+ endname = p;
+ if (start != name) {
+ p = name;
+ while (p < start) {
+ while (IS_BORING(*p))
+ p++;
+ if (*p == CTLESC)
+ p++;
+ *enddir++ = *p++;
+ }
+ }
+ if (enddir == expdir) {
+ cp = ".";
+ } else if (enddir == expdir + 1 && *expdir == '/') {
+ cp = "/";
+ } else {
+ cp = expdir;
+ enddir[-1] = '\0';
+ }
+ if ((dirp = opendir(cp)) == NULL)
+ return;
+ if (enddir != expdir)
+ enddir[-1] = '/';
+ if (*endname == 0) {
+ atend = 1;
+ } else {
+ atend = 0;
+ *endname++ = '\0';
+ }
+ matchdot = 0;
+ p = start;
+ while (IS_BORING(*p))
+ p++;
+ if (*p == CTLESC)
+ p++;
+ if (*p == '.')
+ matchdot++;
+ while (! int_pending() && (dp = readdir(dirp)) != NULL) {
+ if (dp->d_name[0] == '.' && ! matchdot)
+ continue;
+ if (patmatch(start, dp->d_name, 0)) {
+ if (atend) {
+ scopy(dp->d_name, enddir);
+ addfname(expdir);
+ } else {
+ for (p = enddir, cp = dp->d_name;
+ (*p++ = *cp++) != '\0';)
+ continue;
+ p[-1] = '/';
+ expmeta(p, endname);
+ }
+ }
+ }
+ closedir(dirp);
+ if (! atend)
+ endname[-1] = '/';
+}
+
+
+/*
+ * Add a file name to the list.
+ */
+
+STATIC void
+addfname(char *name)
+{
+ char *p;
+ struct strlist *sp;
+
+ p = stalloc(strlen(name) + 1);
+ scopy(name, p);
+ sp = stalloc(sizeof(*sp));
+ sp->text = p;
+ *exparg.lastp = sp;
+ exparg.lastp = &sp->next;
+}
+
+
+/*
+ * Sort the results of file name expansion. It calculates the number of
+ * strings to sort and then calls msort (short for merge sort) to do the
+ * work.
+ */
+
+STATIC struct strlist *
+expsort(struct strlist *str)
+{
+ int len;
+ struct strlist *sp;
+
+ len = 0;
+ for (sp = str ; sp ; sp = sp->next)
+ len++;
+ return msort(str, len);
+}
+
+
+STATIC struct strlist *
+msort(struct strlist *list, int len)
+{
+ struct strlist *p, *q = NULL;
+ struct strlist **lpp;
+ int half;
+ int n;
+
+ if (len <= 1)
+ return list;
+ half = len >> 1;
+ p = list;
+ for (n = half ; --n >= 0 ; ) {
+ q = p;
+ p = p->next;
+ }
+ q->next = NULL; /* terminate first half of list */
+ q = msort(list, half); /* sort first half of list */
+ p = msort(p, len - half); /* sort second half */
+ lpp = &list;
+ for (;;) {
+ if (strcmp(p->text, q->text) < 0) {
+ *lpp = p;
+ lpp = &p->next;
+ if ((p = *lpp) == NULL) {
+ *lpp = q;
+ break;
+ }
+ } else {
+ *lpp = q;
+ lpp = &q->next;
+ if ((q = *lpp) == NULL) {
+ *lpp = p;
+ break;
+ }
+ }
+ }
+ return list;
+}
+
+
+/*
+ * See if a character matches a character class, starting at the first colon
+ * of "[:class:]".
+ * If a valid character class is recognized, a pointer to the next character
+ * after the final closing bracket is stored into *end, otherwise a null
+ * pointer is stored into *end.
+ */
+static int
+match_charclass(const char *p, wchar_t chr, const char **end)
+{
+ char name[20];
+ char *nameend;
+ wctype_t cclass;
+
+ *end = NULL;
+ p++;
+ nameend = strstr(p, ":]");
+ if (nameend == NULL || nameend == p) /* not a valid class */
+ return 0;
+
+ if (!is_alpha(*p) || strspn(p, /* '_' is a local extension */
+ "0123456789" "_"
+ "abcdefghijklmnopqrstuvwxyz"
+ "ABCDEFGHIJKLMNOPQRSTUVWXYZ") != (size_t)(nameend - p))
+ return 0;
+
+ *end = nameend + 2; /* committed to it being a char class */
+ if ((size_t)(nameend - p) >= sizeof(name)) /* but too long */
+ return 0; /* so no match */
+ memcpy(name, p, nameend - p);
+ name[nameend - p] = '\0';
+ cclass = wctype(name);
+ /* An unknown class matches nothing but is valid nevertheless. */
+ if (cclass == 0)
+ return 0;
+ return iswctype(chr, cclass);
+}
+
+
+/*
+ * Returns true if the pattern matches the string.
+ */
+
+STATIC int
+patmatch(const char *pattern, const char *string, int squoted)
+{
+ const char *p, *q, *end;
+ const char *bt_p, *bt_q;
+ char c;
+ wchar_t wc, wc2;
+
+ VTRACE(DBG_MATCH, ("patmatch(P=\"%s\", W=\"%s\"%s): ",
+ pattern, string, squoted ? ", SQ" : ""));
+ p = pattern;
+ q = string;
+ bt_p = NULL;
+ bt_q = NULL;
+ for (;;) {
+ switch (c = *p++) {
+ case '\0':
+ if (squoted && *q == CTLESC) {
+ if (q[1] == '\0')
+ q++;
+ }
+ if (*q != '\0')
+ goto backtrack;
+ VTRACE(DBG_MATCH, ("match\n"));
+ return 1;
+ case CTLESC:
+ if (squoted && *q == CTLESC)
+ q++;
+ if (*p == '\0' && *q == '\0') {
+ VTRACE(DBG_MATCH, ("match-\\\n"));
+ return 1;
+ }
+ if (*q++ != *p++)
+ goto backtrack;
+ break;
+ case '\\':
+ if (squoted && *q == CTLESC)
+ q++;
+ if (*q++ != *p++)
+ goto backtrack;
+ break;
+ case CTLQUOTEMARK:
+ case CTLQUOTEEND:
+ case CTLNONL:
+ continue;
+ case '?':
+ if (squoted && *q == CTLESC)
+ q++;
+ if (*q++ == '\0') {
+ VTRACE(DBG_MATCH, ("?fail\n"));
+ return 0;
+ }
+ break;
+ case '*':
+ c = *p;
+ while (c == CTLQUOTEMARK || c == '*')
+ c = *++p;
+ if (c != CTLESC && !IS_BORING(c) &&
+ c != '?' && c != '*' && c != '[') {
+ while (*q != c) {
+ if (squoted && *q == CTLESC &&
+ q[1] == c)
+ break;
+ if (*q == '\0') {
+ VTRACE(DBG_MATCH, ("*fail\n"));
+ return 0;
+ }
+ if (squoted && *q == CTLESC)
+ q++;
+ q++;
+ }
+ }
+ if (c == CTLESC && p[1] == '\0') {
+ VTRACE(DBG_MATCH, ("match+\\\n"));
+ return 1;
+ }
+ /*
+ * First try the shortest match for the '*' that
+ * could work. We can forget any earlier '*' since
+ * there is no way having it match more characters
+ * can help us, given that we are already here.
+ */
+ bt_p = p;
+ bt_q = q;
+ break;
+ case '[': {
+ const char *savep, *saveq, *endp;
+ int invert, found;
+ unsigned char chr;
+
+ /*
+ * First quick check to see if there is a
+ * possible matching ']' - if not, then this
+ * is not a char class, and the '[' is just
+ * a literal '['.
+ *
+ * This check will not detect all non classes, but
+ * that's OK - It just means that we execute the
+ * harder code sometimes when it it cannot succeed.
+ */
+ endp = p;
+ if (*endp == '!' || *endp == '^')
+ endp++;
+ for (;;) {
+ while (IS_BORING(*endp))
+ endp++;
+ if (*endp == '\0')
+ goto dft; /* no matching ] */
+ if (*endp++ == ']')
+ break;
+ }
+ /* end shortcut */
+
+ invert = 0;
+ savep = p, saveq = q;
+ invert = 0;
+ if (*p == '!' || *p == '^') {
+ invert++;
+ p++;
+ }
+ found = 0;
+ if (*q == '\0') {
+ VTRACE(DBG_MATCH, ("[]fail\n"));
+ return 0;
+ }
+ if (squoted && *q == CTLESC)
+ q++;
+ chr = (unsigned char)*q++;
+ c = *p++;
+ do {
+ if (IS_BORING(c))
+ continue;
+ if (c == '\0') {
+ p = savep, q = saveq;
+ c = '[';
+ goto dft;
+ }
+ if (c == '[' && *p == ':') {
+ found |= match_charclass(p, chr, &end);
+ if (end != NULL) {
+ p = end;
+ continue;
+ }
+ }
+ if (c == CTLESC || c == '\\')
+ c = *p++;
+ wc = (unsigned char)c;
+ if (*p == '-' && p[1] != ']') {
+ p++;
+ if (*p == CTLESC || *p == '\\')
+ p++;
+ wc2 = (unsigned char)*p++;
+ if ( collate_range_cmp(chr, wc) >= 0
+ && collate_range_cmp(chr, wc2) <= 0
+ )
+ found = 1;
+ } else {
+ if (chr == wc)
+ found = 1;
+ }
+ } while ((c = *p++) != ']');
+ if (found == invert)
+ goto backtrack;
+ break;
+ }
+ dft: default:
+ if (squoted && *q == CTLESC)
+ q++;
+ if (*q++ == c)
+ break;
+ backtrack:
+ /*
+ * If we have a mismatch (other than hitting the end
+ * of the string), go back to the last '*' seen and
+ * have it match one additional character.
+ */
+ if (bt_p == NULL) {
+ VTRACE(DBG_MATCH, ("BTP fail\n"));
+ return 0;
+ }
+ if (*bt_q == '\0') {
+ VTRACE(DBG_MATCH, ("BTQ fail\n"));
+ return 0;
+ }
+ bt_q++;
+ p = bt_p;
+ q = bt_q;
+ break;
+ }
+ }
+}
+
+
+
+/*
+ * Remove any CTLESC or CTLNONL characters from a string.
+ */
+
+void
+rmescapes(char *str)
+{
+ char *p, *q;
+
+ p = str;
+ while (*p != CTLESC && !IS_BORING(*p)) {
+ if (*p++ == '\0')
+ return;
+ }
+ q = p;
+ while (*p) {
+ if (IS_BORING(*p)) {
+ p++;
+ continue;
+ }
+ if (*p == CTLCNL) {
+ p++;
+ *q++ = '\n';
+ continue;
+ }
+ if (*p == CTLESC)
+ p++;
+ *q++ = *p++;
+ }
+ *q = '\0';
+}
+
+/*
+ * and a special version for dealing with expressions to be parsed
+ * by the arithmetic evaluator. That needs to be able to count \n's
+ * even ones that were \newline elided \n's, so we have to put the
+ * latter back into the string - just being careful to put them only
+ * at a place where white space can reasonably occur in the string
+ * -- then the \n we insert will just be white space, and ignored
+ * for all purposes except line counting.
+ */
+
+void
+rmescapes_nl(char *str)
+{
+ char *p, *q;
+ int nls = 0, holdnl = 0, holdlast;
+
+ p = str;
+ while (*p != CTLESC && !IS_BORING(*p)) {
+ if (*p++ == '\0')
+ return;
+ }
+ if (p > str) /* must reprocess char before stopper (if any) */
+ --p; /* so we do not place a \n badly */
+ q = p;
+ while (*p) {
+ if (*p == CTLQUOTEMARK || *p == CTLQUOTEEND) {
+ p++;
+ continue;
+ }
+ if (*p == CTLNONL) {
+ p++;
+ nls++;
+ continue;
+ }
+ if (*p == CTLCNL) {
+ p++;
+ *q++ = '\n';
+ continue;
+ }
+ if (*p == CTLESC)
+ p++;
+
+ holdlast = holdnl;
+ holdnl = is_in_name(*p); /* letters, digits, _ */
+ if (q == str || is_space(q[-1]) || (*p != '=' && q[-1] != *p)) {
+ if (nls > 0 && holdnl != holdlast) {
+ while (nls > 0)
+ *q++ = '\n', nls--;
+ }
+ }
+ *q++ = *p++;
+ }
+ while (--nls >= 0)
+ *q++ = '\n';
+ *q = '\0';
+}
+
+
+
+/*
+ * See if a pattern matches in a case statement.
+ */
+
+int
+casematch(union node *pattern, char *val)
+{
+ struct stackmark smark;
+ int result;
+ char *p;
+
+ CTRACE(DBG_MATCH, ("casematch(P=\"%s\", W=\"%s\")\n",
+ pattern->narg.text, val));
+ setstackmark(&smark);
+ argbackq = pattern->narg.backquote;
+ STARTSTACKSTR(expdest);
+ ifslastp = NULL;
+ argstr(pattern->narg.text, EXP_TILDE | EXP_CASE);
+ STPUTC('\0', expdest);
+ p = grabstackstr(expdest);
+ result = patmatch(p, val, 0);
+ popstackmark(&smark);
+ return result;
+}
+
+/*
+ * Our own itoa(). Assumes result buffer is on the stack
+ */
+
+STATIC char *
+cvtnum(int num, char *buf)
+{
+ char temp[32];
+ int neg = num < 0;
+ char *p = temp + sizeof temp - 1;
+
+ if (neg)
+ num = -num;
+
+ *p = '\0';
+ do {
+ *--p = num % 10 + '0';
+ } while ((num /= 10) != 0 && p > temp + 1);
+
+ if (neg)
+ *--p = '-';
+
+ while (*p)
+ STPUTC(*p++, buf);
+ return buf;
+}
+
+/*
+ * Do most of the work for wordexp(3).
+ */
+
+int
+wordexpcmd(int argc, char **argv)
+{
+ size_t len;
+ int i;
+
+ out1fmt("%d", argc - 1);
+ out1c('\0');
+ for (i = 1, len = 0; i < argc; i++)
+ len += strlen(argv[i]);
+ out1fmt("%zu", len);
+ out1c('\0');
+ for (i = 1; i < argc; i++) {
+ out1str(argv[i]);
+ out1c('\0');
+ }
+ return (0);
+}
diff --git a/bin/sh/expand.h b/bin/sh/expand.h
new file mode 100644
index 0000000..d435fd8
--- /dev/null
+++ b/bin/sh/expand.h
@@ -0,0 +1,71 @@
+/* $NetBSD: expand.h,v 1.24 2018/11/18 17:23:37 kre Exp $ */
+
+/*-
+ * Copyright (c) 1991, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Kenneth Almquist.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * @(#)expand.h 8.2 (Berkeley) 5/4/95
+ */
+
+#include <inttypes.h>
+
+struct strlist {
+ struct strlist *next;
+ char *text;
+};
+
+
+struct arglist {
+ struct strlist *list;
+ struct strlist **lastp;
+};
+
+/*
+ * expandarg() flags
+ */
+#define EXP_SPLIT 0x1 /* perform word splitting */
+#define EXP_TILDE 0x2 /* do normal tilde expansion */
+#define EXP_VARTILDE 0x4 /* expand tildes in an assignment */
+#define EXP_REDIR 0x8 /* file glob for a redirection (1 match only) */
+#define EXP_CASE 0x10 /* keeps quotes around for CASE pattern */
+#define EXP_IFS_SPLIT 0x20 /* need to record arguments for ifs breakup */
+#define EXP_IN_QUOTES 0x40 /* don't set EXP_IFS_SPLIT again */
+#define EXP_GLOB 0x80 /* perform filename globbing */
+#define EXP_NL 0x100 /* keep CRTNONL in output */
+
+#define EXP_FULL (EXP_SPLIT | EXP_GLOB)
+#define EXP_QNEEDED (EXP_GLOB | EXP_CASE | EXP_REDIR)
+
+union node;
+
+void expandhere(union node *, int);
+void expandarg(union node *, struct arglist *, int);
+void rmescapes(char *);
+int casematch(union node *, char *);
diff --git a/bin/sh/funcs/cmv b/bin/sh/funcs/cmv
new file mode 100644
index 0000000..0e3eef6
--- /dev/null
+++ b/bin/sh/funcs/cmv
@@ -0,0 +1,43 @@
+# $NetBSD: cmv,v 1.8 2016/02/29 23:50:59 christos Exp $
+# Copyright (c) 1991, 1993
+# The Regents of the University of California. All rights reserved.
+#
+# This code is derived from software contributed to Berkeley by
+# Kenneth Almquist.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1. Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+# ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+# SUCH DAMAGE.
+#
+# @(#)cmv 8.2 (Berkeley) 5/4/95
+
+# Conditional move--don't replace an existing file.
+
+cmv() {
+ if test $# != 2
+ then echo "cmv: arg count"
+ return 2
+ fi
+ if test -f "$2" -o -w "$2"
+ then echo "$2 exists"
+ return 2
+ fi
+ /bin/mv "$1" "$2"
+}
diff --git a/bin/sh/funcs/dirs b/bin/sh/funcs/dirs
new file mode 100644
index 0000000..ef2ae0a
--- /dev/null
+++ b/bin/sh/funcs/dirs
@@ -0,0 +1,67 @@
+# $NetBSD: dirs,v 1.8 2016/02/29 23:50:59 christos Exp $
+# Copyright (c) 1991, 1993
+# The Regents of the University of California. All rights reserved.
+#
+# This code is derived from software contributed to Berkeley by
+# Kenneth Almquist.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1. Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+# ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+# SUCH DAMAGE.
+#
+# @(#)dirs 8.2 (Berkeley) 5/4/95
+
+# pushd, popd, and dirs --- written by Chris Bertin
+# Pixel Computer Inc. ...!wjh12!pixel!pixutl!chris
+# as modified by Patrick Elam of GTRI and Kenneth Almquist at UW
+
+pushd () {
+ SAVE=`pwd`
+ if [ "$1" = "" ]
+ then if [ "$DSTACK" = "" ]
+ then echo "pushd: directory stack empty."
+ return 1
+ fi
+ set $DSTACK
+ cd $1 || return
+ shift 1
+ DSTACK="$*"
+ else cd $1 > /dev/null || return
+ fi
+ DSTACK="$SAVE $DSTACK"
+ dirs
+}
+
+popd () {
+ if [ "$DSTACK" = "" ]
+ then echo "popd: directory stack empty."
+ return 1
+ fi
+ set $DSTACK
+ cd $1
+ shift
+ DSTACK=$*
+ dirs
+}
+
+dirs () {
+ echo "`pwd` $DSTACK"
+ return 0
+}
diff --git a/bin/sh/funcs/kill b/bin/sh/funcs/kill
new file mode 100644
index 0000000..70f2b27
--- /dev/null
+++ b/bin/sh/funcs/kill
@@ -0,0 +1,43 @@
+# $NetBSD: kill,v 1.8 2016/02/29 23:50:59 christos Exp $
+# Copyright (c) 1991, 1993
+# The Regents of the University of California. All rights reserved.
+#
+# This code is derived from software contributed to Berkeley by
+# Kenneth Almquist.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1. Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+# ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+# SUCH DAMAGE.
+#
+# @(#)kill 8.2 (Berkeley) 5/4/95
+
+# Convert job names to process ids and then run /bin/kill.
+
+kill() {
+ local args x
+ args=
+ for x in "$@"
+ do case $x in
+ %*) x=`jobid "$x"` ;;
+ esac
+ args="$args $x"
+ done
+ /bin/kill $args
+}
diff --git a/bin/sh/funcs/login b/bin/sh/funcs/login
new file mode 100644
index 0000000..a2fe60e
--- /dev/null
+++ b/bin/sh/funcs/login
@@ -0,0 +1,32 @@
+# $NetBSD: login,v 1.8 2016/02/29 23:50:59 christos Exp $
+# Copyright (c) 1991, 1993
+# The Regents of the University of California. All rights reserved.
+#
+# This code is derived from software contributed to Berkeley by
+# Kenneth Almquist.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1. Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+# ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+# SUCH DAMAGE.
+#
+# @(#)login 8.2 (Berkeley) 5/4/95
+
+# replaces the login builtin in the BSD shell
+login () exec login "$@"
diff --git a/bin/sh/funcs/newgrp b/bin/sh/funcs/newgrp
new file mode 100644
index 0000000..1ad46a3
--- /dev/null
+++ b/bin/sh/funcs/newgrp
@@ -0,0 +1,31 @@
+# $NetBSD: newgrp,v 1.8 2016/02/29 23:50:59 christos Exp $
+# Copyright (c) 1991, 1993
+# The Regents of the University of California. All rights reserved.
+#
+# This code is derived from software contributed to Berkeley by
+# Kenneth Almquist.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1. Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+# ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+# SUCH DAMAGE.
+#
+# @(#)newgrp 8.2 (Berkeley) 5/4/95
+
+newgrp() exec newgrp "$@"
diff --git a/bin/sh/funcs/popd b/bin/sh/funcs/popd
new file mode 100644
index 0000000..836741c
--- /dev/null
+++ b/bin/sh/funcs/popd
@@ -0,0 +1,67 @@
+# $NetBSD: popd,v 1.8 2016/02/29 23:50:59 christos Exp $
+# Copyright (c) 1991, 1993
+# The Regents of the University of California. All rights reserved.
+#
+# This code is derived from software contributed to Berkeley by
+# Kenneth Almquist.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1. Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+# ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+# SUCH DAMAGE.
+#
+# @(#)popd 8.2 (Berkeley) 5/4/95
+
+# pushd, popd, and dirs --- written by Chris Bertin
+# Pixel Computer Inc. ...!wjh12!pixel!pixutl!chris
+# as modified by Patrick Elam of GTRI and Kenneth Almquist at UW
+
+pushd () {
+ SAVE=`pwd`
+ if [ "$1" = "" ]
+ then if [ "$DSTACK" = "" ]
+ then echo "pushd: directory stack empty."
+ return 1
+ fi
+ set $DSTACK
+ cd $1 || return
+ shift 1
+ DSTACK="$*"
+ else cd $1 > /dev/null || return
+ fi
+ DSTACK="$SAVE $DSTACK"
+ dirs
+}
+
+popd () {
+ if [ "$DSTACK" = "" ]
+ then echo "popd: directory stack empty."
+ return 1
+ fi
+ set $DSTACK
+ cd $1
+ shift
+ DSTACK=$*
+ dirs
+}
+
+dirs () {
+ echo "`pwd` $DSTACK"
+ return 0
+}
diff --git a/bin/sh/funcs/pushd b/bin/sh/funcs/pushd
new file mode 100644
index 0000000..c6ec1af
--- /dev/null
+++ b/bin/sh/funcs/pushd
@@ -0,0 +1,67 @@
+# $NetBSD: pushd,v 1.8 2016/02/29 23:50:59 christos Exp $
+# Copyright (c) 1991, 1993
+# The Regents of the University of California. All rights reserved.
+#
+# This code is derived from software contributed to Berkeley by
+# Kenneth Almquist.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1. Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+# ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+# SUCH DAMAGE.
+#
+# @(#)pushd 8.2 (Berkeley) 5/4/95
+
+# pushd, popd, and dirs --- written by Chris Bertin
+# Pixel Computer Inc. ...!wjh12!pixel!pixutl!chris
+# as modified by Patrick Elam of GTRI and Kenneth Almquist at UW
+
+pushd () {
+ SAVE=`pwd`
+ if [ "$1" = "" ]
+ then if [ "$DSTACK" = "" ]
+ then echo "pushd: directory stack empty."
+ return 1
+ fi
+ set $DSTACK
+ cd $1 || return
+ shift 1
+ DSTACK="$*"
+ else cd $1 > /dev/null || return
+ fi
+ DSTACK="$SAVE $DSTACK"
+ dirs
+}
+
+popd () {
+ if [ "$DSTACK" = "" ]
+ then echo "popd: directory stack empty."
+ return 1
+ fi
+ set $DSTACK
+ cd $1
+ shift
+ DSTACK=$*
+ dirs
+}
+
+dirs () {
+ echo "`pwd` $DSTACK"
+ return 0
+}
diff --git a/bin/sh/funcs/suspend b/bin/sh/funcs/suspend
new file mode 100644
index 0000000..7643f43
--- /dev/null
+++ b/bin/sh/funcs/suspend
@@ -0,0 +1,35 @@
+# $NetBSD: suspend,v 1.8 2016/02/29 23:50:59 christos Exp $
+# Copyright (c) 1991, 1993
+# The Regents of the University of California. All rights reserved.
+#
+# This code is derived from software contributed to Berkeley by
+# Kenneth Almquist.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1. Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+# ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+# SUCH DAMAGE.
+#
+# @(#)suspend 8.2 (Berkeley) 5/4/95
+
+suspend() {
+ local -
+ set +j
+ kill -TSTP 0
+}
diff --git a/bin/sh/histedit.c b/bin/sh/histedit.c
new file mode 100644
index 0000000..5de3adc
--- /dev/null
+++ b/bin/sh/histedit.c
@@ -0,0 +1,576 @@
+/* $NetBSD: histedit.c,v 1.53 2018/07/13 22:43:44 kre Exp $ */
+
+/*-
+ * Copyright (c) 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Kenneth Almquist.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+#ifndef lint
+#if 0
+static char sccsid[] = "@(#)histedit.c 8.2 (Berkeley) 5/4/95";
+#else
+__RCSID("$NetBSD: histedit.c,v 1.53 2018/07/13 22:43:44 kre Exp $");
+#endif
+#endif /* not lint */
+
+#include <sys/param.h>
+#include <paths.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+/*
+ * Editline and history functions (and glue).
+ */
+#include "shell.h"
+#include "parser.h"
+#include "var.h"
+#include "options.h"
+#include "builtins.h"
+#include "main.h"
+#include "output.h"
+#include "mystring.h"
+#include "myhistedit.h"
+#include "error.h"
+#include "alias.h"
+#ifndef SMALL
+#include "eval.h"
+#include "memalloc.h"
+
+#define MAXHISTLOOPS 4 /* max recursions through fc */
+#define DEFEDITOR "ed" /* default editor *should* be $EDITOR */
+
+History *hist; /* history cookie */
+EditLine *el; /* editline cookie */
+int displayhist;
+static FILE *el_in, *el_out;
+unsigned char _el_fn_complete(EditLine *, int);
+
+STATIC const char *fc_replace(const char *, char *, char *);
+
+#ifdef DEBUG
+extern FILE *tracefile;
+#endif
+
+/*
+ * Set history and editing status. Called whenever the status may
+ * have changed (figures out what to do).
+ */
+void
+histedit(void)
+{
+ FILE *el_err;
+
+#define editing (Eflag || Vflag)
+
+ if (iflag == 1) {
+ if (!hist) {
+ /*
+ * turn history on
+ */
+ INTOFF;
+ hist = history_init();
+ INTON;
+
+ if (hist != NULL)
+ sethistsize(histsizeval());
+ else
+ out2str("sh: can't initialize history\n");
+ }
+ if (editing && !el && isatty(0)) { /* && isatty(2) ??? */
+ /*
+ * turn editing on
+ */
+ char *term, *shname;
+
+ INTOFF;
+ if (el_in == NULL)
+ el_in = fdopen(0, "r");
+ if (el_out == NULL)
+ el_out = fdopen(2, "w");
+ if (el_in == NULL || el_out == NULL)
+ goto bad;
+ el_err = el_out;
+#if DEBUG
+ if (tracefile)
+ el_err = tracefile;
+#endif
+ term = lookupvar("TERM");
+ if (term)
+ setenv("TERM", term, 1);
+ else
+ unsetenv("TERM");
+ shname = arg0;
+ if (shname[0] == '-')
+ shname++;
+ el = el_init(shname, el_in, el_out, el_err);
+ if (el != NULL) {
+ if (hist)
+ el_set(el, EL_HIST, history, hist);
+
+ set_prompt_lit(lookupvar("PSlit"));
+ el_set(el, EL_SIGNAL, 1);
+ el_set(el, EL_ALIAS_TEXT, alias_text, NULL);
+ el_set(el, EL_ADDFN, "rl-complete",
+ "ReadLine compatible completion function",
+ _el_fn_complete);
+ } else {
+bad:
+ out2str("sh: can't initialize editing\n");
+ }
+ INTON;
+ } else if (!editing && el) {
+ INTOFF;
+ el_end(el);
+ el = NULL;
+ INTON;
+ }
+ if (el) {
+ if (Vflag)
+ el_set(el, EL_EDITOR, "vi");
+ else if (Eflag)
+ el_set(el, EL_EDITOR, "emacs");
+ el_set(el, EL_BIND, "^I",
+ tabcomplete ? "rl-complete" : "ed-insert", NULL);
+ el_source(el, lookupvar("EDITRC"));
+ }
+ } else {
+ INTOFF;
+ if (el) { /* no editing if not interactive */
+ el_end(el);
+ el = NULL;
+ }
+ if (hist) {
+ history_end(hist);
+ hist = NULL;
+ }
+ INTON;
+ }
+}
+
+void
+set_prompt_lit(const char *lit_ch)
+{
+ wchar_t wc;
+
+ if (!(iflag && editing && el))
+ return;
+
+ if (lit_ch == NULL) {
+ el_set(el, EL_PROMPT, getprompt);
+ return;
+ }
+
+ mbtowc(&wc, NULL, 1); /* state init */
+
+ if (mbtowc(&wc, lit_ch, strlen(lit_ch)) <= 0)
+ el_set(el, EL_PROMPT, getprompt);
+ else
+ el_set(el, EL_PROMPT_ESC, getprompt, (int)wc);
+}
+
+void
+set_editrc(const char *fname)
+{
+ if (iflag && editing && el)
+ el_source(el, fname);
+}
+
+void
+sethistsize(const char *hs)
+{
+ int histsize;
+ HistEvent he;
+
+ if (hist != NULL) {
+ if (hs == NULL || *hs == '\0' || *hs == '-' ||
+ (histsize = number(hs)) < 0)
+ histsize = 100;
+ history(hist, &he, H_SETSIZE, histsize);
+ history(hist, &he, H_SETUNIQUE, 1);
+ }
+}
+
+void
+setterm(const char *term)
+{
+ if (el != NULL && term != NULL)
+ if (el_set(el, EL_TERMINAL, term) != 0) {
+ outfmt(out2, "sh: Can't set terminal type %s\n", term);
+ outfmt(out2, "sh: Using dumb terminal settings.\n");
+ }
+}
+
+int
+inputrc(int argc, char **argv)
+{
+ if (argc != 2) {
+ out2str("usage: inputrc file\n");
+ return 1;
+ }
+ if (el != NULL) {
+ if (el_source(el, argv[1])) {
+ out2str("inputrc: failed\n");
+ return 1;
+ } else
+ return 0;
+ } else {
+ out2str("sh: inputrc ignored, not editing\n");
+ return 1;
+ }
+}
+
+/*
+ * This command is provided since POSIX decided to standardize
+ * the Korn shell fc command. Oh well...
+ */
+int
+histcmd(volatile int argc, char ** volatile argv)
+{
+ int ch;
+ const char * volatile editor = NULL;
+ HistEvent he;
+ volatile int lflg = 0, nflg = 0, rflg = 0, sflg = 0;
+ int i, retval;
+ const char *firststr, *laststr;
+ int first, last, direction;
+ char * volatile pat = NULL, * volatile repl; /* ksh "fc old=new" crap */
+ static int active = 0;
+ struct jmploc jmploc;
+ struct jmploc *volatile savehandler;
+ char editfile[MAXPATHLEN + 1];
+ FILE * volatile efp;
+#ifdef __GNUC__
+ repl = NULL; /* XXX gcc4 */
+ efp = NULL; /* XXX gcc4 */
+#endif
+
+ if (hist == NULL)
+ error("history not active");
+
+ if (argc == 1)
+ error("missing history argument");
+
+ optreset = 1; optind = 1; /* initialize getopt */
+ while (not_fcnumber(argv[optind]) &&
+ (ch = getopt(argc, argv, ":e:lnrs")) != -1)
+ switch ((char)ch) {
+ case 'e':
+ editor = optionarg;
+ break;
+ case 'l':
+ lflg = 1;
+ break;
+ case 'n':
+ nflg = 1;
+ break;
+ case 'r':
+ rflg = 1;
+ break;
+ case 's':
+ sflg = 1;
+ break;
+ case ':':
+ error("option -%c expects argument", optopt);
+ /* NOTREACHED */
+ case '?':
+ default:
+ error("unknown option: -%c", optopt);
+ /* NOTREACHED */
+ }
+ argc -= optind, argv += optind;
+
+ /*
+ * If executing...
+ */
+ if (lflg == 0 || editor || sflg) {
+ lflg = 0; /* ignore */
+ editfile[0] = '\0';
+ /*
+ * Catch interrupts to reset active counter and
+ * cleanup temp files.
+ */
+ savehandler = handler;
+ if (setjmp(jmploc.loc)) {
+ active = 0;
+ if (*editfile)
+ unlink(editfile);
+ handler = savehandler;
+ longjmp(handler->loc, 1);
+ }
+ handler = &jmploc;
+ if (++active > MAXHISTLOOPS) {
+ active = 0;
+ displayhist = 0;
+ error("called recursively too many times");
+ }
+ /*
+ * Set editor.
+ */
+ if (sflg == 0) {
+ if (editor == NULL &&
+ (editor = bltinlookup("FCEDIT", 1)) == NULL &&
+ (editor = bltinlookup("EDITOR", 1)) == NULL)
+ editor = DEFEDITOR;
+ if (editor[0] == '-' && editor[1] == '\0') {
+ sflg = 1; /* no edit */
+ editor = NULL;
+ }
+ }
+ }
+
+ /*
+ * If executing, parse [old=new] now
+ */
+ if (lflg == 0 && argc > 0 &&
+ ((repl = strchr(argv[0], '=')) != NULL)) {
+ pat = argv[0];
+ *repl++ = '\0';
+ argc--, argv++;
+ }
+
+ /*
+ * If -s is specified, accept only one operand
+ */
+ if (sflg && argc >= 2)
+ error("too many args");
+
+ /*
+ * determine [first] and [last]
+ */
+ switch (argc) {
+ case 0:
+ firststr = lflg ? "-16" : "-1";
+ laststr = "-1";
+ break;
+ case 1:
+ firststr = argv[0];
+ laststr = lflg ? "-1" : argv[0];
+ break;
+ case 2:
+ firststr = argv[0];
+ laststr = argv[1];
+ break;
+ default:
+ error("too many args");
+ /* NOTREACHED */
+ }
+ /*
+ * Turn into event numbers.
+ */
+ first = str_to_event(firststr, 0);
+ last = str_to_event(laststr, 1);
+
+ if (rflg) {
+ i = last;
+ last = first;
+ first = i;
+ }
+ /*
+ * XXX - this should not depend on the event numbers
+ * always increasing. Add sequence numbers or offset
+ * to the history element in next (diskbased) release.
+ */
+ direction = first < last ? H_PREV : H_NEXT;
+
+ /*
+ * If editing, grab a temp file.
+ */
+ if (editor) {
+ int fd;
+ INTOFF; /* easier */
+ snprintf(editfile, sizeof(editfile), "%s_shXXXXXX", _PATH_TMP);
+ if ((fd = mkstemp(editfile)) < 0)
+ error("can't create temporary file %s", editfile);
+ if ((efp = fdopen(fd, "w")) == NULL) {
+ close(fd);
+ error("can't allocate stdio buffer for temp");
+ }
+ }
+
+ /*
+ * Loop through selected history events. If listing or executing,
+ * do it now. Otherwise, put into temp file and call the editor
+ * after.
+ *
+ * The history interface needs rethinking, as the following
+ * convolutions will demonstrate.
+ */
+ history(hist, &he, H_FIRST);
+ retval = history(hist, &he, H_NEXT_EVENT, first);
+ for (;retval != -1; retval = history(hist, &he, direction)) {
+ if (lflg) {
+ if (!nflg)
+ out1fmt("%5d ", he.num);
+ out1str(he.str);
+ } else {
+ const char *s = pat ?
+ fc_replace(he.str, pat, repl) : he.str;
+
+ if (sflg) {
+ if (displayhist) {
+ out2str(s);
+ }
+
+ evalstring(strcpy(stalloc(strlen(s) + 1), s), 0);
+ if (displayhist && hist) {
+ /*
+ * XXX what about recursive and
+ * relative histnums.
+ */
+ history(hist, &he, H_ENTER, s);
+ }
+
+ break;
+ } else
+ fputs(s, efp);
+ }
+ /*
+ * At end? (if we were to lose last, we'd sure be
+ * messed up).
+ */
+ if (he.num == last)
+ break;
+ }
+ if (editor) {
+ char *editcmd;
+ size_t cmdlen;
+
+ fclose(efp);
+ cmdlen = strlen(editor) + strlen(editfile) + 2;
+ editcmd = stalloc(cmdlen);
+ snprintf(editcmd, cmdlen, "%s %s", editor, editfile);
+ evalstring(editcmd, 0); /* XXX - should use no JC command */
+ INTON;
+ readcmdfile(editfile); /* XXX - should read back - quick tst */
+ unlink(editfile);
+ }
+
+ if (lflg == 0 && active > 0)
+ --active;
+ if (displayhist)
+ displayhist = 0;
+ return 0;
+}
+
+STATIC const char *
+fc_replace(const char *s, char *p, char *r)
+{
+ char *dest;
+ int plen = strlen(p);
+
+ STARTSTACKSTR(dest);
+ while (*s) {
+ if (*s == *p && strncmp(s, p, plen) == 0) {
+ while (*r)
+ STPUTC(*r++, dest);
+ s += plen;
+ *p = '\0'; /* so no more matches */
+ } else
+ STPUTC(*s++, dest);
+ }
+ STACKSTRNUL(dest);
+ dest = grabstackstr(dest);
+
+ return (dest);
+}
+
+int
+not_fcnumber(char *s)
+{
+ if (s == NULL)
+ return 0;
+ if (*s == '-')
+ s++;
+ return (!is_number(s));
+}
+
+int
+str_to_event(const char *str, int last)
+{
+ HistEvent he;
+ const char *s = str;
+ int relative = 0;
+ int i, retval;
+
+ retval = history(hist, &he, H_FIRST);
+ switch (*s) {
+ case '-':
+ relative = 1;
+ /*FALLTHROUGH*/
+ case '+':
+ s++;
+ }
+ if (is_number(s)) {
+ i = number(s);
+ if (relative) {
+ while (retval != -1 && i--) {
+ retval = history(hist, &he, H_NEXT);
+ }
+ if (retval == -1)
+ retval = history(hist, &he, H_LAST);
+ } else {
+ retval = history(hist, &he, H_NEXT_EVENT, i);
+ if (retval == -1) {
+ /*
+ * the notion of first and last is
+ * backwards to that of the history package
+ */
+ retval = history(hist, &he,
+ last ? H_FIRST : H_LAST);
+ }
+ }
+ if (retval == -1)
+ error("history number %s not found (internal error)",
+ str);
+ } else {
+ /*
+ * pattern
+ */
+ retval = history(hist, &he, H_PREV_STR, str);
+ if (retval == -1)
+ error("history pattern not found: %s", str);
+ }
+ return (he.num);
+}
+#else
+int
+histcmd(int argc, char **argv)
+{
+ error("not compiled with history support");
+ /* NOTREACHED */
+}
+int
+inputrc(int argc, char **argv)
+{
+ error("not compiled with history support");
+ /* NOTREACHED */
+}
+#endif
diff --git a/bin/sh/init.h b/bin/sh/init.h
new file mode 100644
index 0000000..60d924e
--- /dev/null
+++ b/bin/sh/init.h
@@ -0,0 +1,39 @@
+/* $NetBSD: init.h,v 1.10 2003/08/07 09:05:32 agc Exp $ */
+
+/*-
+ * Copyright (c) 1991, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Kenneth Almquist.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * @(#)init.h 8.2 (Berkeley) 5/4/95
+ */
+
+void init(void);
+void reset(void);
+void initshellproc(void);
diff --git a/bin/sh/input.c b/bin/sh/input.c
new file mode 100644
index 0000000..dc686f5
--- /dev/null
+++ b/bin/sh/input.c
@@ -0,0 +1,695 @@
+/* $NetBSD: input.c,v 1.69 2019/01/16 07:14:17 kre Exp $ */
+
+/*-
+ * Copyright (c) 1991, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Kenneth Almquist.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+#ifndef lint
+#if 0
+static char sccsid[] = "@(#)input.c 8.3 (Berkeley) 6/9/95";
+#else
+__RCSID("$NetBSD: input.c,v 1.69 2019/01/16 07:14:17 kre Exp $");
+#endif
+#endif /* not lint */
+
+#include <stdio.h> /* defines BUFSIZ */
+#include <fcntl.h>
+#include <errno.h>
+#include <unistd.h>
+#include <limits.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/stat.h>
+
+/*
+ * This file implements the input routines used by the parser.
+ */
+
+#include "shell.h"
+#include "redir.h"
+#include "syntax.h"
+#include "input.h"
+#include "output.h"
+#include "options.h"
+#include "memalloc.h"
+#include "error.h"
+#include "alias.h"
+#include "parser.h"
+#include "myhistedit.h"
+#include "show.h"
+
+#define EOF_NLEFT -99 /* value of parsenleft when EOF pushed back */
+
+MKINIT
+struct strpush {
+ struct strpush *prev; /* preceding string on stack */
+ const char *prevstring;
+ int prevnleft;
+ int prevlleft;
+ struct alias *ap; /* if push was associated with an alias */
+};
+
+/*
+ * The parsefile structure pointed to by the global variable parsefile
+ * contains information about the current file being read.
+ */
+
+MKINIT
+struct parsefile {
+ struct parsefile *prev; /* preceding file on stack */
+ int linno; /* current line */
+ int fd; /* file descriptor (or -1 if string) */
+ int nleft; /* number of chars left in this line */
+ int lleft; /* number of chars left in this buffer */
+ const char *nextc; /* next char in buffer */
+ char *buf; /* input buffer */
+ struct strpush *strpush; /* for pushing strings at this level */
+ struct strpush basestrpush; /* so pushing one is fast */
+};
+
+
+int plinno = 1; /* input line number */
+int parsenleft; /* copy of parsefile->nleft */
+MKINIT int parselleft; /* copy of parsefile->lleft */
+const char *parsenextc; /* copy of parsefile->nextc */
+MKINIT struct parsefile basepf; /* top level input file */
+MKINIT char basebuf[BUFSIZ]; /* buffer for top level input file */
+struct parsefile *parsefile = &basepf; /* current input file */
+int init_editline = 0; /* editline library initialized? */
+int whichprompt; /* 1 == PS1, 2 == PS2 */
+
+STATIC void pushfile(void);
+static int preadfd(void);
+
+#ifdef mkinit
+INCLUDE <stdio.h>
+INCLUDE "input.h"
+INCLUDE "error.h"
+
+INIT {
+ basepf.nextc = basepf.buf = basebuf;
+}
+
+RESET {
+ if (exception != EXSHELLPROC)
+ parselleft = parsenleft = 0; /* clear input buffer */
+ popallfiles();
+}
+
+SHELLPROC {
+ popallfiles();
+}
+#endif
+
+
+#if 0 /* this is unused */
+/*
+ * Read a line from the script.
+ */
+
+char *
+pfgets(char *line, int len)
+{
+ char *p = line;
+ int nleft = len;
+ int c;
+
+ while (--nleft > 0) {
+ c = pgetc_macro();
+ if (c == PFAKE) /* consecutive PFAKEs is impossible */
+ c = pgetc_macro();
+ if (c == PEOF) {
+ if (p == line)
+ return NULL;
+ break;
+ }
+ *p++ = c;
+ if (c == '\n') {
+ plinno++;
+ break;
+ }
+ }
+ *p = '\0';
+ return line;
+}
+#endif
+
+
+/*
+ * Read a character from the script, returning PEOF on end of file.
+ * Nul characters in the input are silently discarded.
+ */
+
+int
+pgetc(void)
+{
+ int c;
+
+ c = pgetc_macro();
+ if (c == PFAKE)
+ c = pgetc_macro();
+ return c;
+}
+
+
+static int
+preadfd(void)
+{
+ int nr;
+ char *buf = parsefile->buf;
+ parsenextc = buf;
+
+ retry:
+#ifndef SMALL
+ if (parsefile->fd == 0 && el) {
+ static const char *rl_cp;
+ static int el_len;
+
+ if (rl_cp == NULL)
+ rl_cp = el_gets(el, &el_len);
+ if (rl_cp == NULL)
+ nr = el_len == 0 ? 0 : -1;
+ else {
+ nr = el_len;
+ if (nr > BUFSIZ - 8)
+ nr = BUFSIZ - 8;
+ memcpy(buf, rl_cp, nr);
+ if (nr != el_len) {
+ el_len -= nr;
+ rl_cp += nr;
+ } else
+ rl_cp = 0;
+ }
+
+ } else
+#endif
+ nr = read(parsefile->fd, buf, BUFSIZ - 8);
+
+
+ if (nr <= 0) {
+ if (nr < 0) {
+ if (errno == EINTR)
+ goto retry;
+ if (parsefile->fd == 0 && errno == EWOULDBLOCK) {
+ int flags = fcntl(0, F_GETFL, 0);
+
+ if (flags >= 0 && flags & O_NONBLOCK) {
+ flags &=~ O_NONBLOCK;
+ if (fcntl(0, F_SETFL, flags) >= 0) {
+ out2str("sh: turning off NDELAY mode\n");
+ goto retry;
+ }
+ }
+ }
+ }
+ nr = -1;
+ }
+ return nr;
+}
+
+/*
+ * Refill the input buffer and return the next input character:
+ *
+ * 1) If a string was pushed back on the input, pop it;
+ * 2) If an EOF was pushed back (parsenleft == EOF_NLEFT) or we are reading
+ * from a string so we can't refill the buffer, return EOF.
+ * 3) If there is more stuff in this buffer, use it else call read to fill it.
+ * 4) Process input up to the next newline, deleting nul characters.
+ */
+
+int
+preadbuffer(void)
+{
+ char *p, *q;
+ int more;
+#ifndef SMALL
+ int something;
+#endif
+ char savec;
+
+ while (parsefile->strpush) {
+ if (parsenleft == -1 && parsefile->strpush->ap != NULL)
+ return PFAKE;
+ popstring();
+ if (--parsenleft >= 0)
+ return (*parsenextc++);
+ }
+ if (parsenleft == EOF_NLEFT || parsefile->buf == NULL)
+ return PEOF;
+ flushout(&output);
+ flushout(&errout);
+
+ again:
+ if (parselleft <= 0) {
+ if ((parselleft = preadfd()) == -1) {
+ parselleft = parsenleft = EOF_NLEFT;
+ return PEOF;
+ }
+ }
+
+ /* p = (not const char *)parsenextc; */
+ p = parsefile->buf + (parsenextc - parsefile->buf);
+ q = p;
+
+ /* delete nul characters */
+#ifndef SMALL
+ something = 0;
+#endif
+ for (more = 1; more;) {
+ switch (*p) {
+ case '\0':
+ p++; /* Skip nul */
+ goto check;
+
+ case '\t':
+ case ' ':
+ break;
+
+ case '\n':
+ parsenleft = q - parsenextc;
+ more = 0; /* Stop processing here */
+ break;
+
+ default:
+#ifndef SMALL
+ something = 1;
+#endif
+ break;
+ }
+
+ *q++ = *p++;
+ check:
+ if (--parselleft <= 0) {
+ parsenleft = q - parsenextc - 1;
+ if (parsenleft < 0)
+ goto again;
+ *q = '\0';
+ more = 0;
+ }
+ }
+
+ savec = *q;
+ *q = '\0';
+
+#ifndef SMALL
+ if (parsefile->fd == 0 && hist && (something || whichprompt == 2)) {
+ HistEvent he;
+
+ INTOFF;
+ history(hist, &he, whichprompt != 2 ? H_ENTER : H_APPEND,
+ parsenextc);
+ INTON;
+ }
+#endif
+
+ if (vflag) {
+ out2str(parsenextc);
+ flushout(out2);
+ }
+
+ *q = savec;
+
+ return *parsenextc++;
+}
+
+/*
+ * Test whether we have reached EOF on input stream.
+ * Return true only if certain (without attempting a read).
+ *
+ * Note the similarity to the opening section of preadbuffer()
+ */
+int
+at_eof(void)
+{
+ struct strpush *sp = parsefile->strpush;
+
+ if (parsenleft > 0) /* more chars are in the buffer */
+ return 0;
+
+ while (sp != NULL) {
+ /*
+ * If any pushed string has any remaining data,
+ * then we are not at EOF (simulating popstring())
+ */
+ if (sp->prevnleft > 0)
+ return 0;
+ sp = sp->prev;
+ }
+
+ /*
+ * If we reached real EOF and pushed it back,
+ * or if we are just processing a string (not reading a file)
+ * then there is no more. Note that if a file pushes a
+ * string, the file's ->buf remains present.
+ */
+ if (parsenleft == EOF_NLEFT || parsefile->buf == NULL)
+ return 1;
+
+ /*
+ * In other cases, there might be more
+ */
+ return 0;
+}
+
+/*
+ * Undo the last call to pgetc. Only one character may be pushed back.
+ * PEOF may be pushed back.
+ */
+
+void
+pungetc(void)
+{
+ parsenleft++;
+ parsenextc--;
+}
+
+/*
+ * Push a string back onto the input at this current parsefile level.
+ * We handle aliases this way.
+ */
+void
+pushstring(const char *s, int len, struct alias *ap)
+{
+ struct strpush *sp;
+
+ VTRACE(DBG_INPUT,
+ ("pushstring(\"%.*s\", %d)%s%s%s had: nl=%d ll=%d \"%.*s\"\n",
+ len, s, len, ap ? " for alias:'" : "",
+ ap ? ap->name : "", ap ? "'" : "",
+ parsenleft, parselleft, parsenleft, parsenextc));
+
+ INTOFF;
+ if (parsefile->strpush) {
+ sp = ckmalloc(sizeof (struct strpush));
+ sp->prev = parsefile->strpush;
+ parsefile->strpush = sp;
+ } else
+ sp = parsefile->strpush = &(parsefile->basestrpush);
+
+ sp->prevstring = parsenextc;
+ sp->prevnleft = parsenleft;
+ sp->prevlleft = parselleft;
+ sp->ap = ap;
+ if (ap)
+ ap->flag |= ALIASINUSE;
+ parsenextc = s;
+ parsenleft = len;
+ INTON;
+}
+
+void
+popstring(void)
+{
+ struct strpush *sp = parsefile->strpush;
+
+ INTOFF;
+ if (sp->ap) {
+ int alen;
+
+ if ((alen = strlen(sp->ap->val)) > 0 &&
+ (sp->ap->val[alen - 1] == ' ' ||
+ sp->ap->val[alen - 1] == '\t'))
+ checkkwd |= CHKALIAS;
+ sp->ap->flag &= ~ALIASINUSE;
+ }
+ parsenextc = sp->prevstring;
+ parsenleft = sp->prevnleft;
+ parselleft = sp->prevlleft;
+
+ VTRACE(DBG_INPUT, ("popstring()%s%s%s nl=%d ll=%d \"%.*s\"\n",
+ sp->ap ? " from alias:'" : "", sp->ap ? sp->ap->name : "",
+ sp->ap ? "'" : "", parsenleft, parselleft, parsenleft, parsenextc));
+
+ parsefile->strpush = sp->prev;
+ if (sp != &(parsefile->basestrpush))
+ ckfree(sp);
+ INTON;
+}
+
+/*
+ * Set the input to take input from a file. If push is set, push the
+ * old input onto the stack first.
+ */
+
+void
+setinputfile(const char *fname, int push)
+{
+ unsigned char magic[4];
+ int fd;
+ int fd2;
+ struct stat sb;
+
+ CTRACE(DBG_INPUT,("setinputfile(\"%s\", %spush)\n",fname,push?"":"no"));
+
+ INTOFF;
+ if ((fd = open(fname, O_RDONLY)) < 0)
+ error("Can't open %s", fname);
+
+ /* Since the message "Syntax error: "(" unexpected" is not very
+ * helpful, we check if the file starts with the ELF magic to
+ * avoid that message. The first lseek tries to make sure that
+ * we can later rewind the file.
+ */
+ if (fstat(fd, &sb) == 0 && S_ISREG(sb.st_mode) &&
+ lseek(fd, 0, SEEK_SET) == 0) {
+ if (read(fd, magic, 4) == 4) {
+ if (memcmp(magic, "\177ELF", 4) == 0) {
+ (void)close(fd);
+ error("Cannot execute ELF binary %s", fname);
+ }
+ }
+ if (lseek(fd, 0, SEEK_SET) != 0) {
+ (void)close(fd);
+ error("Cannot rewind the file %s", fname);
+ }
+ }
+
+ fd2 = to_upper_fd(fd); /* closes fd, returns higher equiv */
+ if (fd2 == fd) {
+ (void) close(fd);
+ error("Out of file descriptors");
+ }
+
+ setinputfd(fd2, push);
+ INTON;
+}
+
+/*
+ * When a shell fd needs to be altered (when the user wants to use
+ * the same fd - rare, but happens - we need to locate the ref to
+ * the fd, and update it. This happens via a callback.
+ * This is the callback func for fd's used for shell input
+ */
+static void
+input_fd_swap(int from, int to)
+{
+ struct parsefile *pf;
+
+ pf = parsefile;
+ while (pf != NULL) { /* don't need to stop at basepf */
+ if (pf->fd == from)
+ pf->fd = to;
+ pf = pf->prev;
+ }
+}
+
+/*
+ * Like setinputfile, but takes an open file descriptor. Call this with
+ * interrupts off.
+ */
+
+void
+setinputfd(int fd, int push)
+{
+ VTRACE(DBG_INPUT, ("setinputfd(%d, %spush)\n", fd, push?"":"no"));
+
+ register_sh_fd(fd, input_fd_swap);
+ (void) fcntl(fd, F_SETFD, FD_CLOEXEC);
+ if (push)
+ pushfile();
+ if (parsefile->fd > 0)
+ sh_close(parsefile->fd);
+ parsefile->fd = fd;
+ if (parsefile->buf == NULL)
+ parsefile->buf = ckmalloc(BUFSIZ);
+ parselleft = parsenleft = 0;
+ plinno = 1;
+
+ CTRACE(DBG_INPUT, ("setinputfd(%d, %spush) done; plinno=1\n", fd,
+ push ? "" : "no"));
+}
+
+
+/*
+ * Like setinputfile, but takes input from a string.
+ */
+
+void
+setinputstring(char *string, int push, int line1)
+{
+
+ INTOFF;
+ if (push) /* XXX: always, as it happens */
+ pushfile();
+ parsenextc = string;
+ parselleft = parsenleft = strlen(string);
+ plinno = line1;
+
+ CTRACE(DBG_INPUT,
+ ("setinputstring(\"%.20s%s\" (%d), %spush, @ %d)\n", string,
+ (parsenleft > 20 ? "..." : ""), parsenleft, push?"":"no", line1));
+ INTON;
+}
+
+
+
+/*
+ * To handle the "." command, a stack of input files is used. Pushfile
+ * adds a new entry to the stack and popfile restores the previous level.
+ */
+
+STATIC void
+pushfile(void)
+{
+ struct parsefile *pf;
+
+ VTRACE(DBG_INPUT,
+ ("pushfile(): fd=%d buf=%p nl=%d ll=%d \"%.*s\" plinno=%d\n",
+ parsefile->fd, parsefile->buf, parsenleft, parselleft,
+ parsenleft, parsenextc, plinno));
+
+ parsefile->nleft = parsenleft;
+ parsefile->lleft = parselleft;
+ parsefile->nextc = parsenextc;
+ parsefile->linno = plinno;
+ pf = (struct parsefile *)ckmalloc(sizeof (struct parsefile));
+ pf->prev = parsefile;
+ pf->fd = -1;
+ pf->strpush = NULL;
+ pf->basestrpush.prev = NULL;
+ pf->buf = NULL;
+ parsefile = pf;
+}
+
+
+void
+popfile(void)
+{
+ struct parsefile *pf = parsefile;
+
+ INTOFF;
+ if (pf->fd >= 0)
+ sh_close(pf->fd);
+ if (pf->buf)
+ ckfree(pf->buf);
+ while (pf->strpush)
+ popstring();
+ parsefile = pf->prev;
+ ckfree(pf);
+ parsenleft = parsefile->nleft;
+ parselleft = parsefile->lleft;
+ parsenextc = parsefile->nextc;
+
+ VTRACE(DBG_INPUT,
+ ("popfile(): fd=%d buf=%p nl=%d ll=%d \"%.*s\" plinno:%d->%d\n",
+ parsefile->fd, parsefile->buf, parsenleft, parselleft,
+ parsenleft, parsenextc, plinno, parsefile->linno));
+
+ plinno = parsefile->linno;
+ INTON;
+}
+
+/*
+ * Return current file (to go back to it later using popfilesupto()).
+ */
+
+struct parsefile *
+getcurrentfile(void)
+{
+ return parsefile;
+}
+
+
+/*
+ * Pop files until the given file is on top again. Useful for regular
+ * builtins that read shell commands from files or strings.
+ * If the given file is not an active file, an error is raised.
+ */
+
+void
+popfilesupto(struct parsefile *file)
+{
+ while (parsefile != file && parsefile != &basepf)
+ popfile();
+ if (parsefile != file)
+ error("popfilesupto() misused");
+}
+
+
+/*
+ * Return to top level.
+ */
+
+void
+popallfiles(void)
+{
+ while (parsefile != &basepf)
+ popfile();
+}
+
+
+
+/*
+ * Close the file(s) that the shell is reading commands from. Called
+ * after a fork is done.
+ *
+ * Takes one arg, vfork, which tells it to not modify its global vars
+ * as it is still running in the parent.
+ *
+ * This code is (probably) unnecessary as the 'close on exec' flag is
+ * set and should be enough. In the vfork case it is definitely wrong
+ * to close the fds as another fork() may be done later to feed data
+ * from a 'here' document into a pipe and we don't want to close the
+ * pipe!
+ */
+
+void
+closescript(int vforked)
+{
+ if (vforked)
+ return;
+ popallfiles();
+ if (parsefile->fd > 0) {
+ sh_close(parsefile->fd);
+ parsefile->fd = 0;
+ }
+}
diff --git a/bin/sh/input.h b/bin/sh/input.h
new file mode 100644
index 0000000..c40d4aa
--- /dev/null
+++ b/bin/sh/input.h
@@ -0,0 +1,69 @@
+/* $NetBSD: input.h,v 1.21 2018/08/19 23:50:27 kre Exp $ */
+
+/*-
+ * Copyright (c) 1991, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Kenneth Almquist.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * @(#)input.h 8.2 (Berkeley) 5/4/95
+ */
+
+/* PEOF (the end of file marker) is defined in syntax.h */
+
+/*
+ * The input line number. Input.c just defines this variable, and saves
+ * and restores it when files are pushed and popped. The user of this
+ * package must set its value.
+ */
+extern int plinno;
+extern int parsenleft; /* number of characters left in input buffer */
+extern const char *parsenextc; /* next character in input buffer */
+extern int init_editline; /* 0 == not setup, 1 == OK, -1 == failed */
+extern int whichprompt; /* 1 ==> PS1, 2 ==> PS2 */
+
+struct alias;
+struct parsefile;
+
+char *pfgets(char *, int);
+int pgetc(void);
+int preadbuffer(void);
+int at_eof(void);
+void pungetc(void);
+void pushstring(const char *, int, struct alias *);
+void popstring(void);
+void setinputfile(const char *, int);
+void setinputfd(int, int);
+void setinputstring(char *, int, int);
+void popfile(void);
+struct parsefile *getcurrentfile(void);
+void popfilesupto(struct parsefile *);
+void popallfiles(void);
+void closescript(int);
+
+#define pgetc_macro() (--parsenleft >= 0? *parsenextc++ : preadbuffer())
diff --git a/bin/sh/jobs.c b/bin/sh/jobs.c
new file mode 100644
index 0000000..d066bb8
--- /dev/null
+++ b/bin/sh/jobs.c
@@ -0,0 +1,1812 @@
+/* $NetBSD: jobs.c,v 1.103 2018/12/03 02:38:30 kre Exp $ */
+
+/*-
+ * Copyright (c) 1991, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Kenneth Almquist.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+#ifndef lint
+#if 0
+static char sccsid[] = "@(#)jobs.c 8.5 (Berkeley) 5/4/95";
+#else
+__RCSID("$NetBSD: jobs.c,v 1.103 2018/12/03 02:38:30 kre Exp $");
+#endif
+#endif /* not lint */
+
+#include <stdio.h>
+#include <fcntl.h>
+#include <signal.h>
+#include <errno.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <paths.h>
+#include <sys/types.h>
+#include <sys/param.h>
+#ifdef BSD
+#include <sys/wait.h>
+#include <sys/time.h>
+#include <sys/resource.h>
+#endif
+#include <sys/ioctl.h>
+
+#include "shell.h"
+#if JOBS
+#if OLD_TTY_DRIVER
+#include "sgtty.h"
+#else
+#include <termios.h>
+#endif
+#undef CEOF /* syntax.h redefines this */
+#endif
+#include "redir.h"
+#include "show.h"
+#include "main.h"
+#include "parser.h"
+#include "nodes.h"
+#include "jobs.h"
+#include "var.h"
+#include "options.h"
+#include "builtins.h"
+#include "trap.h"
+#include "syntax.h"
+#include "input.h"
+#include "output.h"
+#include "memalloc.h"
+#include "error.h"
+#include "mystring.h"
+
+
+#ifndef WCONTINUED
+#define WCONTINUED 0 /* So we can compile on old systems */
+#endif
+#ifndef WIFCONTINUED
+#define WIFCONTINUED(x) (0) /* ditto */
+#endif
+
+
+static struct job *jobtab; /* array of jobs */
+static int njobs; /* size of array */
+static int jobs_invalid; /* set in child */
+MKINIT pid_t backgndpid = -1; /* pid of last background process */
+#if JOBS
+int initialpgrp; /* pgrp of shell on invocation */
+static int curjob = -1; /* current job */
+#endif
+static int ttyfd = -1;
+
+STATIC void restartjob(struct job *);
+STATIC void freejob(struct job *);
+STATIC struct job *getjob(const char *, int);
+STATIC int dowait(int, struct job *, struct job **);
+#define WBLOCK 1
+#define WNOFREE 2
+#define WSILENT 4
+STATIC int jobstatus(const struct job *, int);
+STATIC int waitproc(int, struct job *, int *);
+STATIC void cmdtxt(union node *);
+STATIC void cmdlist(union node *, int);
+STATIC void cmdputs(const char *);
+inline static void cmdputi(int);
+
+#ifdef SYSV
+STATIC int onsigchild(void);
+#endif
+
+#ifdef OLD_TTY_DRIVER
+static pid_t tcgetpgrp(int fd);
+static int tcsetpgrp(int fd, pid_t pgrp);
+
+static pid_t
+tcgetpgrp(int fd)
+{
+ pid_t pgrp;
+ if (ioctl(fd, TIOCGPGRP, (char *)&pgrp) == -1)
+ return -1;
+ else
+ return pgrp;
+}
+
+static int
+tcsetpgrp(int fd, pid_tpgrp)
+{
+ return ioctl(fd, TIOCSPGRP, (char *)&pgrp);
+}
+#endif
+
+static void
+ttyfd_change(int from, int to)
+{
+ if (ttyfd == from)
+ ttyfd = to;
+}
+
+/*
+ * Turn job control on and off.
+ *
+ * Note: This code assumes that the third arg to ioctl is a character
+ * pointer, which is true on Berkeley systems but not System V. Since
+ * System V doesn't have job control yet, this isn't a problem now.
+ */
+
+MKINIT int jobctl;
+
+void
+setjobctl(int on)
+{
+#ifdef OLD_TTY_DRIVER
+ int ldisc;
+#endif
+
+ if (on == jobctl || rootshell == 0)
+ return;
+ if (on) {
+#if defined(FIOCLEX) || defined(FD_CLOEXEC)
+ int i;
+
+ if (ttyfd != -1)
+ sh_close(ttyfd);
+ if ((ttyfd = open("/dev/tty", O_RDWR)) == -1) {
+ for (i = 0; i < 3; i++) {
+ if (isatty(i) && (ttyfd = dup(i)) != -1)
+ break;
+ }
+ if (i == 3)
+ goto out;
+ }
+ ttyfd = to_upper_fd(ttyfd); /* Move to a high fd */
+ register_sh_fd(ttyfd, ttyfd_change);
+#else
+ out2str("sh: Need FIOCLEX or FD_CLOEXEC to support job control");
+ goto out;
+#endif
+ do { /* while we are in the background */
+ if ((initialpgrp = tcgetpgrp(ttyfd)) < 0) {
+ out:
+ out2str("sh: can't access tty; job control turned off\n");
+ mflag = 0;
+ return;
+ }
+ if (initialpgrp == -1)
+ initialpgrp = getpgrp();
+ else if (initialpgrp != getpgrp()) {
+ killpg(0, SIGTTIN);
+ continue;
+ }
+ } while (0);
+
+#ifdef OLD_TTY_DRIVER
+ if (ioctl(ttyfd, TIOCGETD, (char *)&ldisc) < 0
+ || ldisc != NTTYDISC) {
+ out2str("sh: need new tty driver to run job control; job control turned off\n");
+ mflag = 0;
+ return;
+ }
+#endif
+ setsignal(SIGTSTP, 0);
+ setsignal(SIGTTOU, 0);
+ setsignal(SIGTTIN, 0);
+ if (getpgrp() != rootpid && setpgid(0, rootpid) == -1)
+ error("Cannot set process group (%s) at %d",
+ strerror(errno), __LINE__);
+ if (tcsetpgrp(ttyfd, rootpid) == -1)
+ error("Cannot set tty process group (%s) at %d",
+ strerror(errno), __LINE__);
+ } else { /* turning job control off */
+ if (getpgrp() != initialpgrp && setpgid(0, initialpgrp) == -1)
+ error("Cannot set process group (%s) at %d",
+ strerror(errno), __LINE__);
+ if (tcsetpgrp(ttyfd, initialpgrp) == -1)
+ error("Cannot set tty process group (%s) at %d",
+ strerror(errno), __LINE__);
+ sh_close(ttyfd);
+ ttyfd = -1;
+ setsignal(SIGTSTP, 0);
+ setsignal(SIGTTOU, 0);
+ setsignal(SIGTTIN, 0);
+ }
+ jobctl = on;
+}
+
+
+#ifdef mkinit
+INCLUDE <stdlib.h>
+
+SHELLPROC {
+ backgndpid = -1;
+#if JOBS
+ jobctl = 0;
+#endif
+}
+
+#endif
+
+
+
+#if JOBS
+static int
+do_fgcmd(const char *arg_ptr)
+{
+ struct job *jp;
+ int i;
+ int status;
+
+ if (jobs_invalid)
+ error("No current jobs");
+ jp = getjob(arg_ptr, 0);
+ if (jp->jobctl == 0)
+ error("job not created under job control");
+ out1fmt("%s", jp->ps[0].cmd);
+ for (i = 1; i < jp->nprocs; i++)
+ out1fmt(" | %s", jp->ps[i].cmd );
+ out1c('\n');
+ flushall();
+
+ for (i = 0; i < jp->nprocs; i++)
+ if (tcsetpgrp(ttyfd, jp->ps[i].pid) != -1)
+ break;
+
+ if (i >= jp->nprocs) {
+ error("Cannot set tty process group (%s) at %d",
+ strerror(errno), __LINE__);
+ }
+ restartjob(jp);
+ INTOFF;
+ status = waitforjob(jp);
+ INTON;
+ return status;
+}
+
+int
+fgcmd(int argc, char **argv)
+{
+ nextopt("");
+ return do_fgcmd(*argptr);
+}
+
+int
+fgcmd_percent(int argc, char **argv)
+{
+ nextopt("");
+ return do_fgcmd(*argv);
+}
+
+static void
+set_curjob(struct job *jp, int mode)
+{
+ struct job *jp1, *jp2;
+ int i, ji;
+
+ ji = jp - jobtab;
+
+ /* first remove from list */
+ if (ji == curjob)
+ curjob = jp->prev_job;
+ else {
+ for (i = 0; i < njobs; i++) {
+ if (jobtab[i].prev_job != ji)
+ continue;
+ jobtab[i].prev_job = jp->prev_job;
+ break;
+ }
+ }
+
+ /* Then re-insert in correct position */
+ switch (mode) {
+ case 0: /* job being deleted */
+ jp->prev_job = -1;
+ break;
+ case 1: /* newly created job or backgrounded job,
+ put after all stopped jobs. */
+ if (curjob != -1 && jobtab[curjob].state == JOBSTOPPED) {
+ for (jp1 = jobtab + curjob; ; jp1 = jp2) {
+ if (jp1->prev_job == -1)
+ break;
+ jp2 = jobtab + jp1->prev_job;
+ if (jp2->state != JOBSTOPPED)
+ break;
+ }
+ jp->prev_job = jp1->prev_job;
+ jp1->prev_job = ji;
+ break;
+ }
+ /* FALLTHROUGH */
+ case 2: /* newly stopped job - becomes curjob */
+ jp->prev_job = curjob;
+ curjob = ji;
+ break;
+ }
+}
+
+int
+bgcmd(int argc, char **argv)
+{
+ struct job *jp;
+ int i;
+
+ nextopt("");
+ if (jobs_invalid)
+ error("No current jobs");
+ do {
+ jp = getjob(*argptr, 0);
+ if (jp->jobctl == 0)
+ error("job not created under job control");
+ set_curjob(jp, 1);
+ out1fmt("[%ld] %s", (long)(jp - jobtab + 1), jp->ps[0].cmd);
+ for (i = 1; i < jp->nprocs; i++)
+ out1fmt(" | %s", jp->ps[i].cmd );
+ out1c('\n');
+ flushall();
+ restartjob(jp);
+ } while (*argptr && *++argptr);
+ return 0;
+}
+
+
+STATIC void
+restartjob(struct job *jp)
+{
+ struct procstat *ps;
+ int i;
+
+ if (jp->state == JOBDONE)
+ return;
+ INTOFF;
+ for (i = 0; i < jp->nprocs; i++)
+ if (killpg(jp->ps[i].pid, SIGCONT) != -1)
+ break;
+ if (i >= jp->nprocs)
+ error("Cannot continue job (%s)", strerror(errno));
+ for (ps = jp->ps, i = jp->nprocs ; --i >= 0 ; ps++) {
+ if (WIFSTOPPED(ps->status)) {
+ VTRACE(DBG_JOBS, (
+ "restartjob: [%zu] pid %d status change"
+ " from %#x (stopped) to -1 (running)\n",
+ (size_t)(jp-jobtab+1), ps->pid, ps->status));
+ ps->status = -1;
+ jp->state = JOBRUNNING;
+ }
+ }
+ INTON;
+}
+#endif
+
+inline static void
+cmdputi(int n)
+{
+ char str[20];
+
+ fmtstr(str, sizeof str, "%d", n);
+ cmdputs(str);
+}
+
+static void
+showjob(struct output *out, struct job *jp, int mode)
+{
+ int procno;
+ int st;
+ struct procstat *ps;
+ int col;
+ char s[64];
+
+#if JOBS
+ if (mode & SHOW_PGID) {
+ /* just output process (group) id of pipeline */
+ outfmt(out, "%ld\n", (long)jp->ps->pid);
+ return;
+ }
+#endif
+
+ procno = jp->nprocs;
+ if (!procno)
+ return;
+
+ if (mode & SHOW_PID)
+ mode |= SHOW_MULTILINE;
+
+ if ((procno > 1 && !(mode & SHOW_MULTILINE))
+ || (mode & SHOW_SIGNALLED)) {
+ /* See if we have more than one status to report */
+ ps = jp->ps;
+ st = ps->status;
+ do {
+ int st1 = ps->status;
+ if (st1 != st)
+ /* yes - need multi-line output */
+ mode |= SHOW_MULTILINE;
+ if (st1 == -1 || !(mode & SHOW_SIGNALLED) || WIFEXITED(st1))
+ continue;
+ if (WIFSTOPPED(st1) || ((st1 = WTERMSIG(st1) & 0x7f)
+ && st1 != SIGINT && st1 != SIGPIPE))
+ mode |= SHOW_ISSIG;
+
+ } while (ps++, --procno);
+ procno = jp->nprocs;
+ }
+
+ if (mode & SHOW_SIGNALLED && !(mode & SHOW_ISSIG)) {
+ if (jp->state == JOBDONE && !(mode & SHOW_NO_FREE)) {
+ VTRACE(DBG_JOBS, ("showjob: freeing job %d\n",
+ jp - jobtab + 1));
+ freejob(jp);
+ }
+ return;
+ }
+
+ for (ps = jp->ps; --procno >= 0; ps++) { /* for each process */
+ if (ps == jp->ps)
+ fmtstr(s, 16, "[%ld] %c ",
+ (long)(jp - jobtab + 1),
+#if JOBS
+ jp - jobtab == curjob ?
+ '+' :
+ curjob != -1 &&
+ jp - jobtab == jobtab[curjob].prev_job ?
+ '-' :
+#endif
+ ' ');
+ else
+ fmtstr(s, 16, " " );
+ col = strlen(s);
+ if (mode & SHOW_PID) {
+ fmtstr(s + col, 16, "%ld ", (long)ps->pid);
+ col += strlen(s + col);
+ }
+ if (ps->status == -1) {
+ scopy("Running", s + col);
+ } else if (WIFEXITED(ps->status)) {
+ st = WEXITSTATUS(ps->status);
+ if (st)
+ fmtstr(s + col, 16, "Done(%d)", st);
+ else
+ fmtstr(s + col, 16, "Done");
+ } else {
+#if JOBS
+ if (WIFSTOPPED(ps->status))
+ st = WSTOPSIG(ps->status);
+ else /* WIFSIGNALED(ps->status) */
+#endif
+ st = WTERMSIG(ps->status);
+ scopyn(strsignal(st), s + col, 32);
+ if (WCOREDUMP(ps->status)) {
+ col += strlen(s + col);
+ scopyn(" (core dumped)", s + col, 64 - col);
+ }
+ }
+ col += strlen(s + col);
+ outstr(s, out);
+ do {
+ outc(' ', out);
+ col++;
+ } while (col < 30);
+ outstr(ps->cmd, out);
+ if (mode & SHOW_MULTILINE) {
+ if (procno > 0) {
+ outc(' ', out);
+ outc('|', out);
+ }
+ } else {
+ while (--procno >= 0)
+ outfmt(out, " | %s", (++ps)->cmd );
+ }
+ outc('\n', out);
+ }
+ flushout(out);
+ jp->flags &= ~JOBCHANGED;
+ if (jp->state == JOBDONE && !(mode & SHOW_NO_FREE))
+ freejob(jp);
+}
+
+int
+jobscmd(int argc, char **argv)
+{
+ int mode, m;
+
+ mode = 0;
+ while ((m = nextopt("lp")))
+ if (m == 'l')
+ mode = SHOW_PID;
+ else
+ mode = SHOW_PGID;
+ if (!iflag)
+ mode |= SHOW_NO_FREE;
+ if (*argptr)
+ do
+ showjob(out1, getjob(*argptr,0), mode);
+ while (*++argptr);
+ else
+ showjobs(out1, mode);
+ return 0;
+}
+
+
+/*
+ * Print a list of jobs. If "change" is nonzero, only print jobs whose
+ * statuses have changed since the last call to showjobs.
+ *
+ * If the shell is interrupted in the process of creating a job, the
+ * result may be a job structure containing zero processes. Such structures
+ * will be freed here.
+ */
+
+void
+showjobs(struct output *out, int mode)
+{
+ int jobno;
+ struct job *jp;
+ int silent = 0, gotpid;
+
+ CTRACE(DBG_JOBS, ("showjobs(%x) called\n", mode));
+
+ /* If not even one one job changed, there is nothing to do */
+ gotpid = dowait(WSILENT, NULL, NULL);
+ while (dowait(WSILENT, NULL, NULL) > 0)
+ continue;
+#ifdef JOBS
+ /*
+ * Check if we are not in our foreground group, and if not
+ * put us in it.
+ */
+ if (mflag && gotpid != -1 && tcgetpgrp(ttyfd) != getpid()) {
+ if (tcsetpgrp(ttyfd, getpid()) == -1)
+ error("Cannot set tty process group (%s) at %d",
+ strerror(errno), __LINE__);
+ VTRACE(DBG_JOBS|DBG_INPUT, ("repaired tty process group\n"));
+ silent = 1;
+ }
+#endif
+
+ for (jobno = 1, jp = jobtab ; jobno <= njobs ; jobno++, jp++) {
+ if (!jp->used)
+ continue;
+ if (jp->nprocs == 0) {
+ if (!jobs_invalid)
+ freejob(jp);
+ continue;
+ }
+ if ((mode & SHOW_CHANGED) && !(jp->flags & JOBCHANGED))
+ continue;
+ if (silent && (jp->flags & JOBCHANGED)) {
+ jp->flags &= ~JOBCHANGED;
+ continue;
+ }
+ showjob(out, jp, mode);
+ }
+}
+
+/*
+ * Mark a job structure as unused.
+ */
+
+STATIC void
+freejob(struct job *jp)
+{
+ INTOFF;
+ if (jp->ps != &jp->ps0) {
+ ckfree(jp->ps);
+ jp->ps = &jp->ps0;
+ }
+ jp->nprocs = 0;
+ jp->used = 0;
+#if JOBS
+ set_curjob(jp, 0);
+#endif
+ INTON;
+}
+
+/*
+ * Extract the status of a completed job (for $?)
+ */
+STATIC int
+jobstatus(const struct job *jp, int raw)
+{
+ int status = 0;
+ int retval;
+
+ if ((jp->flags & JPIPEFAIL) && jp->nprocs) {
+ int i;
+
+ for (i = 0; i < jp->nprocs; i++)
+ if (jp->ps[i].status != 0)
+ status = jp->ps[i].status;
+ } else
+ status = jp->ps[jp->nprocs ? jp->nprocs - 1 : 0].status;
+
+ if (raw)
+ return status;
+
+ if (WIFEXITED(status))
+ retval = WEXITSTATUS(status);
+#if JOBS
+ else if (WIFSTOPPED(status))
+ retval = WSTOPSIG(status) + 128;
+#endif
+ else {
+ /* XXX: limits number of signals */
+ retval = WTERMSIG(status) + 128;
+ }
+
+ return retval;
+}
+
+
+
+int
+waitcmd(int argc, char **argv)
+{
+ struct job *job, *last;
+ int retval;
+ struct job *jp;
+ int i;
+ int any = 0;
+ int found;
+ char *pid = NULL, *fpid;
+ char **arg;
+ char idstring[20];
+
+ while ((i = nextopt("np:")) != '\0') {
+ switch (i) {
+ case 'n':
+ any = 1;
+ break;
+ case 'p':
+ if (pid)
+ error("more than one -p unsupported");
+ pid = optionarg;
+ break;
+ }
+ }
+
+ if (pid != NULL) {
+ if (!validname(pid, '\0', NULL))
+ error("invalid name: -p '%s'", pid);
+ if (unsetvar(pid, 0))
+ error("%s readonly", pid);
+ }
+
+ /*
+ * If we have forked, and not yet created any new jobs, then
+ * we have no children, whatever jobtab claims,
+ * so simply return in that case.
+ *
+ * The return code is 127 if we had any pid args (none are found)
+ * or if we had -n (nothing exited), but 0 for plain old "wait".
+ */
+ if (jobs_invalid) {
+ CTRACE(DBG_WAIT, ("builtin wait%s%s in child, invalid jobtab\n",
+ any ? " -n" : "", *argptr ? " pid..." : ""));
+ return (any || *argptr) ? 127 : 0;
+ }
+
+ /* clear stray flags left from previous waitcmd */
+ for (jp = jobtab, i = njobs ; --i >= 0 ; jp++) {
+ jp->flags &= ~JOBWANTED;
+ jp->ref = NULL;
+ }
+
+ CTRACE(DBG_WAIT,
+ ("builtin wait%s%s\n", any ? " -n" : "", *argptr ? " pid..." : ""));
+
+ /*
+ * First, validate the jobnum args, count how many refer to
+ * (different) running jobs, and if we had -n, and found that one has
+ * already finished, we return that one. Otherwise remember
+ * which ones we are looking for (JOBWANTED).
+ */
+ found = 0;
+ last = NULL;
+ for (arg = argptr; *arg; arg++) {
+ last = jp = getjob(*arg, 1);
+ if (!jp)
+ continue;
+ if (jp->ref == NULL)
+ jp->ref = *arg;
+ if (any && jp->state == JOBDONE) {
+ /*
+ * We just want any of them, and this one is
+ * ready for consumption, bon apetit ...
+ */
+ retval = jobstatus(jp, 0);
+ if (pid)
+ setvar(pid, *arg, 0);
+ if (!iflag)
+ freejob(jp);
+ CTRACE(DBG_WAIT, ("wait -n found %s already done: %d\n", *arg, retval));
+ return retval;
+ }
+ if (!(jp->flags & JOBWANTED)) {
+ /*
+ * It is possible to list the same job several
+ * times - the obvious "wait 1 1 1" or
+ * "wait %% %2 102" where job 2 is current and pid 102
+ * However many times it is requested, it is found once.
+ */
+ found++;
+ jp->flags |= JOBWANTED;
+ }
+ job = jp;
+ }
+
+ VTRACE(DBG_WAIT, ("wait %s%s%sfound %d candidates (last %s)\n",
+ any ? "-n " : "", *argptr ? *argptr : "",
+ argptr[0] && argptr[1] ? "... " : " ", found,
+ job ? (job->ref ? job->ref : "<no-arg>") : "none"));
+
+ /*
+ * If we were given a list of jobnums:
+ * and none of those exist, then we're done.
+ */
+ if (*argptr && found == 0)
+ return 127;
+
+ /*
+ * Otherwise we need to wait for something to complete
+ * When it does, we check and see if it is one of the
+ * jobs we're waiting on, and if so, we clean it up.
+ * If we had -n, then we're done, otherwise we do it all again
+ * until all we had listed are done, of if there were no
+ * jobnum args, all are done.
+ */
+
+ retval = any || *argptr ? 127 : 0;
+ fpid = NULL;
+ for (;;) {
+ VTRACE(DBG_WAIT, ("wait waiting (%d remain): ", found));
+ for (jp = jobtab, i = njobs; --i >= 0; jp++) {
+ if (jp->used && jp->flags & JOBWANTED &&
+ jp->state == JOBDONE)
+ break;
+ if (jp->used && jp->state == JOBRUNNING)
+ break;
+ }
+ if (i < 0) {
+ CTRACE(DBG_WAIT, ("nothing running (ret: %d) fpid %s\n",
+ retval, fpid ? fpid : "unset"));
+ if (pid && fpid)
+ setvar(pid, fpid, 0);
+ return retval;
+ }
+ VTRACE(DBG_WAIT, ("found @%d/%d state: %d\n", njobs-i, njobs,
+ jp->state));
+
+ /*
+ * There is at least 1 job running, so we can
+ * safely wait() for something to exit.
+ */
+ if (jp->state == JOBRUNNING) {
+ job = NULL;
+ if ((i = dowait(WBLOCK|WNOFREE, NULL, &job)) == -1)
+ return 128 + lastsig();
+
+ /*
+ * one of the job's processes exited,
+ * but there are more
+ */
+ if (job->state == JOBRUNNING)
+ continue;
+ } else
+ job = jp; /* we want this, and it is done */
+
+ if (job->flags & JOBWANTED || (*argptr == 0 && any)) {
+ int rv;
+
+ job->flags &= ~JOBWANTED; /* got it */
+ rv = jobstatus(job, 0);
+ VTRACE(DBG_WAIT, (
+ "wanted %d (%s) done: st=%d", i,
+ job->ref ? job->ref : "", rv));
+ if (any || job == last) {
+ retval = rv;
+ fpid = job->ref;
+
+ VTRACE(DBG_WAIT, (" save"));
+ if (pid) {
+ /*
+ * don't need fpid unless we are going
+ * to return it.
+ */
+ if (fpid == NULL) {
+ /*
+ * this only happens with "wait -n"
+ * (that is, no pid args)
+ */
+ snprintf(idstring, sizeof idstring,
+ "%d", job->ps[ job->nprocs ?
+ job->nprocs-1 :
+ 0 ].pid);
+ fpid = idstring;
+ }
+ VTRACE(DBG_WAIT, (" (for %s)", fpid));
+ }
+ }
+
+ if (job->state == JOBDONE) {
+ VTRACE(DBG_WAIT, (" free"));
+ freejob(job);
+ }
+
+ if (any || (found > 0 && --found == 0)) {
+ if (pid && fpid)
+ setvar(pid, fpid, 0);
+ VTRACE(DBG_WAIT, (" return %d\n", retval));
+ return retval;
+ }
+ VTRACE(DBG_WAIT, ("\n"));
+ continue;
+ }
+
+ /* this is to handle "wait" (no args) */
+ if (found == 0 && job->state == JOBDONE) {
+ VTRACE(DBG_JOBS|DBG_WAIT, ("Cleanup: %d\n", i));
+ freejob(job);
+ }
+ }
+}
+
+
+int
+jobidcmd(int argc, char **argv)
+{
+ struct job *jp;
+ int i;
+ int pg = 0, onep = 0, job = 0;
+
+ while ((i = nextopt("gjp"))) {
+ switch (i) {
+ case 'g': pg = 1; break;
+ case 'j': job = 1; break;
+ case 'p': onep = 1; break;
+ }
+ }
+ CTRACE(DBG_JOBS, ("jobidcmd%s%s%s%s %s\n", pg ? " -g" : "",
+ onep ? " -p" : "", job ? " -j" : "", jobs_invalid ? " [inv]" : "",
+ *argptr ? *argptr : "<implicit %%>"));
+ if (pg + onep + job > 1)
+ error("-g -j and -p options cannot be combined");
+
+ if (argptr[0] && argptr[1])
+ error("usage: jobid [-g|-p|-r] jobid");
+
+ jp = getjob(*argptr, 0);
+ if (job) {
+ out1fmt("%%%zu\n", (size_t)(jp - jobtab + 1));
+ return 0;
+ }
+ if (pg) {
+ if (jp->pgrp != 0) {
+ out1fmt("%ld\n", (long)jp->pgrp);
+ return 0;
+ }
+ return 1;
+ }
+ if (onep) {
+ i = jp->nprocs - 1;
+ if (i < 0)
+ return 1;
+ out1fmt("%ld\n", (long)jp->ps[i].pid);
+ return 0;
+ }
+ for (i = 0 ; i < jp->nprocs ; ) {
+ out1fmt("%ld", (long)jp->ps[i].pid);
+ out1c(++i < jp->nprocs ? ' ' : '\n');
+ }
+ return 0;
+}
+
+int
+getjobpgrp(const char *name)
+{
+ struct job *jp;
+
+ if (jobs_invalid)
+ error("No such job: %s", name);
+ jp = getjob(name, 1);
+ if (jp == 0)
+ return 0;
+ return -jp->ps[0].pid;
+}
+
+/*
+ * Convert a job name to a job structure.
+ */
+
+STATIC struct job *
+getjob(const char *name, int noerror)
+{
+ int jobno = -1;
+ struct job *jp;
+ int pid;
+ int i;
+ const char *err_msg = "No such job: %s";
+
+ if (name == NULL) {
+#if JOBS
+ jobno = curjob;
+#endif
+ err_msg = "No current job";
+ } else if (name[0] == '%') {
+ if (is_number(name + 1)) {
+ jobno = number(name + 1) - 1;
+ } else if (!name[1] || !name[2]) {
+ switch (name[1]) {
+#if JOBS
+ case 0:
+ case '+':
+ case '%':
+ jobno = curjob;
+ err_msg = "No current job";
+ break;
+ case '-':
+ jobno = curjob;
+ if (jobno != -1)
+ jobno = jobtab[jobno].prev_job;
+ err_msg = "No previous job";
+ break;
+#endif
+ default:
+ goto check_pattern;
+ }
+ } else {
+ struct job *found;
+ check_pattern:
+ found = NULL;
+ for (jp = jobtab, i = njobs ; --i >= 0 ; jp++) {
+ if (!jp->used || jp->nprocs <= 0)
+ continue;
+ if ((name[1] == '?'
+ && strstr(jp->ps[0].cmd, name + 2))
+ || prefix(name + 1, jp->ps[0].cmd)) {
+ if (found) {
+ err_msg = "%s: ambiguous";
+ found = 0;
+ break;
+ }
+ found = jp;
+ }
+ }
+ if (found)
+ return found;
+ }
+
+ } else if (is_number(name)) {
+ pid = number(name);
+ for (jp = jobtab, i = njobs ; --i >= 0 ; jp++) {
+ if (jp->used && jp->nprocs > 0
+ && jp->ps[jp->nprocs - 1].pid == pid)
+ return jp;
+ }
+ }
+
+ if (jobno >= 0 && jobno < njobs) {
+ jp = jobtab + jobno;
+ if (jp->used)
+ return jp;
+ }
+ if (!noerror)
+ error(err_msg, name);
+ return 0;
+}
+
+
+
+/*
+ * Return a new job structure,
+ */
+
+struct job *
+makejob(union node *node, int nprocs)
+{
+ int i;
+ struct job *jp;
+
+ if (jobs_invalid) {
+ for (i = njobs, jp = jobtab ; --i >= 0 ; jp++) {
+ if (jp->used)
+ freejob(jp);
+ }
+ jobs_invalid = 0;
+ }
+
+ for (i = njobs, jp = jobtab ; ; jp++) {
+ if (--i < 0) {
+ INTOFF;
+ if (njobs == 0) {
+ jobtab = ckmalloc(4 * sizeof jobtab[0]);
+ } else {
+ jp = ckmalloc((njobs + 4) * sizeof jobtab[0]);
+ memcpy(jp, jobtab, njobs * sizeof jp[0]);
+ /* Relocate `ps' pointers */
+ for (i = 0; i < njobs; i++)
+ if (jp[i].ps == &jobtab[i].ps0)
+ jp[i].ps = &jp[i].ps0;
+ ckfree(jobtab);
+ jobtab = jp;
+ }
+ jp = jobtab + njobs;
+ for (i = 4 ; --i >= 0 ; njobs++) {
+ jobtab[njobs].used = 0;
+ jobtab[njobs].prev_job = -1;
+ }
+ INTON;
+ break;
+ }
+ if (jp->used == 0)
+ break;
+ }
+ INTOFF;
+ jp->state = JOBRUNNING;
+ jp->used = 1;
+ jp->flags = pipefail ? JPIPEFAIL : 0;
+ jp->nprocs = 0;
+ jp->pgrp = 0;
+#if JOBS
+ jp->jobctl = jobctl;
+ set_curjob(jp, 1);
+#endif
+ if (nprocs > 1) {
+ jp->ps = ckmalloc(nprocs * sizeof (struct procstat));
+ } else {
+ jp->ps = &jp->ps0;
+ }
+ INTON;
+ VTRACE(DBG_JOBS, ("makejob(%p, %d)%s returns %%%d\n", (void *)node,
+ nprocs, (jp->flags&JPIPEFAIL)?" PF":"", jp - jobtab + 1));
+ return jp;
+}
+
+
+/*
+ * Fork off a subshell. If we are doing job control, give the subshell its
+ * own process group. Jp is a job structure that the job is to be added to.
+ * N is the command that will be evaluated by the child. Both jp and n may
+ * be NULL. The mode parameter can be one of the following:
+ * FORK_FG - Fork off a foreground process.
+ * FORK_BG - Fork off a background process.
+ * FORK_NOJOB - Like FORK_FG, but don't give the process its own
+ * process group even if job control is on.
+ *
+ * When job control is turned off, background processes have their standard
+ * input redirected to /dev/null (except for the second and later processes
+ * in a pipeline).
+ */
+
+int
+forkshell(struct job *jp, union node *n, int mode)
+{
+ pid_t pid;
+ int serrno;
+
+ CTRACE(DBG_JOBS, ("forkshell(%%%d, %p, %d) called\n",
+ jp - jobtab, n, mode));
+
+ switch ((pid = fork())) {
+ case -1:
+ serrno = errno;
+ VTRACE(DBG_JOBS, ("Fork failed, errno=%d\n", serrno));
+ INTON;
+ error("Cannot fork (%s)", strerror(serrno));
+ break;
+ case 0:
+ SHELL_FORKED();
+ forkchild(jp, n, mode, 0);
+ return 0;
+ default:
+ return forkparent(jp, n, mode, pid);
+ }
+}
+
+int
+forkparent(struct job *jp, union node *n, int mode, pid_t pid)
+{
+ int pgrp;
+
+ if (rootshell && mode != FORK_NOJOB && mflag) {
+ if (jp == NULL || jp->nprocs == 0)
+ pgrp = pid;
+ else
+ pgrp = jp->ps[0].pid;
+ jp->pgrp = pgrp;
+ /* This can fail because we are doing it in the child also */
+ (void)setpgid(pid, pgrp);
+ }
+ if (mode == FORK_BG)
+ backgndpid = pid; /* set $! */
+ if (jp) {
+ struct procstat *ps = &jp->ps[jp->nprocs++];
+ ps->pid = pid;
+ ps->status = -1;
+ ps->cmd[0] = 0;
+ if (/* iflag && rootshell && */ n)
+ commandtext(ps, n);
+ }
+ CTRACE(DBG_JOBS, ("In parent shell: child = %d (mode %d)\n",pid,mode));
+ return pid;
+}
+
+void
+forkchild(struct job *jp, union node *n, int mode, int vforked)
+{
+ int wasroot;
+ int pgrp;
+ const char *devnull = _PATH_DEVNULL;
+ const char *nullerr = "Can't open %s";
+
+ wasroot = rootshell;
+ CTRACE(DBG_JOBS, ("Child shell %d %sforked from %d (mode %d)\n",
+ getpid(), vforked?"v":"", getppid(), mode));
+
+ if (!vforked) {
+ rootshell = 0;
+ handler = &main_handler;
+ }
+
+ closescript(vforked);
+ clear_traps(vforked);
+#if JOBS
+ if (!vforked)
+ jobctl = 0; /* do job control only in root shell */
+ if (wasroot && mode != FORK_NOJOB && mflag) {
+ if (jp == NULL || jp->nprocs == 0)
+ pgrp = getpid();
+ else
+ pgrp = jp->ps[0].pid;
+ /* This can fail because we are doing it in the parent also */
+ (void)setpgid(0, pgrp);
+ if (mode == FORK_FG) {
+ if (tcsetpgrp(ttyfd, pgrp) == -1)
+ error("Cannot set tty process group (%s) at %d",
+ strerror(errno), __LINE__);
+ }
+ setsignal(SIGTSTP, vforked);
+ setsignal(SIGTTOU, vforked);
+ } else if (mode == FORK_BG) {
+ ignoresig(SIGINT, vforked);
+ ignoresig(SIGQUIT, vforked);
+ if ((jp == NULL || jp->nprocs == 0) &&
+ ! fd0_redirected_p ()) {
+ close(0);
+ if (open(devnull, O_RDONLY) != 0)
+ error(nullerr, devnull);
+ }
+ }
+#else
+ if (mode == FORK_BG) {
+ ignoresig(SIGINT, vforked);
+ ignoresig(SIGQUIT, vforked);
+ if ((jp == NULL || jp->nprocs == 0) &&
+ ! fd0_redirected_p ()) {
+ close(0);
+ if (open(devnull, O_RDONLY) != 0)
+ error(nullerr, devnull);
+ }
+ }
+#endif
+ if (wasroot && iflag) {
+ setsignal(SIGINT, vforked);
+ setsignal(SIGQUIT, vforked);
+ setsignal(SIGTERM, vforked);
+ }
+
+ if (!vforked)
+ jobs_invalid = 1;
+}
+
+/*
+ * Wait for job to finish.
+ *
+ * Under job control we have the problem that while a child process is
+ * running interrupts generated by the user are sent to the child but not
+ * to the shell. This means that an infinite loop started by an inter-
+ * active user may be hard to kill. With job control turned off, an
+ * interactive user may place an interactive program inside a loop. If
+ * the interactive program catches interrupts, the user doesn't want
+ * these interrupts to also abort the loop. The approach we take here
+ * is to have the shell ignore interrupt signals while waiting for a
+ * forground process to terminate, and then send itself an interrupt
+ * signal if the child process was terminated by an interrupt signal.
+ * Unfortunately, some programs want to do a bit of cleanup and then
+ * exit on interrupt; unless these processes terminate themselves by
+ * sending a signal to themselves (instead of calling exit) they will
+ * confuse this approach.
+ */
+
+int
+waitforjob(struct job *jp)
+{
+#if JOBS
+ int mypgrp = getpgrp();
+#endif
+ int status;
+ int st;
+
+ INTOFF;
+ VTRACE(DBG_JOBS, ("waitforjob(%%%d) called\n", jp - jobtab + 1));
+ while (jp->state == JOBRUNNING) {
+ dowait(WBLOCK, jp, NULL);
+ }
+#if JOBS
+ if (jp->jobctl) {
+ if (tcsetpgrp(ttyfd, mypgrp) == -1)
+ error("Cannot set tty process group (%s) at %d",
+ strerror(errno), __LINE__);
+ }
+ if (jp->state == JOBSTOPPED && curjob != jp - jobtab)
+ set_curjob(jp, 2);
+#endif
+ status = jobstatus(jp, 1);
+
+ /* convert to 8 bits */
+ if (WIFEXITED(status))
+ st = WEXITSTATUS(status);
+#if JOBS
+ else if (WIFSTOPPED(status))
+ st = WSTOPSIG(status) + 128;
+#endif
+ else
+ st = WTERMSIG(status) + 128;
+
+ VTRACE(DBG_JOBS, ("waitforjob: job %d, nproc %d, status %d, st %x\n",
+ jp - jobtab + 1, jp->nprocs, status, st));
+#if JOBS
+ if (jp->jobctl) {
+ /*
+ * This is truly gross.
+ * If we're doing job control, then we did a TIOCSPGRP which
+ * caused us (the shell) to no longer be in the controlling
+ * session -- so we wouldn't have seen any ^C/SIGINT. So, we
+ * intuit from the subprocess exit status whether a SIGINT
+ * occurred, and if so interrupt ourselves. Yuck. - mycroft
+ */
+ if (WIFSIGNALED(status) && WTERMSIG(status) == SIGINT)
+ raise(SIGINT);
+ }
+#endif
+ if (! JOBS || jp->state == JOBDONE)
+ freejob(jp);
+ INTON;
+ return st;
+}
+
+
+
+/*
+ * Wait for a process to terminate.
+ */
+
+STATIC int
+dowait(int flags, struct job *job, struct job **changed)
+{
+ int pid;
+ int status;
+ struct procstat *sp;
+ struct job *jp;
+ struct job *thisjob;
+ int done;
+ int stopped;
+
+ VTRACE(DBG_JOBS|DBG_PROCS, ("dowait(%x) called\n", flags));
+
+ if (changed != NULL)
+ *changed = NULL;
+
+ do {
+ pid = waitproc(flags & WBLOCK, job, &status);
+ VTRACE(DBG_JOBS|DBG_PROCS, ("wait returns pid %d, status %#x\n",
+ pid, status));
+ } while (pid == -1 && errno == EINTR && pendingsigs == 0);
+ if (pid <= 0)
+ return pid;
+ INTOFF;
+ thisjob = NULL;
+ for (jp = jobtab ; jp < jobtab + njobs ; jp++) {
+ if (jp->used) {
+ done = 1;
+ stopped = 1;
+ for (sp = jp->ps ; sp < jp->ps + jp->nprocs ; sp++) {
+ if (sp->pid == -1)
+ continue;
+ if (sp->pid == pid &&
+ (sp->status==-1 || WIFSTOPPED(sp->status))) {
+ VTRACE(DBG_JOBS | DBG_PROCS,
+ ("Job %d: changing status of proc %d from %#x to %#x\n",
+ jp - jobtab + 1, pid,
+ sp->status, status));
+ if (WIFCONTINUED(status)) {
+ if (sp->status != -1)
+ jp->flags |= JOBCHANGED;
+ sp->status = -1;
+ jp->state = 0;
+ } else
+ sp->status = status;
+ thisjob = jp;
+ if (changed != NULL)
+ *changed = jp;
+ }
+ if (sp->status == -1)
+ stopped = 0;
+ else if (WIFSTOPPED(sp->status))
+ done = 0;
+ }
+ if (stopped) { /* stopped or done */
+ int state = done ? JOBDONE : JOBSTOPPED;
+
+ if (jp->state != state) {
+ VTRACE(DBG_JOBS,
+ ("Job %d: changing state from %d to %d\n",
+ jp - jobtab + 1, jp->state, state));
+ jp->state = state;
+#if JOBS
+ if (done)
+ set_curjob(jp, 0);
+#endif
+ }
+ }
+ }
+ }
+
+ if (thisjob &&
+ (thisjob->state != JOBRUNNING || thisjob->flags & JOBCHANGED)) {
+ int mode = 0;
+
+ if (!rootshell || !iflag)
+ mode = SHOW_SIGNALLED;
+ if ((job == thisjob && (flags & WNOFREE) == 0) ||
+ job != thisjob)
+ mode = SHOW_SIGNALLED | SHOW_NO_FREE;
+ if (mode && (flags & WSILENT) == 0)
+ showjob(out2, thisjob, mode);
+ else {
+ VTRACE(DBG_JOBS,
+ ("Not printing status, rootshell=%d, job=%p\n",
+ rootshell, job));
+ thisjob->flags |= JOBCHANGED;
+ }
+ }
+
+ INTON;
+ return pid;
+}
+
+
+
+/*
+ * Do a wait system call. If job control is compiled in, we accept
+ * stopped processes. If block is zero, we return a value of zero
+ * rather than blocking.
+ *
+ * System V doesn't have a non-blocking wait system call. It does
+ * have a SIGCLD signal that is sent to a process when one of its
+ * children dies. The obvious way to use SIGCLD would be to install
+ * a handler for SIGCLD which simply bumped a counter when a SIGCLD
+ * was received, and have waitproc bump another counter when it got
+ * the status of a process. Waitproc would then know that a wait
+ * system call would not block if the two counters were different.
+ * This approach doesn't work because if a process has children that
+ * have not been waited for, System V will send it a SIGCLD when it
+ * installs a signal handler for SIGCLD. What this means is that when
+ * a child exits, the shell will be sent SIGCLD signals continuously
+ * until is runs out of stack space, unless it does a wait call before
+ * restoring the signal handler. The code below takes advantage of
+ * this (mis)feature by installing a signal handler for SIGCLD and
+ * then checking to see whether it was called. If there are any
+ * children to be waited for, it will be.
+ *
+ * If neither SYSV nor BSD is defined, we don't implement nonblocking
+ * waits at all. In this case, the user will not be informed when
+ * a background process until the next time she runs a real program
+ * (as opposed to running a builtin command or just typing return),
+ * and the jobs command may give out of date information.
+ */
+
+#ifdef SYSV
+STATIC int gotsigchild;
+
+STATIC int onsigchild() {
+ gotsigchild = 1;
+}
+#endif
+
+
+STATIC int
+waitproc(int block, struct job *jp, int *status)
+{
+#ifdef BSD
+ int flags = 0;
+
+#if JOBS
+ if (mflag || (jp != NULL && jp->jobctl))
+ flags |= WUNTRACED | WCONTINUED;
+#endif
+ if (block == 0)
+ flags |= WNOHANG;
+ VTRACE(DBG_WAIT, ("waitproc: doing waitpid(flags=%#x)\n", flags));
+ return waitpid(-1, status, flags);
+#else
+#ifdef SYSV
+ int (*save)();
+
+ if (block == 0) {
+ gotsigchild = 0;
+ save = signal(SIGCLD, onsigchild);
+ signal(SIGCLD, save);
+ if (gotsigchild == 0)
+ return 0;
+ }
+ return wait(status);
+#else
+ if (block == 0)
+ return 0;
+ return wait(status);
+#endif
+#endif
+}
+
+/*
+ * return 1 if there are stopped jobs, otherwise 0
+ */
+int job_warning = 0;
+int
+stoppedjobs(void)
+{
+ int jobno;
+ struct job *jp;
+
+ if (job_warning || jobs_invalid)
+ return (0);
+ for (jobno = 1, jp = jobtab; jobno <= njobs; jobno++, jp++) {
+ if (jp->used == 0)
+ continue;
+ if (jp->state == JOBSTOPPED) {
+ out2str("You have stopped jobs.\n");
+ job_warning = 2;
+ return (1);
+ }
+ }
+
+ return (0);
+}
+
+/*
+ * Return a string identifying a command (to be printed by the
+ * jobs command).
+ */
+
+STATIC char *cmdnextc;
+STATIC int cmdnleft;
+
+void
+commandtext(struct procstat *ps, union node *n)
+{
+ int len;
+
+ cmdnextc = ps->cmd;
+ if (iflag || mflag || sizeof(ps->cmd) <= 60)
+ len = sizeof(ps->cmd);
+ else if (sizeof ps->cmd <= 400)
+ len = 50;
+ else if (sizeof ps->cmd <= 800)
+ len = 80;
+ else
+ len = sizeof(ps->cmd) / 10;
+ cmdnleft = len;
+ cmdtxt(n);
+ if (cmdnleft <= 0) {
+ char *p = ps->cmd + len - 4;
+ p[0] = '.';
+ p[1] = '.';
+ p[2] = '.';
+ p[3] = 0;
+ } else
+ *cmdnextc = '\0';
+
+ VTRACE(DBG_JOBS,
+ ("commandtext: ps->cmd %p, end %p, left %d\n\t\"%s\"\n",
+ ps->cmd, cmdnextc, cmdnleft, ps->cmd));
+}
+
+
+STATIC void
+cmdtxt(union node *n)
+{
+ union node *np;
+ struct nodelist *lp;
+ const char *p;
+ int i;
+
+ if (n == NULL || cmdnleft <= 0)
+ return;
+ switch (n->type) {
+ case NSEMI:
+ cmdtxt(n->nbinary.ch1);
+ cmdputs("; ");
+ cmdtxt(n->nbinary.ch2);
+ break;
+ case NAND:
+ cmdtxt(n->nbinary.ch1);
+ cmdputs(" && ");
+ cmdtxt(n->nbinary.ch2);
+ break;
+ case NOR:
+ cmdtxt(n->nbinary.ch1);
+ cmdputs(" || ");
+ cmdtxt(n->nbinary.ch2);
+ break;
+ case NDNOT:
+ cmdputs("! ");
+ /* FALLTHROUGH */
+ case NNOT:
+ cmdputs("! ");
+ cmdtxt(n->nnot.com);
+ break;
+ case NPIPE:
+ for (lp = n->npipe.cmdlist ; lp ; lp = lp->next) {
+ cmdtxt(lp->n);
+ if (lp->next)
+ cmdputs(" | ");
+ }
+ if (n->npipe.backgnd)
+ cmdputs(" &");
+ break;
+ case NSUBSHELL:
+ cmdputs("(");
+ cmdtxt(n->nredir.n);
+ cmdputs(")");
+ break;
+ case NREDIR:
+ case NBACKGND:
+ cmdtxt(n->nredir.n);
+ break;
+ case NIF:
+ cmdputs("if ");
+ cmdtxt(n->nif.test);
+ cmdputs("; then ");
+ cmdtxt(n->nif.ifpart);
+ if (n->nif.elsepart) {
+ cmdputs("; else ");
+ cmdtxt(n->nif.elsepart);
+ }
+ cmdputs("; fi");
+ break;
+ case NWHILE:
+ cmdputs("while ");
+ goto until;
+ case NUNTIL:
+ cmdputs("until ");
+ until:
+ cmdtxt(n->nbinary.ch1);
+ cmdputs("; do ");
+ cmdtxt(n->nbinary.ch2);
+ cmdputs("; done");
+ break;
+ case NFOR:
+ cmdputs("for ");
+ cmdputs(n->nfor.var);
+ cmdputs(" in ");
+ cmdlist(n->nfor.args, 1);
+ cmdputs("; do ");
+ cmdtxt(n->nfor.body);
+ cmdputs("; done");
+ break;
+ case NCASE:
+ cmdputs("case ");
+ cmdputs(n->ncase.expr->narg.text);
+ cmdputs(" in ");
+ for (np = n->ncase.cases; np; np = np->nclist.next) {
+ cmdtxt(np->nclist.pattern);
+ cmdputs(") ");
+ cmdtxt(np->nclist.body);
+ switch (n->type) { /* switch (not if) for later */
+ case NCLISTCONT:
+ cmdputs(";& ");
+ break;
+ default:
+ cmdputs(";; ");
+ break;
+ }
+ }
+ cmdputs("esac");
+ break;
+ case NDEFUN:
+ cmdputs(n->narg.text);
+ cmdputs("() { ... }");
+ break;
+ case NCMD:
+ cmdlist(n->ncmd.args, 1);
+ cmdlist(n->ncmd.redirect, 0);
+ if (n->ncmd.backgnd)
+ cmdputs(" &");
+ break;
+ case NARG:
+ cmdputs(n->narg.text);
+ break;
+ case NTO:
+ p = ">"; i = 1; goto redir;
+ case NCLOBBER:
+ p = ">|"; i = 1; goto redir;
+ case NAPPEND:
+ p = ">>"; i = 1; goto redir;
+ case NTOFD:
+ p = ">&"; i = 1; goto redir;
+ case NFROM:
+ p = "<"; i = 0; goto redir;
+ case NFROMFD:
+ p = "<&"; i = 0; goto redir;
+ case NFROMTO:
+ p = "<>"; i = 0; goto redir;
+ redir:
+ if (n->nfile.fd != i)
+ cmdputi(n->nfile.fd);
+ cmdputs(p);
+ if (n->type == NTOFD || n->type == NFROMFD) {
+ if (n->ndup.dupfd < 0)
+ cmdputs("-");
+ else
+ cmdputi(n->ndup.dupfd);
+ } else {
+ cmdtxt(n->nfile.fname);
+ }
+ break;
+ case NHERE:
+ case NXHERE:
+ cmdputs("<<...");
+ break;
+ default:
+ cmdputs("???");
+ break;
+ }
+}
+
+STATIC void
+cmdlist(union node *np, int sep)
+{
+ for (; np; np = np->narg.next) {
+ if (!sep)
+ cmdputs(" ");
+ cmdtxt(np);
+ if (sep && np->narg.next)
+ cmdputs(" ");
+ }
+}
+
+
+STATIC void
+cmdputs(const char *s)
+{
+ const char *p, *str = 0;
+ char c, cc[2] = " ";
+ char *nextc;
+ int nleft;
+ int subtype = 0;
+ int quoted = 0;
+ static char vstype[16][4] = { "", "}", "-", "+", "?", "=",
+ "#", "##", "%", "%%", "}" };
+
+ p = s;
+ nextc = cmdnextc;
+ nleft = cmdnleft;
+ while (nleft > 0 && (c = *p++) != 0) {
+ switch (c) {
+ case CTLNONL:
+ c = '\0';
+ break;
+ case CTLESC:
+ c = *p++;
+ break;
+ case CTLVAR:
+ subtype = *p++;
+ if (subtype & VSLINENO) { /* undo LINENO hack */
+ if ((subtype & VSTYPE) == VSLENGTH)
+ str = "${#LINENO"; /*}*/
+ else
+ str = "${LINENO"; /*}*/
+ while (is_digit(*p))
+ p++;
+ } else if ((subtype & VSTYPE) == VSLENGTH)
+ str = "${#"; /*}*/
+ else
+ str = "${"; /*}*/
+ if (!(subtype & VSQUOTE) != !(quoted & 1)) {
+ quoted ^= 1;
+ c = '"';
+ } else {
+ c = *str++;
+ }
+ break;
+ case CTLENDVAR: /*{*/
+ c = '}';
+ if (quoted & 1)
+ str = "\"";
+ quoted >>= 1;
+ subtype = 0;
+ break;
+ case CTLBACKQ:
+ c = '$';
+ str = "(...)";
+ break;
+ case CTLBACKQ+CTLQUOTE:
+ c = '"';
+ str = "$(...)\"";
+ break;
+ case CTLARI:
+ c = '$';
+ if (*p == ' ')
+ p++;
+ str = "(("; /*))*/
+ break;
+ case CTLENDARI: /*((*/
+ c = ')';
+ str = ")";
+ break;
+ case CTLQUOTEMARK:
+ quoted ^= 1;
+ c = '"';
+ break;
+ case CTLQUOTEEND:
+ quoted >>= 1;
+ c = '"';
+ break;
+ case '=':
+ if (subtype == 0)
+ break;
+ str = vstype[subtype & VSTYPE];
+ if (subtype & VSNUL)
+ c = ':';
+ else
+ c = *str++; /*{*/
+ if (c != '}')
+ quoted <<= 1;
+ else if (*p == CTLENDVAR)
+ c = *str++;
+ subtype = 0;
+ break;
+ case '\'':
+ case '\\':
+ case '"':
+ case '$':
+ /* These can only happen inside quotes */
+ cc[0] = c;
+ str = cc;
+ c = '\\';
+ break;
+ default:
+ break;
+ }
+ if (c != '\0') do { /* c == 0 implies nothing in str */
+ *nextc++ = c;
+ } while (--nleft > 0 && str && (c = *str++));
+ str = 0;
+ }
+ if ((quoted & 1) && nleft) {
+ *nextc++ = '"';
+ nleft--;
+ }
+ cmdnleft = nleft;
+ cmdnextc = nextc;
+}
diff --git a/bin/sh/jobs.h b/bin/sh/jobs.h
new file mode 100644
index 0000000..a587e9e
--- /dev/null
+++ b/bin/sh/jobs.h
@@ -0,0 +1,105 @@
+/* $NetBSD: jobs.h,v 1.23 2018/09/11 03:30:40 kre Exp $ */
+
+/*-
+ * Copyright (c) 1991, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Kenneth Almquist.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * @(#)jobs.h 8.2 (Berkeley) 5/4/95
+ */
+
+#include "output.h"
+
+/* Mode argument to forkshell. Don't change FORK_FG or FORK_BG. */
+#define FORK_FG 0
+#define FORK_BG 1
+#define FORK_NOJOB 2
+
+/* mode flags for showjob(s) */
+#define SHOW_PGID 0x01 /* only show pgid - for jobs -p */
+#define SHOW_MULTILINE 0x02 /* one line per process */
+#define SHOW_PID 0x04 /* include process pid */
+#define SHOW_CHANGED 0x08 /* only jobs whose state has changed */
+#define SHOW_SIGNALLED 0x10 /* only if stopped/exited on signal */
+#define SHOW_ISSIG 0x20 /* job was signalled */
+#define SHOW_NO_FREE 0x40 /* do not free job */
+
+
+/*
+ * A job structure contains information about a job. A job is either a
+ * single process or a set of processes contained in a pipeline. In the
+ * latter case, pidlist will be non-NULL, and will point to a -1 terminated
+ * array of pids.
+ */
+#define MAXCMDTEXT 200
+
+struct procstat {
+ pid_t pid; /* process id */
+ int status; /* last process status from wait() */
+ char cmd[MAXCMDTEXT];/* text of command being run */
+};
+
+struct job {
+ struct procstat ps0; /* status of process */
+ struct procstat *ps; /* status or processes when more than one */
+ void *ref; /* temporary reference, used variously */
+ int nprocs; /* number of processes */
+ pid_t pgrp; /* process group of this job */
+ char state;
+#define JOBRUNNING 0 /* at least one proc running */
+#define JOBSTOPPED 1 /* all procs are stopped */
+#define JOBDONE 2 /* all procs are completed */
+ char used; /* true if this entry is in used */
+ char flags;
+#define JOBCHANGED 1 /* set if status has changed */
+#define JOBWANTED 2 /* set if this is a job being sought */
+#define JPIPEFAIL 4 /* set if -o pipefail when job created */
+#if JOBS
+ char jobctl; /* job running under job control */
+ int prev_job; /* previous job index */
+#endif
+};
+
+extern pid_t backgndpid; /* pid of last background process */
+extern int job_warning; /* user was warned about stopped jobs */
+
+void setjobctl(int);
+void showjobs(struct output *, int);
+struct job *makejob(union node *, int);
+int forkshell(struct job *, union node *, int);
+void forkchild(struct job *, union node *, int, int);
+int forkparent(struct job *, union node *, int, pid_t);
+int waitforjob(struct job *);
+int stoppedjobs(void);
+void commandtext(struct procstat *, union node *);
+int getjobpgrp(const char *);
+
+#if ! JOBS
+#define setjobctl(on) /* do nothing */
+#endif
diff --git a/bin/sh/machdep.h b/bin/sh/machdep.h
new file mode 100644
index 0000000..14e803b
--- /dev/null
+++ b/bin/sh/machdep.h
@@ -0,0 +1,47 @@
+/* $NetBSD: machdep.h,v 1.11 2003/08/07 09:05:33 agc Exp $ */
+
+/*-
+ * Copyright (c) 1991, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Kenneth Almquist.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * @(#)machdep.h 8.2 (Berkeley) 5/4/95
+ */
+
+/*
+ * Most machines require the value returned from malloc to be aligned
+ * in some way. The following macro will get this right on many machines.
+ */
+
+#define SHELL_SIZE (sizeof(union {int i; char *cp; double d; }) - 1)
+/*
+ * It appears that grabstackstr() will barf with such alignments
+ * because stalloc() will return a string allocated in a new stackblock.
+ */
+#define SHELL_ALIGN(nbytes) (((nbytes) + SHELL_SIZE) & ~SHELL_SIZE)
diff --git a/bin/sh/mail.c b/bin/sh/mail.c
new file mode 100644
index 0000000..484b50f
--- /dev/null
+++ b/bin/sh/mail.c
@@ -0,0 +1,144 @@
+/* $NetBSD: mail.c,v 1.18 2017/06/04 20:28:13 kre Exp $ */
+
+/*-
+ * Copyright (c) 1991, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Kenneth Almquist.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+#ifndef lint
+#if 0
+static char sccsid[] = "@(#)mail.c 8.2 (Berkeley) 5/4/95";
+#else
+__RCSID("$NetBSD: mail.c,v 1.18 2017/06/04 20:28:13 kre Exp $");
+#endif
+#endif /* not lint */
+
+/*
+ * Routines to check for mail. (Perhaps make part of main.c?)
+ */
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "shell.h"
+#include "exec.h" /* defines padvance() */
+#include "var.h"
+#include "output.h"
+#include "memalloc.h"
+#include "error.h"
+#include "mail.h"
+
+
+#define MAXMBOXES 10
+
+
+STATIC int nmboxes; /* number of mailboxes */
+STATIC off_t mailsize[MAXMBOXES]; /* sizes of mailboxes */
+
+
+
+/*
+ * Print appropriate message(s) if mail has arrived. If the argument is
+ * nozero, then the value of MAIL has changed, so we just update the
+ * values.
+ */
+
+void
+chkmail(int silent)
+{
+ int i;
+ const char *mpath;
+ char *p;
+ char *q;
+ struct stackmark smark;
+ struct stat statb;
+
+ if (silent)
+ nmboxes = 10;
+ if (nmboxes == 0)
+ return;
+ setstackmark(&smark);
+ mpath = mpathset() ? mpathval() : mailval();
+ for (i = 0 ; i < nmboxes ; i++) {
+ p = padvance(&mpath, nullstr, 1);
+ if (p == NULL)
+ break;
+ stunalloc(p);
+ if (*p == '\0')
+ continue;
+ for (q = p ; *q ; q++)
+ ;
+ if (q[-1] != '/')
+ abort();
+ q[-1] = '\0'; /* delete trailing '/' */
+#ifdef notdef /* this is what the System V shell claims to do (it lies) */
+ if (stat(p, &statb) < 0)
+ statb.st_mtime = 0;
+ if (statb.st_mtime > mailtime[i] && ! silent) {
+ out2str(pathopt ? pathopt : "you have mail");
+ out2c('\n');
+ }
+ mailtime[i] = statb.st_mtime;
+#else /* this is what it should do */
+ if (stat(p, &statb) < 0)
+ statb.st_size = 0;
+ if (statb.st_size > mailsize[i] && ! silent) {
+ const char *pp;
+
+ if ((pp = pathopt) != NULL) {
+ int len = 0;
+
+ while (*pp && *pp != ':')
+ len++, pp++;
+ if (len == 0) {
+ CHECKSTRSPACE(16, p);
+ strcat(p, ": file changed");
+ } else {
+ while (stackblocksize() <= len)
+ growstackblock();
+ p = stackblock();
+ memcpy(p, pathopt, len);
+ p[len] = '\0';
+ }
+ pp = p;
+ } else
+ pp = "you have mail";
+
+ out2str(pp);
+ out2c('\n');
+ }
+ mailsize[i] = statb.st_size;
+#endif
+ }
+ nmboxes = i;
+ popstackmark(&smark);
+}
diff --git a/bin/sh/mail.h b/bin/sh/mail.h
new file mode 100644
index 0000000..9ea7c21
--- /dev/null
+++ b/bin/sh/mail.h
@@ -0,0 +1,37 @@
+/* $NetBSD: mail.h,v 1.10 2003/08/07 09:05:34 agc Exp $ */
+
+/*-
+ * Copyright (c) 1991, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Kenneth Almquist.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * @(#)mail.h 8.2 (Berkeley) 5/4/95
+ */
+
+void chkmail(int);
diff --git a/bin/sh/main.c b/bin/sh/main.c
new file mode 100644
index 0000000..a023611
--- /dev/null
+++ b/bin/sh/main.c
@@ -0,0 +1,393 @@
+/* $NetBSD: main.c,v 1.80 2019/01/19 14:20:22 kre Exp $ */
+
+/*-
+ * Copyright (c) 1991, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Kenneth Almquist.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+#ifndef lint
+__COPYRIGHT("@(#) Copyright (c) 1991, 1993\
+ The Regents of the University of California. All rights reserved.");
+#endif /* not lint */
+
+#ifndef lint
+#if 0
+static char sccsid[] = "@(#)main.c 8.7 (Berkeley) 7/19/95";
+#else
+__RCSID("$NetBSD: main.c,v 1.80 2019/01/19 14:20:22 kre Exp $");
+#endif
+#endif /* not lint */
+
+#include <errno.h>
+#include <stdio.h>
+#include <signal.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <locale.h>
+#include <fcntl.h>
+
+
+#include "shell.h"
+#include "main.h"
+#include "mail.h"
+#include "options.h"
+#include "builtins.h"
+#include "output.h"
+#include "parser.h"
+#include "nodes.h"
+#include "expand.h"
+#include "eval.h"
+#include "jobs.h"
+#include "input.h"
+#include "trap.h"
+#include "var.h"
+#include "show.h"
+#include "memalloc.h"
+#include "error.h"
+#include "init.h"
+#include "mystring.h"
+#include "exec.h"
+#include "cd.h"
+#include "redir.h"
+
+#define PROFILE 0
+
+int rootpid;
+int rootshell;
+struct jmploc main_handler;
+int max_user_fd;
+#if PROFILE
+short profile_buf[16384];
+extern int etext();
+#endif
+
+STATIC void read_profile(const char *);
+
+/*
+ * Main routine. We initialize things, parse the arguments, execute
+ * profiles if we're a login shell, and then call cmdloop to execute
+ * commands. The setjmp call sets up the location to jump to when an
+ * exception occurs. When an exception occurs the variable "state"
+ * is used to figure out how far we had gotten.
+ */
+
+int
+main(int argc, char **argv)
+{
+ struct stackmark smark;
+ volatile int state;
+ char *shinit;
+ uid_t uid;
+ gid_t gid;
+
+ uid = getuid();
+ gid = getgid();
+
+ max_user_fd = fcntl(0, F_MAXFD);
+ if (max_user_fd < 2)
+ max_user_fd = 2;
+
+ setlocale(LC_ALL, "");
+
+ posix = getenv("POSIXLY_CORRECT") != NULL;
+#if PROFILE
+ monitor(4, etext, profile_buf, sizeof profile_buf, 50);
+#endif
+ state = 0;
+ if (setjmp(main_handler.loc)) {
+ /*
+ * When a shell procedure is executed, we raise the
+ * exception EXSHELLPROC to clean up before executing
+ * the shell procedure.
+ */
+ switch (exception) {
+ case EXSHELLPROC:
+ rootpid = getpid();
+ rootshell = 1;
+ minusc = NULL;
+ state = 3;
+ break;
+
+ case EXEXEC:
+ exitstatus = exerrno;
+ break;
+
+ case EXERROR:
+ exitstatus = 2;
+ break;
+
+ default:
+ break;
+ }
+
+ if (exception != EXSHELLPROC) {
+ if (state == 0 || iflag == 0 || ! rootshell ||
+ exception == EXEXIT)
+ exitshell(exitstatus);
+ }
+ reset();
+ if (exception == EXINT) {
+ out2c('\n');
+ flushout(&errout);
+ }
+ popstackmark(&smark);
+ FORCEINTON; /* enable interrupts */
+ if (state == 1)
+ goto state1;
+ else if (state == 2)
+ goto state2;
+ else if (state == 3)
+ goto state3;
+ else
+ goto state4;
+ }
+ handler = &main_handler;
+#ifdef DEBUG
+#if DEBUG >= 2
+ debug = 1; /* this may be reset by procargs() later */
+#endif
+ opentrace();
+ trputs("Shell args: "); trargs(argv);
+#if DEBUG >= 3
+ set_debug(((DEBUG)==3 ? "_@" : "++"), 1);
+#endif
+#endif
+ rootpid = getpid();
+ rootshell = 1;
+ init();
+ initpwd();
+ setstackmark(&smark);
+ procargs(argc, argv);
+
+ /*
+ * Limit bogus system(3) or popen(3) calls in setuid binaries,
+ * by requiring the -p flag
+ */
+ if (!pflag && (uid != geteuid() || gid != getegid())) {
+ setuid(uid);
+ setgid(gid);
+ /* PS1 might need to be changed accordingly. */
+ choose_ps1();
+ }
+
+ if (argv[0] && argv[0][0] == '-') {
+ state = 1;
+ read_profile("/etc/profile");
+ state1:
+ state = 2;
+ read_profile(".profile");
+ }
+ state2:
+ state = 3;
+ if ((iflag || !posix) &&
+ getuid() == geteuid() && getgid() == getegid()) {
+ struct stackmark env_smark;
+
+ setstackmark(&env_smark);
+ if ((shinit = lookupvar("ENV")) != NULL && *shinit != '\0') {
+ state = 3;
+ read_profile(expandenv(shinit));
+ }
+ popstackmark(&env_smark);
+ }
+ state3:
+ state = 4;
+ line_number = 1; /* undo anything from profile files */
+
+ if (sflag == 0 || minusc) {
+ static int sigs[] = {
+ SIGINT, SIGQUIT, SIGHUP,
+#ifdef SIGTSTP
+ SIGTSTP,
+#endif
+ SIGPIPE
+ };
+#define SIGSSIZE (sizeof(sigs)/sizeof(sigs[0]))
+ size_t i;
+
+ for (i = 0; i < SIGSSIZE; i++)
+ setsignal(sigs[i], 0);
+ }
+
+ if (minusc)
+ evalstring(minusc, sflag ? 0 : EV_EXIT);
+
+ if (sflag || minusc == NULL) {
+ state4: /* XXX ??? - why isn't this before the "if" statement */
+ cmdloop(1);
+ }
+#if PROFILE
+ monitor(0);
+#endif
+ line_number = plinno;
+ exitshell(exitstatus);
+ /* NOTREACHED */
+}
+
+
+/*
+ * Read and execute commands. "Top" is nonzero for the top level command
+ * loop; it turns on prompting if the shell is interactive.
+ */
+
+void
+cmdloop(int top)
+{
+ union node *n;
+ struct stackmark smark;
+ int inter;
+ int numeof = 0;
+ enum skipstate skip;
+
+ CTRACE(DBG_ALWAYS, ("cmdloop(%d) called\n", top));
+ setstackmark(&smark);
+ for (;;) {
+ if (pendingsigs)
+ dotrap();
+ inter = 0;
+ if (iflag == 1 && top) {
+ inter = 1;
+ showjobs(out2, SHOW_CHANGED);
+ chkmail(0);
+ flushout(&errout);
+ nflag = 0;
+ }
+ n = parsecmd(inter);
+ VXTRACE(DBG_PARSE|DBG_EVAL|DBG_CMDS,("cmdloop: "),showtree(n));
+ if (n == NEOF) {
+ if (!top || numeof >= 50)
+ break;
+ if (nflag)
+ break;
+ if (!stoppedjobs()) {
+ if (!iflag || !Iflag)
+ break;
+ out2str("\nUse \"exit\" to leave shell.\n");
+ }
+ numeof++;
+ } else if (n != NULL && nflag == 0) {
+ job_warning = (job_warning == 2) ? 1 : 0;
+ numeof = 0;
+ evaltree(n, 0);
+ }
+ rststackmark(&smark);
+
+ /*
+ * Any SKIP* can occur here! SKIP(FUNC|BREAK|CONT) occur when
+ * a dotcmd is in a loop or a function body and appropriate
+ * built-ins occurs in file scope in the sourced file. Values
+ * other than SKIPFILE are reset by the appropriate eval*()
+ * that contained the dotcmd() call.
+ */
+ skip = current_skipstate();
+ if (skip != SKIPNONE) {
+ if (skip == SKIPFILE)
+ stop_skipping();
+ break;
+ }
+ }
+ popstackmark(&smark);
+}
+
+
+
+/*
+ * Read /etc/profile or .profile. Return on error.
+ */
+
+STATIC void
+read_profile(const char *name)
+{
+ int fd;
+ int xflag_set = 0;
+ int vflag_set = 0;
+
+ if (*name == '\0')
+ return;
+
+ INTOFF;
+ if ((fd = open(name, O_RDONLY)) >= 0)
+ setinputfd(fd, 1);
+ INTON;
+ if (fd < 0)
+ return;
+ /* -q turns off -x and -v just when executing init files */
+ if (qflag) {
+ if (xflag)
+ xflag = 0, xflag_set = 1;
+ if (vflag)
+ vflag = 0, vflag_set = 1;
+ }
+ cmdloop(0);
+ if (qflag) {
+ if (xflag_set)
+ xflag = 1;
+ if (vflag_set)
+ vflag = 1;
+ }
+ popfile();
+}
+
+
+
+/*
+ * Read a file containing shell functions.
+ */
+
+void
+readcmdfile(char *name)
+{
+ int fd;
+
+ INTOFF;
+ if ((fd = open(name, O_RDONLY)) >= 0)
+ setinputfd(fd, 1);
+ else
+ error("Can't open %s", name);
+ INTON;
+ cmdloop(0);
+ popfile();
+}
+
+
+
+int
+exitcmd(int argc, char **argv)
+{
+ if (stoppedjobs())
+ return 0;
+ if (argc > 1)
+ exitshell(number(argv[1]));
+ else
+ exitshell_savedstatus();
+ /* NOTREACHED */
+}
diff --git a/bin/sh/main.h b/bin/sh/main.h
new file mode 100644
index 0000000..db1d576
--- /dev/null
+++ b/bin/sh/main.h
@@ -0,0 +1,42 @@
+/* $NetBSD: main.h,v 1.12 2018/12/03 02:38:30 kre Exp $ */
+
+/*-
+ * Copyright (c) 1991, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Kenneth Almquist.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * @(#)main.h 8.2 (Berkeley) 5/4/95
+ */
+
+extern int rootpid; /* pid of main shell */
+extern int rootshell; /* true if we aren't a child of the main shell */
+extern struct jmploc main_handler; /* top level exception handler */
+
+void readcmdfile(char *);
+void cmdloop(int);
diff --git a/bin/sh/memalloc.c b/bin/sh/memalloc.c
new file mode 100644
index 0000000..da8ff3c
--- /dev/null
+++ b/bin/sh/memalloc.c
@@ -0,0 +1,334 @@
+/* $NetBSD: memalloc.c,v 1.32 2018/08/22 20:08:54 kre Exp $ */
+
+/*-
+ * Copyright (c) 1991, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Kenneth Almquist.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+#ifndef lint
+#if 0
+static char sccsid[] = "@(#)memalloc.c 8.3 (Berkeley) 5/4/95";
+#else
+__RCSID("$NetBSD: memalloc.c,v 1.32 2018/08/22 20:08:54 kre Exp $");
+#endif
+#endif /* not lint */
+
+#include <stdlib.h>
+#include <unistd.h>
+
+#include "shell.h"
+#include "output.h"
+#include "memalloc.h"
+#include "error.h"
+#include "machdep.h"
+#include "mystring.h"
+
+/*
+ * Like malloc, but returns an error when out of space.
+ */
+
+pointer
+ckmalloc(size_t nbytes)
+{
+ pointer p;
+
+ p = malloc(nbytes);
+ if (p == NULL)
+ error("Out of space");
+ return p;
+}
+
+
+/*
+ * Same for realloc.
+ */
+
+pointer
+ckrealloc(pointer p, int nbytes)
+{
+ p = realloc(p, nbytes);
+ if (p == NULL)
+ error("Out of space");
+ return p;
+}
+
+
+/*
+ * Make a copy of a string in safe storage.
+ */
+
+char *
+savestr(const char *s)
+{
+ char *p;
+
+ p = ckmalloc(strlen(s) + 1);
+ scopy(s, p);
+ return p;
+}
+
+
+/*
+ * Parse trees for commands are allocated in lifo order, so we use a stack
+ * to make this more efficient, and also to avoid all sorts of exception
+ * handling code to handle interrupts in the middle of a parse.
+ *
+ * The size 504 was chosen because the Ultrix malloc handles that size
+ * well.
+ */
+
+#define MINSIZE 504 /* minimum size of a block */
+
+struct stack_block {
+ struct stack_block *prev;
+ char space[MINSIZE];
+};
+
+struct stack_block stackbase;
+struct stack_block *stackp = &stackbase;
+struct stackmark *markp;
+char *stacknxt = stackbase.space;
+int stacknleft = MINSIZE;
+int sstrnleft;
+int herefd = -1;
+
+pointer
+stalloc(int nbytes)
+{
+ char *p;
+
+ nbytes = SHELL_ALIGN(nbytes);
+ if (nbytes > stacknleft) {
+ int blocksize;
+ struct stack_block *sp;
+
+ blocksize = nbytes;
+ if (blocksize < MINSIZE)
+ blocksize = MINSIZE;
+ INTOFF;
+ sp = ckmalloc(sizeof(struct stack_block) - MINSIZE + blocksize);
+ sp->prev = stackp;
+ stacknxt = sp->space;
+ stacknleft = blocksize;
+ stackp = sp;
+ INTON;
+ }
+ p = stacknxt;
+ stacknxt += nbytes;
+ stacknleft -= nbytes;
+ return p;
+}
+
+
+void
+stunalloc(pointer p)
+{
+ if (p == NULL) { /*DEBUG */
+ write(2, "stunalloc\n", 10);
+ abort();
+ }
+ stacknleft += stacknxt - (char *)p;
+ stacknxt = p;
+}
+
+
+/* save the current status of the sh stack */
+void
+setstackmark(struct stackmark *mark)
+{
+ mark->stackp = stackp;
+ mark->stacknxt = stacknxt;
+ mark->stacknleft = stacknleft;
+ mark->sstrnleft = sstrnleft;
+ mark->marknext = markp;
+ markp = mark;
+}
+
+/* reset the stack mark, and remove it from the list of marks */
+void
+popstackmark(struct stackmark *mark)
+{
+ markp = mark->marknext; /* delete mark from the list */
+ rststackmark(mark); /* and reset stack */
+}
+
+/* reset the shell stack to its state recorded in the stack mark */
+void
+rststackmark(struct stackmark *mark)
+{
+ struct stack_block *sp;
+
+ INTOFF;
+ while (stackp != mark->stackp) {
+ /* delete any recently allocated mem blocks */
+ sp = stackp;
+ stackp = sp->prev;
+ ckfree(sp);
+ }
+ stacknxt = mark->stacknxt;
+ stacknleft = mark->stacknleft;
+ sstrnleft = mark->sstrnleft;
+ INTON;
+}
+
+
+/*
+ * When the parser reads in a string, it wants to stick the string on the
+ * stack and only adjust the stack pointer when it knows how big the
+ * string is. Stackblock (defined in stack.h) returns a pointer to a block
+ * of space on top of the stack and stackblocklen returns the length of
+ * this block. Growstackblock will grow this space by at least one byte,
+ * possibly moving it (like realloc). Grabstackblock actually allocates the
+ * part of the block that has been used.
+ */
+
+void
+growstackblock(void)
+{
+ int newlen = SHELL_ALIGN(stacknleft * 2 + 100);
+
+ INTOFF;
+ if (stacknxt == stackp->space && stackp != &stackbase) {
+ struct stack_block *oldstackp;
+ struct stackmark *xmark;
+ struct stack_block *sp;
+
+ oldstackp = stackp;
+ sp = stackp;
+ stackp = sp->prev;
+ sp = ckrealloc((pointer)sp,
+ sizeof(struct stack_block) - MINSIZE + newlen);
+ sp->prev = stackp;
+ stackp = sp;
+ stacknxt = sp->space;
+ sstrnleft += newlen - stacknleft;
+ stacknleft = newlen;
+
+ /*
+ * Stack marks pointing to the start of the old block
+ * must be relocated to point to the new block
+ */
+ xmark = markp;
+ while (xmark != NULL && xmark->stackp == oldstackp) {
+ xmark->stackp = stackp;
+ xmark->stacknxt = stacknxt;
+ xmark->sstrnleft += stacknleft - xmark->stacknleft;
+ xmark->stacknleft = stacknleft;
+ xmark = xmark->marknext;
+ }
+ } else {
+ char *oldspace = stacknxt;
+ int oldlen = stacknleft;
+ char *p = stalloc(newlen);
+
+ (void)memcpy(p, oldspace, oldlen);
+ stacknxt = p; /* free the space */
+ stacknleft += newlen; /* we just allocated */
+ }
+ INTON;
+}
+
+void
+grabstackblock(int len)
+{
+ len = SHELL_ALIGN(len);
+ stacknxt += len;
+ stacknleft -= len;
+}
+
+/*
+ * The following routines are somewhat easier to use than the above.
+ * The user declares a variable of type STACKSTR, which may be declared
+ * to be a register. The macro STARTSTACKSTR initializes things. Then
+ * the user uses the macro STPUTC to add characters to the string. In
+ * effect, STPUTC(c, p) is the same as *p++ = c except that the stack is
+ * grown as necessary. When the user is done, she can just leave the
+ * string there and refer to it using stackblock(). Or she can allocate
+ * the space for it using grabstackstr(). If it is necessary to allow
+ * someone else to use the stack temporarily and then continue to grow
+ * the string, the user should use grabstack to allocate the space, and
+ * then call ungrabstr(p) to return to the previous mode of operation.
+ *
+ * USTPUTC is like STPUTC except that it doesn't check for overflow.
+ * CHECKSTACKSPACE can be called before USTPUTC to ensure that there
+ * is space for at least one character.
+ */
+
+char *
+growstackstr(void)
+{
+ int len = stackblocksize();
+ if (herefd >= 0 && len >= 1024) {
+ xwrite(herefd, stackblock(), len);
+ sstrnleft = len - 1;
+ return stackblock();
+ }
+ growstackblock();
+ sstrnleft = stackblocksize() - len - 1;
+ return stackblock() + len;
+}
+
+/*
+ * Called from CHECKSTRSPACE.
+ */
+
+char *
+makestrspace(void)
+{
+ int len = stackblocksize() - sstrnleft;
+ growstackblock();
+ sstrnleft = stackblocksize() - len;
+ return stackblock() + len;
+}
+
+/*
+ * Note that this only works to release stack space for reuse
+ * if nothing else has allocated space on the stack since the grabstackstr()
+ *
+ * "s" is the start of the area to be released, and "p" represents the end
+ * of the string we have stored beyond there and are now releasing.
+ * (ie: "p" should be the same as in the call to grabstackstr()).
+ *
+ * stunalloc(s) and ungrabstackstr(s, p) are almost interchangable after
+ * a grabstackstr(), however the latter also returns string space so we
+ * can just continue with STPUTC() etc without needing a new STARTSTACKSTR(s)
+ */
+void
+ungrabstackstr(char *s, char *p)
+{
+#ifdef DEBUG
+ if (s < stacknxt || stacknxt + stacknleft < s)
+ abort();
+#endif
+ stacknleft += stacknxt - s;
+ stacknxt = s;
+ sstrnleft = stacknleft - (p - s);
+}
diff --git a/bin/sh/memalloc.h b/bin/sh/memalloc.h
new file mode 100644
index 0000000..ed6669e
--- /dev/null
+++ b/bin/sh/memalloc.h
@@ -0,0 +1,79 @@
+/* $NetBSD: memalloc.h,v 1.18 2018/08/22 20:08:54 kre Exp $ */
+
+/*-
+ * Copyright (c) 1991, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Kenneth Almquist.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * @(#)memalloc.h 8.2 (Berkeley) 5/4/95
+ */
+
+struct stackmark {
+ struct stack_block *stackp;
+ char *stacknxt;
+ int stacknleft;
+ int sstrnleft;
+ struct stackmark *marknext;
+};
+
+
+extern char *stacknxt;
+extern int stacknleft;
+extern int sstrnleft;
+extern int herefd;
+
+pointer ckmalloc(size_t);
+pointer ckrealloc(pointer, int);
+char *savestr(const char *);
+pointer stalloc(int);
+void stunalloc(pointer);
+void setstackmark(struct stackmark *);
+void popstackmark(struct stackmark *);
+void rststackmark(struct stackmark *);
+void growstackblock(void);
+void grabstackblock(int);
+char *growstackstr(void);
+char *makestrspace(void);
+void ungrabstackstr(char *, char *);
+
+
+
+#define stackblock() stacknxt
+#define stackblocksize() stacknleft
+#define STARTSTACKSTR(p) p = stackblock(), sstrnleft = stackblocksize()
+#define STPUTC(c, p) (--sstrnleft >= 0? (*p++ = (c)) : (p = growstackstr(), *p++ = (c)))
+#define CHECKSTRSPACE(n, p) { if (sstrnleft < n) p = makestrspace(); }
+#define USTPUTC(c, p) (--sstrnleft, *p++ = (c))
+#define STACKSTRNUL(p) (sstrnleft == 0? (p = growstackstr(), sstrnleft++, *p = '\0') : (*p = '\0'))
+#define STUNPUTC(p) (++sstrnleft, --p)
+#define STTOPC(p) p[-1]
+#define STADJUST(amount, p) (p += (amount), sstrnleft -= (amount))
+#define grabstackstr(p) stalloc((p) - stackblock())
+
+#define ckfree(p) free((pointer)(p))
diff --git a/bin/sh/miscbltin.c b/bin/sh/miscbltin.c
new file mode 100644
index 0000000..3988532
--- /dev/null
+++ b/bin/sh/miscbltin.c
@@ -0,0 +1,458 @@
+/* $NetBSD: miscbltin.c,v 1.44 2017/05/13 15:03:34 gson Exp $ */
+
+/*-
+ * Copyright (c) 1991, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Kenneth Almquist.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+#ifndef lint
+#if 0
+static char sccsid[] = "@(#)miscbltin.c 8.4 (Berkeley) 5/4/95";
+#else
+__RCSID("$NetBSD: miscbltin.c,v 1.44 2017/05/13 15:03:34 gson Exp $");
+#endif
+#endif /* not lint */
+
+/*
+ * Miscelaneous builtins.
+ */
+
+#include <sys/types.h> /* quad_t */
+#include <sys/param.h> /* BSD4_4 */
+#include <sys/stat.h>
+#include <sys/time.h>
+#include <sys/resource.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <ctype.h>
+#include <errno.h>
+
+#include "shell.h"
+#include "options.h"
+#include "var.h"
+#include "output.h"
+#include "memalloc.h"
+#include "error.h"
+#include "builtins.h"
+#include "mystring.h"
+
+#undef rflag
+
+
+
+/*
+ * The read builtin.
+ * Backslahes escape the next char unless -r is specified.
+ *
+ * This uses unbuffered input, which may be avoidable in some cases.
+ *
+ * Note that if IFS=' :' then read x y should work so that:
+ * 'a b' x='a', y='b'
+ * ' a b ' x='a', y='b'
+ * ':b' x='', y='b'
+ * ':' x='', y=''
+ * '::' x='', y=''
+ * ': :' x='', y=''
+ * ':::' x='', y='::'
+ * ':b c:' x='', y='b c:'
+ */
+
+int
+readcmd(int argc, char **argv)
+{
+ char **ap;
+ char c;
+ int rflag;
+ char *prompt;
+ const char *ifs;
+ char *p;
+ int startword;
+ int status;
+ int i;
+ int is_ifs;
+ int saveall = 0;
+
+ rflag = 0;
+ prompt = NULL;
+ while ((i = nextopt("p:r")) != '\0') {
+ if (i == 'p')
+ prompt = optionarg;
+ else
+ rflag = 1;
+ }
+
+ if (prompt && isatty(0)) {
+ out2str(prompt);
+ flushall();
+ }
+
+ if (*(ap = argptr) == NULL)
+ error("arg count");
+
+ if ((ifs = bltinlookup("IFS", 1)) == NULL)
+ ifs = " \t\n";
+
+ status = 0;
+ startword = 2;
+ STARTSTACKSTR(p);
+ for (;;) {
+ if (read(0, &c, 1) != 1) {
+ status = 1;
+ break;
+ }
+ if (c == '\0')
+ continue;
+ if (c == '\\' && !rflag) {
+ if (read(0, &c, 1) != 1) {
+ status = 1;
+ break;
+ }
+ if (c != '\n')
+ STPUTC(c, p);
+ continue;
+ }
+ if (c == '\n')
+ break;
+ if (strchr(ifs, c))
+ is_ifs = strchr(" \t\n", c) ? 1 : 2;
+ else
+ is_ifs = 0;
+
+ if (startword != 0) {
+ if (is_ifs == 1) {
+ /* Ignore leading IFS whitespace */
+ if (saveall)
+ STPUTC(c, p);
+ continue;
+ }
+ if (is_ifs == 2 && startword == 1) {
+ /* Only one non-whitespace IFS per word */
+ startword = 2;
+ if (saveall)
+ STPUTC(c, p);
+ continue;
+ }
+ }
+
+ if (is_ifs == 0) {
+ /* append this character to the current variable */
+ startword = 0;
+ if (saveall)
+ /* Not just a spare terminator */
+ saveall++;
+ STPUTC(c, p);
+ continue;
+ }
+
+ /* end of variable... */
+ startword = is_ifs;
+
+ if (ap[1] == NULL) {
+ /* Last variable needs all IFS chars */
+ saveall++;
+ STPUTC(c, p);
+ continue;
+ }
+
+ STACKSTRNUL(p);
+ setvar(*ap, stackblock(), 0);
+ ap++;
+ STARTSTACKSTR(p);
+ }
+ STACKSTRNUL(p);
+
+ /* Remove trailing IFS chars */
+ for (; stackblock() <= --p; *p = 0) {
+ if (!strchr(ifs, *p))
+ break;
+ if (strchr(" \t\n", *p))
+ /* Always remove whitespace */
+ continue;
+ if (saveall > 1)
+ /* Don't remove non-whitespace unless it was naked */
+ break;
+ }
+ setvar(*ap, stackblock(), 0);
+
+ /* Set any remaining args to "" */
+ while (*++ap != NULL)
+ setvar(*ap, nullstr, 0);
+ return status;
+}
+
+
+
+int
+umaskcmd(int argc, char **argv)
+{
+ char *ap;
+ int mask;
+ int i;
+ int symbolic_mode = 0;
+
+ while ((i = nextopt("S")) != '\0') {
+ symbolic_mode = 1;
+ }
+
+ INTOFF;
+ mask = umask(0);
+ umask(mask);
+ INTON;
+
+ if ((ap = *argptr) == NULL) {
+ if (symbolic_mode) {
+ char u[4], g[4], o[4];
+
+ i = 0;
+ if ((mask & S_IRUSR) == 0)
+ u[i++] = 'r';
+ if ((mask & S_IWUSR) == 0)
+ u[i++] = 'w';
+ if ((mask & S_IXUSR) == 0)
+ u[i++] = 'x';
+ u[i] = '\0';
+
+ i = 0;
+ if ((mask & S_IRGRP) == 0)
+ g[i++] = 'r';
+ if ((mask & S_IWGRP) == 0)
+ g[i++] = 'w';
+ if ((mask & S_IXGRP) == 0)
+ g[i++] = 'x';
+ g[i] = '\0';
+
+ i = 0;
+ if ((mask & S_IROTH) == 0)
+ o[i++] = 'r';
+ if ((mask & S_IWOTH) == 0)
+ o[i++] = 'w';
+ if ((mask & S_IXOTH) == 0)
+ o[i++] = 'x';
+ o[i] = '\0';
+
+ out1fmt("u=%s,g=%s,o=%s\n", u, g, o);
+ } else {
+ out1fmt("%.4o\n", mask);
+ }
+ } else {
+ if (isdigit((unsigned char)*ap)) {
+ mask = 0;
+ do {
+ if (*ap >= '8' || *ap < '0')
+ error("Illegal number: %s", argv[1]);
+ mask = (mask << 3) + (*ap - '0');
+ } while (*++ap != '\0');
+ umask(mask);
+ } else {
+ void *set;
+
+ INTOFF;
+ if ((set = setmode(ap)) != 0) {
+ mask = getmode(set, ~mask & 0777);
+ ckfree(set);
+ }
+ INTON;
+ if (!set)
+ error("Cannot set mode `%s' (%s)", ap,
+ strerror(errno));
+
+ umask(~mask & 0777);
+ }
+ }
+ return 0;
+}
+
+/*
+ * ulimit builtin
+ *
+ * This code, originally by Doug Gwyn, Doug Kingston, Eric Gisin, and
+ * Michael Rendell was ripped from pdksh 5.0.8 and hacked for use with
+ * ash by J.T. Conklin.
+ *
+ * Public domain.
+ */
+
+struct limits {
+ const char *name;
+ const char *unit;
+ int cmd;
+ int factor; /* multiply by to get rlim_{cur,max} values */
+ char option;
+};
+
+static const struct limits limits[] = {
+#ifdef RLIMIT_CPU
+ { "time", "seconds", RLIMIT_CPU, 1, 't' },
+#endif
+#ifdef RLIMIT_FSIZE
+ { "file", "blocks", RLIMIT_FSIZE, 512, 'f' },
+#endif
+#ifdef RLIMIT_DATA
+ { "data", "kbytes", RLIMIT_DATA, 1024, 'd' },
+#endif
+#ifdef RLIMIT_STACK
+ { "stack", "kbytes", RLIMIT_STACK, 1024, 's' },
+#endif
+#ifdef RLIMIT_CORE
+ { "coredump", "blocks", RLIMIT_CORE, 512, 'c' },
+#endif
+#ifdef RLIMIT_RSS
+ { "memory", "kbytes", RLIMIT_RSS, 1024, 'm' },
+#endif
+#ifdef RLIMIT_MEMLOCK
+ { "locked memory","kbytes", RLIMIT_MEMLOCK, 1024, 'l' },
+#endif
+#ifdef RLIMIT_NTHR
+ { "thread", "threads", RLIMIT_NTHR, 1, 'r' },
+#endif
+#ifdef RLIMIT_NPROC
+ { "process", "processes", RLIMIT_NPROC, 1, 'p' },
+#endif
+#ifdef RLIMIT_NOFILE
+ { "nofiles", "descriptors", RLIMIT_NOFILE, 1, 'n' },
+#endif
+#ifdef RLIMIT_VMEM
+ { "vmemory", "kbytes", RLIMIT_VMEM, 1024, 'v' },
+#endif
+#ifdef RLIMIT_SWAP
+ { "swap", "kbytes", RLIMIT_SWAP, 1024, 'w' },
+#endif
+#ifdef RLIMIT_SBSIZE
+ { "sbsize", "bytes", RLIMIT_SBSIZE, 1, 'b' },
+#endif
+ { NULL, NULL, 0, 0, '\0' }
+};
+
+int
+ulimitcmd(int argc, char **argv)
+{
+ int c;
+ rlim_t val = 0;
+ enum { SOFT = 0x1, HARD = 0x2 }
+ how = SOFT | HARD;
+ const struct limits *l;
+ int set, all = 0;
+ int optc, what;
+ struct rlimit limit;
+
+ what = 'f';
+ while ((optc = nextopt("HSabtfdscmlrpnv")) != '\0')
+ switch (optc) {
+ case 'H':
+ how = HARD;
+ break;
+ case 'S':
+ how = SOFT;
+ break;
+ case 'a':
+ all = 1;
+ break;
+ default:
+ what = optc;
+ }
+
+ for (l = limits; l->name && l->option != what; l++)
+ ;
+ if (!l->name)
+ error("internal error (%c)", what);
+
+ set = *argptr ? 1 : 0;
+ if (set) {
+ char *p = *argptr;
+
+ if (all || argptr[1])
+ error("too many arguments");
+ if (strcmp(p, "unlimited") == 0)
+ val = RLIM_INFINITY;
+ else {
+ val = (rlim_t) 0;
+
+ while ((c = *p++) >= '0' && c <= '9')
+ val = (val * 10) + (long)(c - '0');
+ if (c)
+ error("bad number");
+ val *= l->factor;
+ }
+ }
+ if (all) {
+ for (l = limits; l->name; l++) {
+ getrlimit(l->cmd, &limit);
+ if (how & SOFT)
+ val = limit.rlim_cur;
+ else if (how & HARD)
+ val = limit.rlim_max;
+
+ out1fmt("%-13s (-%c %-11s) ", l->name, l->option,
+ l->unit);
+ if (val == RLIM_INFINITY)
+ out1fmt("unlimited\n");
+ else
+ {
+ val /= l->factor;
+#ifdef BSD4_4
+ out1fmt("%lld\n", (long long) val);
+#else
+ out1fmt("%ld\n", (long) val);
+#endif
+ }
+ }
+ return 0;
+ }
+
+ if (getrlimit(l->cmd, &limit) == -1)
+ error("error getting limit (%s)", strerror(errno));
+ if (set) {
+ if (how & HARD)
+ limit.rlim_max = val;
+ if (how & SOFT)
+ limit.rlim_cur = val;
+ if (setrlimit(l->cmd, &limit) < 0)
+ error("error setting limit (%s)", strerror(errno));
+ } else {
+ if (how & SOFT)
+ val = limit.rlim_cur;
+ else if (how & HARD)
+ val = limit.rlim_max;
+
+ if (val == RLIM_INFINITY)
+ out1fmt("unlimited\n");
+ else
+ {
+ val /= l->factor;
+#ifdef BSD4_4
+ out1fmt("%lld\n", (long long) val);
+#else
+ out1fmt("%ld\n", (long) val);
+#endif
+ }
+ }
+ return 0;
+}
diff --git a/bin/sh/miscbltin.h b/bin/sh/miscbltin.h
new file mode 100644
index 0000000..4c12c82
--- /dev/null
+++ b/bin/sh/miscbltin.h
@@ -0,0 +1,31 @@
+/* $NetBSD: miscbltin.h,v 1.3 2003/08/21 17:57:53 christos Exp $ */
+
+/*
+ * Copyright (c) 1997 Christos Zoulas. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+int readcmd(int, char **);
+int umaskcmd(int, char **);
+int ulimitcmd(int, char **);
diff --git a/bin/sh/mkbuiltins b/bin/sh/mkbuiltins
new file mode 100644
index 0000000..2ebf7ac
--- /dev/null
+++ b/bin/sh/mkbuiltins
@@ -0,0 +1,136 @@
+#!/bin/sh -
+# $NetBSD: mkbuiltins,v 1.22 2009/10/06 19:56:58 apb Exp $
+#
+# Copyright (c) 1991, 1993
+# The Regents of the University of California. All rights reserved.
+#
+# This code is derived from software contributed to Berkeley by
+# Kenneth Almquist.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1. Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+# 3. Neither the name of the University nor the names of its contributors
+# may be used to endorse or promote products derived from this software
+# without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+# ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+# SUCH DAMAGE.
+#
+# @(#)mkbuiltins 8.2 (Berkeley) 5/4/95
+
+havehist=1
+if [ x"$1" = x"-h" ]; then
+ havehist=0
+ shift
+fi
+
+shell=$1
+builtins=$2
+objdir=$3
+
+havejobs=0
+if grep '^#define JOBS[ ]*1' ${shell} > /dev/null
+then
+ havejobs=1
+fi
+
+exec <$builtins 3> ${objdir}/builtins.c 4> ${objdir}/builtins.h
+
+echo '/*
+ * This file was generated by the mkbuiltins program.
+ */
+
+#include "shell.h"
+#include "builtins.h"
+
+const struct builtincmd builtincmd[] = {
+' >&3
+
+echo '/*
+ * This file was generated by the mkbuiltins program.
+ */
+
+#include <sys/cdefs.h>
+
+struct builtincmd {
+ const char *name;
+ int (*builtin)(int, char **);
+};
+
+extern const struct builtincmd builtincmd[];
+extern const struct builtincmd splbltincmd[];
+
+' >&4
+
+specials=
+
+while read line
+do
+ set -- $line
+ [ -z "$1" ] && continue
+ case "$1" in
+ \#if*|\#def*|\#end*)
+ echo $line >&3
+ echo $line >&4
+ continue
+ ;;
+ \#*)
+ continue
+ ;;
+ esac
+
+ func=$1
+ shift
+ [ x"$1" = x'-j' ] && {
+ [ $havejobs = 0 ] && continue
+ shift
+ }
+ [ x"$1" = x'-h' ] && {
+ [ $havehist = 0 ] && continue
+ shift
+ }
+ echo 'int '"$func"'(int, char **);' >&4
+ while
+ [ $# != 0 ] && [ x"$1" != x'#' ]
+ do
+ [ x"$1" = x'-s' ] && {
+ specials="$specials $2 $func"
+ shift 2
+ continue;
+ }
+ [ x"$1" = x'-u' ] && shift
+ echo ' { "'$1'", '"$func"' },' >&3
+ shift
+ done
+done
+
+echo ' { 0, 0 },' >&3
+echo '};' >&3
+echo >&3
+echo 'const struct builtincmd splbltincmd[] = {' >&3
+
+set -- $specials
+while
+ [ $# != 0 ]
+do
+ echo ' { "'$1'", '"$2"' },' >&3
+ shift 2
+done
+
+echo ' { 0, 0 },' >&3
+echo "};" >&3
diff --git a/bin/sh/mkinit.sh b/bin/sh/mkinit.sh
new file mode 100755
index 0000000..da4f46f
--- /dev/null
+++ b/bin/sh/mkinit.sh
@@ -0,0 +1,224 @@
+#! /bin/sh
+# $NetBSD: mkinit.sh,v 1.10 2018/12/05 09:20:18 kre Exp $
+
+# Copyright (c) 2003 The NetBSD Foundation, Inc.
+# All rights reserved.
+#
+# This code is derived from software contributed to The NetBSD Foundation
+# by David Laight.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1. Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
+# ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
+# BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+# POSSIBILITY OF SUCH DAMAGE.
+
+srcs="$*"
+
+# use of echo in this script is broken
+
+# some echo versions will expand \n in the args, which breaks C
+# Note: this script is a HOST_PROG ... it must run in the
+# build host's environment, with its shell.
+
+# Fortunately, use of echo here is also trivially simplistic,
+# we can easily replace all uses with ...
+
+echo()
+{
+ printf '%s\n' "$1"
+}
+
+# CAUTION: for anyone modifying this script.... use printf
+# rather than echo to output anything at all... then
+# you will avoid being bitten by the simplicity of this function.
+# This was done this way rather than wholesale replacement
+# to avoid unnecessary code churn.
+
+
+nl='
+'
+openparen='('
+
+# shells have bugs (including older NetBSD sh) in how \ is
+# used in pattern matching. So work out what the shell
+# running this script expects. We could also just use a
+# literal \ in the pattern, which would need to be quoted
+# of course, but then we'd run into a whole host of potential
+# other shell bugs (both with the quoting in the pattern, and
+# with the matching that follows if that works as inended).
+# Far easier, and more reliable, is to just work out what works,
+# and then use it, which more or less mandates using a variable...
+backslash='\\'
+var='abc\' # dummy test case.
+if [ "$var" = "${var%$backslash}" ]
+then
+ # buggy sh, try the broken way
+ backslash='\'
+ if [ "$var" = "${var%$backslash}" ]
+ then
+ printf >&2 "$0: %s\n" 'No pattern match with \ (broken shell)'
+ exit 1
+ fi
+fi
+# We know we can detect the presence of a trailing \, which is all we need.
+# Now to confirm we will not generate false matches.
+var='abc'
+if [ "$var" != "${var%$backslash}" ]
+then
+ printf >&2 "$0: %s\n" 'Bogus pattern match with \ (broken shell)'
+ exit 1
+fi
+unset var
+
+includes=' "shell.h" "mystring.h" "init.h" '
+defines=
+decles=
+event_init=
+event_reset=
+event_shellproc=
+
+for src in $srcs; do
+ exec <$src
+ decnl="$nl"
+ while IFS=; read -r line; do
+ [ "$line" = x ]
+ case "$line " in
+ INIT["{ "]* ) event=init;;
+ RESET["{ "]* ) event=reset;;
+ SHELLPROC["{ "]* ) event=shellproc;;
+ INCLUDE[\ \ ]* )
+ IFS=' '
+ set -- $line
+ # ignore duplicates
+ [ "${includes}" != "${includes% $2 *}" ] && continue
+ includes="$includes$2 "
+ continue
+ ;;
+ MKINIT\ )
+ # struct declaration
+ decles="$decles$nl"
+ while
+ read -r line
+ decles="${decles}${line}${nl}"
+ [ "$line" != "};" ]
+ do
+ :
+ done
+ decnl="$nl"
+ continue
+ ;;
+ MKINIT["{ "]* )
+ # strip initialiser
+ def=${line#MKINIT}
+ comment="${def#*;}"
+ def="${def%;$comment}"
+ def="${def%%=*}"
+ def="${def% }"
+ decles="${decles}${decnl}extern${def};${comment}${nl}"
+ decnl=
+ continue
+ ;;
+ \#define[\ \ ]* )
+ IFS=' '
+ set -- $line
+ # Ignore those with arguments
+ [ "$2" = "${2##*$openparen}" ] || continue
+ # and multiline definitions
+ [ "$line" = "${line%$backslash}" ] || continue
+ defines="${defines}#undef $2${nl}${line}${nl}"
+ continue
+ ;;
+ * ) continue;;
+ esac
+ # code for events
+ ev="${nl} /* from $src: */${nl} {${nl}"
+ # Indent the text by an extra <tab>
+ while
+ read -r line
+ [ "$line" != "}" ]
+ do
+ case "$line" in
+ ('') ;;
+ ('#'*) ;;
+ (*) line=" $line";;
+ esac
+ ev="${ev}${line}${nl}"
+ done
+ ev="${ev} }${nl}"
+ eval event_$event=\"\$event_$event\$ev\"
+ done
+done
+
+exec >init.c.tmp
+
+echo "/*"
+echo " * This file was generated by the mkinit program."
+echo " */"
+echo
+
+IFS=' '
+for f in $includes; do
+ echo "#include $f"
+done
+
+echo
+echo
+echo
+echo "$defines"
+echo
+echo "$decles"
+echo
+echo
+echo "/*"
+echo " * Initialization code."
+echo " */"
+echo
+echo "void"
+echo "init(void)"
+echo "{"
+echo "${event_init}"
+echo "}"
+echo
+echo
+echo
+echo "/*"
+echo " * This routine is called when an error or an interrupt occurs in an"
+echo " * interactive shell and control is returned to the main command loop."
+echo " */"
+echo
+echo "void"
+echo "reset(void)"
+echo "{"
+echo "${event_reset}"
+echo "}"
+echo
+echo
+echo
+echo "/*"
+echo " * This routine is called to initialize the shell to run a shell procedure."
+echo " */"
+echo
+echo "void"
+echo "initshellproc(void)"
+echo "{"
+echo "${event_shellproc}"
+echo "}"
+
+exec >&-
+mv init.c.tmp init.c
diff --git a/bin/sh/mknodenames.sh b/bin/sh/mknodenames.sh
new file mode 100755
index 0000000..1d03200
--- /dev/null
+++ b/bin/sh/mknodenames.sh
@@ -0,0 +1,69 @@
+#! /bin/sh
+
+# $NetBSD: mknodenames.sh,v 1.6 2018/08/18 03:09:37 kre Exp $
+
+# Use this script however you like, but it would be amazing if
+# it has any purpose other than as part of building the shell...
+
+if [ -z "$1" ]; then
+ echo "Usage: $0 nodes.h" 1>&2
+ exit 1
+fi
+
+NODES=$1
+
+test -t 1 && test -z "$2" && exec > nodenames.h
+
+echo "\
+/*
+ * Automatically generated by $0
+ * DO NOT EDIT. Do Not 'cvs add'.
+ */
+"
+echo "#ifndef NODENAMES_H_INCLUDED"
+echo "#define NODENAMES_H_INCLUDED"
+echo
+echo "#ifdef DEBUG"
+
+MAX=$(awk < "$NODES" '
+ /#define/ {
+ if ($3 > MAX) MAX = $3
+ }
+ END { print MAX }
+')
+
+echo
+echo '#ifdef DEFINE_NODENAMES'
+echo "STATIC const char * const NodeNames[${MAX} + 1] = {"
+
+grep '^#define' "$NODES" | sort -k3n | while read define name number opt_comment
+do
+ : ${next:=0}
+ while [ "$number" -gt "$next" ]
+ do
+ echo ' "???",'
+ next=$(( next + 1))
+ done
+ echo ' "'"$name"'",'
+ next=$(( number + 1 ))
+done
+
+echo "};"
+echo '#else'
+echo "extern const char * const NodeNames[${MAX} + 1];"
+echo '#endif'
+echo
+echo '#define NODETYPENAME(type) \'
+echo ' ((unsigned)(type) <= '"${MAX}"' ? NodeNames[(type)] : "??OOR??")'
+echo
+echo '#define NODETYPE(type) NODETYPENAME(type), (type)'
+echo '#define PRIdsNT "s(%d)"'
+echo
+echo '#else /* DEBUG */'
+echo
+echo '#define NODETYPE(type) (type)'
+echo '#define PRIdsNT "d"'
+echo
+echo '#endif /* DEBUG */'
+echo
+echo '#endif /* !NODENAMES_H_INCLUDED */'
diff --git a/bin/sh/mknodes.sh b/bin/sh/mknodes.sh
new file mode 100755
index 0000000..0b1ab80
--- /dev/null
+++ b/bin/sh/mknodes.sh
@@ -0,0 +1,242 @@
+#! /bin/sh
+# $NetBSD: mknodes.sh,v 1.4 2019/01/19 13:08:50 kre Exp $
+
+# Copyright (c) 2003 The NetBSD Foundation, Inc.
+# All rights reserved.
+#
+# This code is derived from software contributed to The NetBSD Foundation
+# by David Laight.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1. Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
+# ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
+# BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+# POSSIBILITY OF SUCH DAMAGE.
+
+nodetypes=$1
+nodes_pat=$2
+objdir="$3"
+
+exec <$nodetypes
+exec >$objdir/nodes.h.tmp
+
+echo "/*"
+echo " * This file was generated by mknodes.sh"
+echo " */"
+echo
+
+tagno=0
+while IFS=; read -r line; do
+ line="${line%%#*}"
+ IFS=' '
+ set -- $line
+ IFS=
+ [ -z "$2" ] && continue
+ case "$line" in
+ [" "]* )
+ IFS=' '
+ [ $field = 0 ] && struct_list="$struct_list $struct"
+ eval field_${struct}_$field=\"\$*\"
+ eval numfld_$struct=\$field
+ field=$(($field + 1))
+ ;;
+ * )
+ define=$1
+ struct=$2
+ echo "#define $define $tagno"
+ tagno=$(($tagno + 1))
+ eval define_$struct=\"\$define_$struct \$define\"
+ struct_define="$struct_define $struct"
+ field=0
+ ;;
+ esac
+done
+
+echo
+
+IFS=' '
+for struct in $struct_list; do
+ echo
+ echo
+ echo "struct $struct {"
+ field=0
+ while
+ eval line=\"\$field_${struct}_$field\"
+ field=$(($field + 1))
+ [ -n "$line" ]
+ do
+ IFS=' '
+ set -- $line
+ name=$1
+ case "$name" in
+ type) if [ -n "$typetype" ] && [ "$typetype" != "$2" ]
+ then
+ echo >&2 "Conflicting type fields: node" \
+ "$struct has $2, others $typetype"
+ exit 1
+ fi
+ if [ $field -ne 1 ]
+ then
+ echo >&2 "Node $struct has type as field" \
+ "$field (should only be first)"
+ exit 1
+ fi
+ typetype=$2
+ ;;
+ *)
+ if [ $field -eq 1 ]
+ then
+ echo >&2 "Node $struct does not have" \
+ "type as first field"
+ exit 1
+ fi
+ ;;
+ esac
+ case $2 in
+ nodeptr ) type="union node *";;
+ nodelist ) type="struct nodelist *";;
+ string ) type="char *";;
+ int*_t | uint*_t | int ) type="$2 ";;
+ * ) name=; shift 2; type="$*";;
+ esac
+ echo " $type$name;"
+ done
+ echo "};"
+done
+
+echo
+echo
+echo "union node {"
+echo " $typetype type;"
+for struct in $struct_list; do
+ echo " struct $struct $struct;"
+done
+echo "};"
+echo
+echo
+echo "struct nodelist {"
+echo " struct nodelist *next;"
+echo " union node *n;"
+echo "};"
+echo
+echo
+echo 'struct funcdef;'
+echo 'struct funcdef *copyfunc(union node *);'
+echo 'union node *getfuncnode(struct funcdef *);'
+echo 'void reffunc(struct funcdef *);'
+echo 'void unreffunc(struct funcdef *);'
+echo 'void freefunc(struct funcdef *);'
+
+mv $objdir/nodes.h.tmp $objdir/nodes.h || exit 1
+
+exec <$nodes_pat
+exec >$objdir/nodes.c.tmp
+
+echo "/*"
+echo " * This file was generated by mknodes.sh"
+echo " */"
+echo
+
+while IFS=; read -r line; do
+ IFS=' '
+ set -- $line
+ IFS=
+ case "$1" in
+ '%SIZES' )
+ echo "static const short nodesize[$tagno] = {"
+ IFS=' '
+ for struct in $struct_define; do
+ echo " SHELL_ALIGN(sizeof (struct $struct)),"
+ done
+ echo "};"
+ ;;
+ '%CALCSIZE' )
+ echo " if (n == NULL)"
+ echo " return;"
+ echo " res->bsize += nodesize[n->type];"
+ echo " switch (n->type) {"
+ IFS=' '
+ for struct in $struct_list; do
+ eval defines=\"\$define_$struct\"
+ for define in $defines; do
+ echo " case $define:"
+ done
+ eval field=\$numfld_$struct
+ while
+ [ $field != 0 ]
+ do
+ eval line=\"\$field_${struct}_$field\"
+ field=$(($field - 1))
+ IFS=' '
+ set -- $line
+ name=$1
+ cl=", res)"
+ case $2 in
+ nodeptr ) fn=calcsize;;
+ nodelist ) fn=sizenodelist;;
+ string ) fn="res->ssize += strlen"
+ cl=") + 1";;
+ * ) continue;;
+ esac
+ echo " ${fn}(n->$struct.$name${cl};"
+ done
+ echo " break;"
+ done
+ echo " };"
+ ;;
+ '%COPY' )
+ echo " if (n == NULL)"
+ echo " return NULL;"
+ echo " new = st->block;"
+ echo " st->block = (char *) st->block + nodesize[n->type];"
+ echo " switch (n->type) {"
+ IFS=' '
+ for struct in $struct_list; do
+ eval defines=\"\$define_$struct\"
+ for define in $defines; do
+ echo " case $define:"
+ done
+ eval field=\$numfld_$struct
+ while
+ [ $field != 0 ]
+ do
+ eval line=\"\$field_${struct}_$field\"
+ field=$(($field - 1))
+ IFS=' '
+ set -- $line
+ name=$1
+ case $2 in
+ nodeptr ) fn="copynode(";;
+ nodelist ) fn="copynodelist(";;
+ string ) fn="nodesavestr(";;
+ int*_t| uint*_t | int ) fn=;;
+ * ) continue;;
+ esac
+ f="$struct.$name"
+ echo " new->$f = ${fn}n->$f${fn:+, st)};"
+ done
+ echo " break;"
+ done
+ echo " };"
+ echo " new->type = n->type;"
+ ;;
+ * ) echo "$line";;
+ esac
+done
+
+mv $objdir/nodes.c.tmp $objdir/nodes.c || exit 1
diff --git a/bin/sh/mkoptions.sh b/bin/sh/mkoptions.sh
new file mode 100644
index 0000000..aecb6df
--- /dev/null
+++ b/bin/sh/mkoptions.sh
@@ -0,0 +1,198 @@
+#! /bin/sh
+
+# $NetBSD: mkoptions.sh,v 1.5 2017/11/15 09:21:19 kre Exp $
+
+#
+# It would be more sensible to generate 2 .h files, one which
+# is for everyone to use, defines the "variables" and (perhaps) generates
+# the externs (though they could just be explicit in options.h)
+# and one just for options.c which generates the initialisation.
+#
+# But then I'd have to deal with making the Makefile handle that properly...
+# (this is simpler there, and it just means a bit more sh compile time.)
+
+set -f
+IFS=' ' # blank, tab (no newline)
+
+IF="$1"
+OF="${3+$3/}$2"
+
+E_FILE=$(${MKTEMP:-mktemp} -t MKO.E.$$)
+O_FILE=$(${MKTEMP:-mktemp} -t MKO.O.$$)
+trap 'rm -f "${E_FILE}" "${O_FILE}"' EXIT
+
+exec 5> "${E_FILE}"
+exec 6> "${O_FILE}"
+
+{
+ printf '/*\n * File automatically generated by %s.\n' "$0"
+ printf ' * Do not edit, do not add to cvs.\n'
+ printf ' */\n\n'
+
+ printf '#ifdef DEFINE_OPTIONS\n'
+ printf 'struct optent optlist[] = {\n'
+} >"${OF}"
+
+FIRST=true
+
+${SED:-sed} <"${IF}" \
+ -e '/^$/d' \
+ -e '/^#/d' \
+ -e '/^[ ]*\//d' \
+ -e '/^[ ]*\*/d' \
+ -e '/^[ ]*;/d' |
+sort -b -k2,2f -k2,2 < "${IF}" |
+while read line
+do
+ # Look for comments in various styles, and ignore them
+ # (these should generally be already removed by sed above)
+
+ case "${line}" in
+ '') continue;;
+ /*) continue;;
+ \**) continue;;
+ \;*) continue;;
+ \#*) continue;;
+ esac
+
+ case "${line}" in
+ *'#if'*)
+ COND="${line#*#}"
+ COND="#${COND%%#*}"
+ ;;
+ *)
+ COND=
+ ;;
+ esac
+ set -- ${line%%[ ]#*}
+
+ var="$1" name="$2"
+
+ case "${var}" in
+ ('' | [!A-Za-z_]* | *[!A-Za-z0-9_]*)
+ printf >&2 "Bad var name: '%s'\\n" "${var}"
+ # exit 1
+ continue # just ignore it for now
+ esac
+
+ case "${name}" in
+ ?) set -- ${var} '' $name $3 $4; name= ;;
+ esac
+
+ chr="$3" set="$4" dflt="$5"
+
+ case "${chr}" in
+ -) chr= set= dflt="$4";;
+ ''|?) ;;
+ *) printf >&2 'flag "%s": Not a character\n' "${chr}"; continue;;
+ esac
+
+ # options must have some kind of name, or they are useless...
+ test -z "${name}${chr}" && continue
+
+ case "${set}" in
+ -) set= ;;
+ [01] | [Oo][Nn] | [Oo][Ff][Ff]) dflt="${set}"; set= ;;
+ ''|?) ;;
+ *) printf >&2 'set "%s": Not a character\n' "${set}"; continue;;
+ esac
+
+ case "${dflt}" in
+ '') ;;
+ [Oo][Nn]) dflt=1;;
+ [Oo][Ff][Ff]) dflt=0;;
+ [01]) ;;
+ *) printf >&2 'default "%s" invalid, use 0 off 1 on\n'; continue;;
+ esac
+
+ # validation complete, now to generate output
+
+ if [ -n "${COND}" ]
+ then
+ printf '%s\n' "${COND}" >&4
+ printf '%s\n' "${COND}" >&5
+ printf '%s\n' "${COND}" >&6
+ fi
+
+ printf '\t_SH_OPT_%s,\n' "${var}" >&5
+
+ if [ -n "${name}" ]
+ then
+ printf ' { "%s", ' "${name}" >&4
+ else
+ printf ' { 0, ' >&4
+ fi
+
+ if [ -n "${chr}" ]
+ then
+ printf "'%s', " "${chr}" >&4
+ else
+ chr=
+ printf '0, ' >&4
+ fi
+
+ if [ -n "${set}" ]
+ then
+ printf "'%s', 0, " "${set}" >&4
+ else
+ printf '0, 0, ' >&4
+ fi
+
+ if [ -n "${dflt}" ]
+ then
+ printf '%s },\n' "${dflt}" >&4
+ else
+ printf '0 },\n' >&4
+ fi
+
+ printf '#define %s\toptlist[_SH_OPT_%s].val\n' "${var}" "${var}" >&6
+
+ if [ -n "${COND}" ]
+ then
+ printf '#endif\n' >&4
+ printf '#endif\n' >&5
+ printf '#endif\n' >&6
+ fi
+
+ test -z "${chr}" && continue
+
+ printf '%s _SH_OPT_%s %s\n' "${chr}" "${var}" "${COND}"
+
+done 4>>"${OF}" | sort -t' ' -k1,1f -k1,1 | while read chr index COND
+do
+ if $FIRST
+ then
+ printf ' { 0, 0, 0, 0, 0 }\n};\n'
+ printf '#endif\n\n'
+
+ printf 'enum shell_opt_names {\n'
+ cat "${E_FILE}"
+ printf '};\n\n'
+
+ printf '#ifdef DEFINE_OPTIONS\n'
+ printf 'const unsigned char optorder[] = {\n'
+ FIRST=false
+ fi
+ [ -n "${COND}" ] && printf '%s\n' "${COND}"
+ printf '\t%s,\n' "${index}"
+ [ -n "${COND}" ] && printf '#endif\n'
+
+done >>"${OF}"
+
+{
+ printf '};\n\n'
+ printf '#define NOPTS (sizeof optlist / sizeof optlist[0] - 1)\n'
+ printf 'int sizeof_optlist = sizeof optlist;\n\n'
+ printf \
+ 'const int option_flags = (sizeof optorder / sizeof optorder[0]);\n'
+ printf '\n#else\n\n'
+ printf 'extern struct optent optlist[];\n'
+ printf 'extern int sizeof_optlist;\n'
+ printf 'extern const unsigned char optorder[];\n'
+ printf 'extern const int option_flags;\n'
+ printf '\n#endif\n\n'
+
+ cat "${O_FILE}"
+} >> "${OF}"
+
+exit 0
diff --git a/bin/sh/mktokens b/bin/sh/mktokens
new file mode 100644
index 0000000..a8f81c4
--- /dev/null
+++ b/bin/sh/mktokens
@@ -0,0 +1,99 @@
+#!/bin/sh -
+# $NetBSD: mktokens,v 1.14 2017/07/26 03:46:54 kre Exp $
+#
+# Copyright (c) 1991, 1993
+# The Regents of the University of California. All rights reserved.
+#
+# This code is derived from software contributed to Berkeley by
+# Kenneth Almquist.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1. Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+# 3. Neither the name of the University nor the names of its contributors
+# may be used to endorse or promote products derived from this software
+# without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+# ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+# SUCH DAMAGE.
+#
+# @(#)mktokens 8.1 (Berkeley) 5/31/93
+
+: ${AWK:=awk}
+: ${SED:=sed}
+
+# The following is a list of tokens. The second column is nonzero if the
+# token marks the end of a list. The third column is the name to print in
+# error messages.
+
+# Note that all the keyword tokens come after TWORD, and all the others
+# come before it. We rely upon that relationship - keep it!
+
+cat > /tmp/ka$$ <<\!
+TEOF 1 end of file
+TNL 0 newline
+TSEMI 0 ";"
+TBACKGND 0 "&"
+TAND 0 "&&"
+TOR 0 "||"
+TPIPE 0 "|"
+TLP 0 "("
+TRP 1 ")"
+TENDCASE 1 ";;"
+TCASEFALL 1 ";&"
+TENDBQUOTE 1 "`"
+TREDIR 0 redirection
+TWORD 0 word
+TIF 0 "if"
+TTHEN 1 "then"
+TELSE 1 "else"
+TELIF 1 "elif"
+TFI 1 "fi"
+TWHILE 0 "while"
+TUNTIL 0 "until"
+TFOR 0 "for"
+TDO 1 "do"
+TDONE 1 "done"
+TBEGIN 0 "{"
+TEND 1 "}"
+TCASE 0 "case"
+TESAC 1 "esac"
+TNOT 0 "!"
+!
+nl=`wc -l /tmp/ka$$`
+exec > token.h
+${AWK} '{print "#define " $1 " " NR-1}' /tmp/ka$$
+echo '
+/* Array indicating which tokens mark the end of a list */
+const char tokendlist[] = {'
+${AWK} '{print "\t" $2 ","}' /tmp/ka$$
+echo '};
+
+const char *const tokname[] = {'
+${SED} -e 's/"/\\"/g' \
+ -e 's/[^ ]*[ ][ ]*[^ ]*[ ][ ]*\(.*\)/ "\1",/' \
+ /tmp/ka$$
+echo '};
+'
+${SED} 's/"//g' /tmp/ka$$ | ${AWK} '
+/TWORD/{print "#define KWDOFFSET " NR; print "";
+ print "const char *const parsekwd[] = {"}
+/TIF/,/neverfound/{print " \"" $3 "\","}'
+echo ' 0
+};'
+
+rm /tmp/ka$$
diff --git a/bin/sh/myhistedit.h b/bin/sh/myhistedit.h
new file mode 100644
index 0000000..855c1bc
--- /dev/null
+++ b/bin/sh/myhistedit.h
@@ -0,0 +1,48 @@
+/* $NetBSD: myhistedit.h,v 1.13 2017/06/28 13:46:06 kre Exp $ */
+
+/*-
+ * Copyright (c) 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * @(#)myhistedit.h 8.2 (Berkeley) 5/4/95
+ */
+
+#include <histedit.h>
+
+extern History *hist;
+extern EditLine *el;
+extern int displayhist;
+
+void histedit(void);
+void sethistsize(const char *);
+void setterm(const char *);
+int inputrc(int, char **);
+void set_editrc(const char *);
+void set_prompt_lit(const char *);
+int not_fcnumber(char *);
+int str_to_event(const char *, int);
+
diff --git a/bin/sh/mystring.c b/bin/sh/mystring.c
new file mode 100644
index 0000000..9b6b40b
--- /dev/null
+++ b/bin/sh/mystring.c
@@ -0,0 +1,140 @@
+/* $NetBSD: mystring.c,v 1.18 2018/07/13 22:43:44 kre Exp $ */
+
+/*-
+ * Copyright (c) 1991, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Kenneth Almquist.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+#ifndef lint
+#if 0
+static char sccsid[] = "@(#)mystring.c 8.2 (Berkeley) 5/4/95";
+#else
+__RCSID("$NetBSD: mystring.c,v 1.18 2018/07/13 22:43:44 kre Exp $");
+#endif
+#endif /* not lint */
+
+/*
+ * String functions.
+ *
+ * equal(s1, s2) Return true if strings are equal.
+ * scopy(from, to) Copy a string.
+ * scopyn(from, to, n) Like scopy, but checks for overflow.
+ * number(s) Convert a string of digits to an integer.
+ * is_number(s) Return true if s is a string of digits.
+ */
+
+#include <inttypes.h>
+#include <limits.h>
+#include <stdlib.h>
+#include "shell.h"
+#include "syntax.h"
+#include "error.h"
+#include "mystring.h"
+
+
+const char nullstr[1]; /* zero length string */
+
+/*
+ * equal - #defined in mystring.h
+ */
+
+/*
+ * scopy - #defined in mystring.h
+ */
+
+
+/*
+ * scopyn - copy a string from "from" to "to", truncating the string
+ * if necessary. "To" is always nul terminated, even if
+ * truncation is performed. "Size" is the size of "to".
+ */
+
+void
+scopyn(const char *from, char *to, int size)
+{
+
+ while (--size > 0) {
+ if ((*to++ = *from++) == '\0')
+ return;
+ }
+ *to = '\0';
+}
+
+
+/*
+ * prefix -- see if pfx is a prefix of string.
+ */
+
+int
+prefix(const char *pfx, const char *string)
+{
+ while (*pfx) {
+ if (*pfx++ != *string++)
+ return 0;
+ }
+ return 1;
+}
+
+
+/*
+ * Convert a string of digits to an integer, printing an error message on
+ * failure.
+ */
+
+int
+number(const char *s)
+{
+ char *ep = NULL;
+ intmax_t n;
+
+ if (!is_digit(*s) || ((n = strtoimax(s, &ep, 10)),
+ (ep == NULL || ep == s || *ep != '\0')))
+ error("Illegal number: '%s'", s);
+ if (n < INT_MIN || n > INT_MAX)
+ error("Number out of range: %s", s);
+ return (int)n;
+}
+
+
+
+/*
+ * Check for a valid number. This should be elsewhere.
+ */
+
+int
+is_number(const char *p)
+{
+ do {
+ if (! is_digit(*p))
+ return 0;
+ } while (*++p != '\0');
+ return 1;
+}
diff --git a/bin/sh/mystring.h b/bin/sh/mystring.h
new file mode 100644
index 0000000..08a73e9
--- /dev/null
+++ b/bin/sh/mystring.h
@@ -0,0 +1,45 @@
+/* $NetBSD: mystring.h,v 1.11 2003/08/07 09:05:35 agc Exp $ */
+
+/*-
+ * Copyright (c) 1991, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Kenneth Almquist.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * @(#)mystring.h 8.2 (Berkeley) 5/4/95
+ */
+
+#include <string.h>
+
+void scopyn(const char *, char *, int);
+int prefix(const char *, const char *);
+int number(const char *);
+int is_number(const char *);
+
+#define equal(s1, s2) (strcmp(s1, s2) == 0)
+#define scopy(s1, s2) ((void)strcpy(s2, s1))
diff --git a/bin/sh/nodes.c.pat b/bin/sh/nodes.c.pat
new file mode 100644
index 0000000..599597c
--- /dev/null
+++ b/bin/sh/nodes.c.pat
@@ -0,0 +1,210 @@
+/* $NetBSD: nodes.c.pat,v 1.14 2018/06/22 11:04:55 kre Exp $ */
+
+/*-
+ * Copyright (c) 1991, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Kenneth Almquist.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * @(#)nodes.c.pat 8.2 (Berkeley) 5/4/95
+ */
+
+#include <stdlib.h>
+#include <stddef.h>
+
+/*
+ * Routine for dealing with parsed shell commands.
+ */
+
+#include "shell.h"
+#include "nodes.h"
+#include "memalloc.h"
+#include "machdep.h"
+#include "mystring.h"
+
+
+/* used to accumulate sizes of nodes */
+struct nodesize {
+ int bsize; /* size of structures in function */
+ int ssize; /* size of strings in node */
+};
+
+/* provides resources for node copies */
+struct nodecopystate {
+ pointer block; /* block to allocate function from */
+ char *string; /* block to allocate strings from */
+};
+
+
+%SIZES
+
+
+
+STATIC void calcsize(union node *, struct nodesize *);
+STATIC void sizenodelist(struct nodelist *, struct nodesize *);
+STATIC union node *copynode(union node *, struct nodecopystate *);
+STATIC struct nodelist *copynodelist(struct nodelist *, struct nodecopystate *);
+STATIC char *nodesavestr(char *, struct nodecopystate *);
+
+struct funcdef {
+ unsigned int refcount;
+ union node n; /* must be last */
+};
+
+
+/*
+ * Make a copy of a parse tree.
+ */
+
+struct funcdef *
+copyfunc(union node *n)
+{
+ struct nodesize sz;
+ struct nodecopystate st;
+ struct funcdef *fn;
+
+ if (n == NULL)
+ return NULL;
+ sz.bsize = offsetof(struct funcdef, n);
+ sz.ssize = 0;
+ calcsize(n, &sz);
+ fn = ckmalloc(sz.bsize + sz.ssize);
+ fn->refcount = 1;
+ st.block = (char *)fn + offsetof(struct funcdef, n);
+ st.string = (char *)fn + sz.bsize;
+ copynode(n, &st);
+ return fn;
+}
+
+union node *
+getfuncnode(struct funcdef *fn)
+{
+ if (fn == NULL)
+ return NULL;
+ return &fn->n;
+}
+
+
+STATIC void
+calcsize(union node *n, struct nodesize *res)
+{
+ %CALCSIZE
+}
+
+
+
+STATIC void
+sizenodelist(struct nodelist *lp, struct nodesize *res)
+{
+ while (lp) {
+ res->bsize += SHELL_ALIGN(sizeof(struct nodelist));
+ calcsize(lp->n, res);
+ lp = lp->next;
+ }
+}
+
+
+
+STATIC union node *
+copynode(union node *n, struct nodecopystate *st)
+{
+ union node *new;
+
+ %COPY
+ return new;
+}
+
+
+STATIC struct nodelist *
+copynodelist(struct nodelist *lp, struct nodecopystate *st)
+{
+ struct nodelist *start;
+ struct nodelist **lpp;
+
+ lpp = &start;
+ while (lp) {
+ *lpp = st->block;
+ st->block = (char *)st->block +
+ SHELL_ALIGN(sizeof(struct nodelist));
+ (*lpp)->n = copynode(lp->n, st);
+ lp = lp->next;
+ lpp = &(*lpp)->next;
+ }
+ *lpp = NULL;
+ return start;
+}
+
+
+
+STATIC char *
+nodesavestr(char *s, struct nodecopystate *st)
+{
+ register char *p = s;
+ register char *q = st->string;
+ char *rtn = st->string;
+
+ while ((*q++ = *p++) != 0)
+ continue;
+ st->string = q;
+ return rtn;
+}
+
+
+
+/*
+ * Handle making a reference to a function, and releasing it.
+ * Free the func code when there are no remaining references.
+ */
+
+void
+reffunc(struct funcdef *fn)
+{
+ if (fn != NULL)
+ fn->refcount++;
+}
+
+void
+unreffunc(struct funcdef *fn)
+{
+ if (fn != NULL) {
+ if (--fn->refcount > 0)
+ return;
+ ckfree(fn);
+ }
+}
+
+/*
+ * this is used when we need to free the func, regardless of refcount
+ * which only happens when re-initing the shell for a SHELLPROC
+ */
+void
+freefunc(struct funcdef *fn)
+{
+ if (fn != NULL)
+ ckfree(fn);
+}
diff --git a/bin/sh/nodetypes b/bin/sh/nodetypes
new file mode 100644
index 0000000..dc5ee4a
--- /dev/null
+++ b/bin/sh/nodetypes
@@ -0,0 +1,149 @@
+# $NetBSD: nodetypes,v 1.18 2017/06/08 13:12:17 kre Exp $
+# Copyright (c) 1991, 1993
+# The Regents of the University of California. All rights reserved.
+#
+# This code is derived from software contributed to Berkeley by
+# Kenneth Almquist.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1. Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+# 3. Neither the name of the University nor the names of its contributors
+# may be used to endorse or promote products derived from this software
+# without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+# ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+# SUCH DAMAGE.
+#
+# @(#)nodetypes 8.2 (Berkeley) 5/4/95
+
+# This file describes the nodes used in parse trees. Unindented lines
+# contain a node type followed by a structure tag. Subsequent indented
+# lines specify the fields of the structure. Several node types can share
+# the same structure, in which case the fields of the structure should be
+# specified only once.
+#
+# A field of a structure is described by the name of the field followed
+# by a type. The currently implemented types are:
+# nodeptr - a pointer to a node
+# nodelist - a pointer to a list of nodes
+# string - a pointer to a nul terminated string
+# int - an integer
+# other - any type that can be copied by assignment
+# temp - a field that doesn't have to be copied when the node is copied
+# The last two types should be followed by the text of a C declaration for
+# the field.
+
+NSEMI nbinary # two commands separated by a semicolon
+ type int
+ ch1 nodeptr # the first child
+ ch2 nodeptr # the second child
+
+NCMD ncmd # a simple command
+ type int
+ backgnd int # set to run command in background
+ args nodeptr # the arguments
+ redirect nodeptr # list of file redirections
+ lineno int
+
+NPIPE npipe # a pipeline
+ type int
+ backgnd int # set to run pipeline in background
+ cmdlist nodelist # the commands in the pipeline
+
+NREDIR nredir # redirection (of a complex command)
+ type int
+ n nodeptr # the command
+ redirect nodeptr # list of file redirections
+
+NBACKGND nredir # run command in background
+NSUBSHELL nredir # run command in a subshell
+
+NAND nbinary # the && operator
+NOR nbinary # the || operator
+
+NIF nif # the if statement. Elif clauses are handled
+ type int # using multiple if nodes.
+ test nodeptr # if test
+ ifpart nodeptr # then ifpart
+ elsepart nodeptr # else elsepart
+
+NWHILE nbinary # the while statement. First child is the test
+NUNTIL nbinary # the until statement
+
+NFOR nfor # the for statement
+ type int
+ args nodeptr # for var in args
+ body nodeptr # do body; done
+ var string # the for variable
+
+NCASE ncase # a case statement
+ type int
+ expr nodeptr # the word to switch on
+ cases nodeptr # the list of cases (NCLIST nodes)
+ lineno int
+
+NCLISTCONT nclist # a case terminated by ';&' (fall through)
+NCLIST nclist # a case
+ type int
+ next nodeptr # the next case in list
+ pattern nodeptr # list of patterns for this case
+ body nodeptr # code to execute for this case
+ lineno int
+
+
+NDEFUN narg # define a function. The "next" field contains
+ # the body of the function.
+
+NARG narg # represents a word
+ type int
+ next nodeptr # next word in list
+ text string # the text of the word
+ backquote nodelist # list of commands in back quotes
+ lineno int
+
+NTO nfile # fd> fname
+NCLOBBER nfile # fd>| fname
+NFROM nfile # fd< fname
+NFROMTO nfile # fd<> fname
+NAPPEND nfile # fd>> fname
+ type int
+ next nodeptr # next redirection in list
+ fd int # file descriptor being redirected
+ fname nodeptr # file name, in a NARG node
+ expfname temp char *expfname # actual file name
+
+NTOFD ndup # fd<&dupfd
+NFROMFD ndup # fd>&dupfd
+ type int
+ next nodeptr # next redirection in list
+ fd int # file descriptor being redirected
+ dupfd int # file descriptor to duplicate
+ vname nodeptr # file name if fd>&$var
+
+
+NHERE nhere # fd<<\!
+NXHERE nhere # fd<<!
+ type int
+ next nodeptr # next redirection in list
+ fd int # file descriptor being redirected
+ doc nodeptr # input to command (NARG node)
+
+NNOT nnot # ! command (actually pipeline)
+NDNOT nnot # ! ! pipeline (optimisation)
+ type int
+ com nodeptr
diff --git a/bin/sh/option.list b/bin/sh/option.list
new file mode 100644
index 0000000..3be683a
--- /dev/null
+++ b/bin/sh/option.list
@@ -0,0 +1,79 @@
+/* $NetBSD: option.list,v 1.9 2018/11/23 20:40:06 kre Exp $ */
+
+/*
+ * define the shell's settable options
+ *
+ * new options can be defined by adding them here,
+ * but they do nothing until code to implement them
+ * is added (using the "var name" field)
+ */
+
+/*
+ * format is up to 5 columns... (followed by anything)
+ * end of line comments can be introduced by ' #' (space/tab hash) to eol.
+ *
+ * The columns are:
+ * 1. internal shell "var name" (required)
+ * 2. option long name
+ * if a single char, then no long name, and remaining
+ * columns shift left (this becomes the short name)
+ * 3. option short name (single character name)
+ * if '-' or absent then no short name
+ * if neither long nor short name, line is ignored
+ * 4. option set short name (name of option equiv class)
+ * if '-' or absent then no class
+ * 5. default value of option
+ * if absent, default is 0
+ * only 0 or 1 possible (0==off 1==on) ("on" and "off" can be used)
+ *
+ * Data may be followed by any C preprocessor #if expression (incl the #if..)
+ * (including #ifdef #ifndef) to conditionalise output for that option.
+ * The #if expression continues until \n or next following '#'
+ */
+
+// the POSIX defined options
+aflag allexport a # export all variables
+eflag errexit e # exit on command error ($? != 0)
+mflag monitor m # enable job control
+Cflag noclobber C # do not overwrite files when using >
+nflag noexec n # do not execue commands
+fflag noglob f # no pathname expansion
+uflag nounset u # expanding unset var is an error
+vflag verbose v # echo commands as read
+xflag xtrace x # trace command execution
+
+// the long name (ignoreeof) is standard, the I flag is not
+Iflag ignoreeof I # do not exit interactive shell on EOF
+
+// defined but not really implemented by the shell (yet) - they do nothing
+bflag notify b # [U] report bg job completion
+nolog nolog # [U] no func definitions in history
+// 'h' is standard, long name (trackall) is not
+hflag trackall h # [U] locate cmds in funcs during defn
+
+// 's' is standard for command line, not as 'set' option, nor 'stdin' name
+sflag stdin s # read from standard input
+// minusc c # command line option only.
+// -- o # handled differently...
+
+// non-standard options -- 'i' is just a state, not an option in standard.
+iflag interactive i # interactive shell
+cdprint cdprint # always print result of a cd
+usefork fork F # use fork(2) instead of vfork(2)
+pflag nopriv p # preserve privs if set[ug]id
+posix posix # be closer to POSIX compat
+qflag quietprofile q # disable -v/-x in startup files
+fnline1 local_lineno L on # number lines in funcs starting at 1
+promptcmds promptcmds # allow $( ) in PS1 (et al).
+pipefail pipefail # pipe exit status
+Xflag xlock X #ifndef SMALL # sticky stderr for -x (implies -x)
+
+// editline/history related options ("vi" is standard, 'V' and others are not)
+// only one of vi/emacs can be set, hence the "set" definition, value
+// of that can be any char (not used for a different set)
+Vflag vi V V # enable vi style editing
+Eflag emacs E V # enable emacs style editing
+tabcomplete tabcomplete # make <tab> cause filename expansion
+
+// internal debug option (not usually included in the shell)
+debug debug #ifdef DEBUG # enable internal shell debugging
diff --git a/bin/sh/options.c b/bin/sh/options.c
new file mode 100644
index 0000000..d2c3e68
--- /dev/null
+++ b/bin/sh/options.c
@@ -0,0 +1,631 @@
+/* $NetBSD: options.c,v 1.53 2018/07/13 22:43:44 kre Exp $ */
+
+/*-
+ * Copyright (c) 1991, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Kenneth Almquist.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+#ifndef lint
+#if 0
+static char sccsid[] = "@(#)options.c 8.2 (Berkeley) 5/4/95";
+#else
+__RCSID("$NetBSD: options.c,v 1.53 2018/07/13 22:43:44 kre Exp $");
+#endif
+#endif /* not lint */
+
+#include <signal.h>
+#include <unistd.h>
+#include <stdlib.h>
+
+#include "shell.h"
+#define DEFINE_OPTIONS
+#include "options.h"
+#undef DEFINE_OPTIONS
+#include "builtins.h"
+#include "nodes.h" /* for other header files */
+#include "eval.h"
+#include "jobs.h"
+#include "input.h"
+#include "output.h"
+#include "trap.h"
+#include "var.h"
+#include "memalloc.h"
+#include "error.h"
+#include "mystring.h"
+#include "syntax.h"
+#ifndef SMALL
+#include "myhistedit.h"
+#endif
+#include "show.h"
+
+char *arg0; /* value of $0 */
+struct shparam shellparam; /* current positional parameters */
+char **argptr; /* argument list for builtin commands */
+char *optionarg; /* set by nextopt (like getopt) */
+char *optptr; /* used by nextopt */
+
+char *minusc; /* argument to -c option */
+
+
+STATIC void options(int);
+STATIC void minus_o(char *, int);
+STATIC void setoption(int, int);
+STATIC int getopts(char *, char *, char **, char ***, char **);
+
+
+/*
+ * Process the shell command line arguments.
+ */
+
+void
+procargs(int argc, char **argv)
+{
+ size_t i;
+ int psx;
+
+ argptr = argv;
+ if (argc > 0)
+ argptr++;
+
+ psx = posix; /* save what we set it to earlier */
+ /*
+ * option values are mostly boolean 0:off 1:on
+ * we use 2 (just in this routine) to mean "unknown yet"
+ */
+ for (i = 0; i < NOPTS; i++)
+ optlist[i].val = 2;
+ posix = psx; /* restore before processing -o ... */
+
+ options(1);
+
+ if (*argptr == NULL && minusc == NULL)
+ sflag = 1;
+ if (iflag == 2 && sflag == 1 && isatty(0) && isatty(2))
+ iflag = 1;
+ if (iflag == 1 && sflag == 2)
+ iflag = 2;
+ if (mflag == 2)
+ mflag = iflag;
+#ifndef DO_SHAREDVFORK
+ if (usefork == 2)
+ usefork = 1;
+#endif
+#if DEBUG >= 2
+ if (debug == 2)
+ debug = 1;
+#endif
+ /*
+ * Any options not dealt with as special cases just above,
+ * and which were not set on the command line, are set to
+ * their expected default values (mostly "off")
+ *
+ * then as each option is initialised, save its setting now
+ * as its "default" value for future use ("set -o default").
+ */
+ for (i = 0; i < NOPTS; i++) {
+ if (optlist[i].val == 2)
+ optlist[i].val = optlist[i].dflt;
+ optlist[i].dflt = optlist[i].val;
+ }
+
+ arg0 = argv[0];
+ if (sflag == 0 && minusc == NULL) {
+ commandname = argv[0];
+ arg0 = *argptr++;
+ setinputfile(arg0, 0);
+ commandname = arg0;
+ }
+ /* POSIX 1003.2: first arg after -c cmd is $0, remainder $1... */
+ if (minusc != NULL) {
+ if (argptr == NULL || *argptr == NULL)
+ error("Bad -c option");
+ minusc = *argptr++;
+ if (*argptr != 0)
+ arg0 = *argptr++;
+ }
+
+ shellparam.p = argptr;
+ shellparam.reset = 1;
+ /* assert(shellparam.malloc == 0 && shellparam.nparam == 0); */
+ while (*argptr) {
+ shellparam.nparam++;
+ argptr++;
+ }
+ optschanged();
+}
+
+
+void
+optschanged(void)
+{
+ setinteractive(iflag);
+#ifndef SMALL
+ histedit();
+#endif
+ setjobctl(mflag);
+}
+
+/*
+ * Process shell options. The global variable argptr contains a pointer
+ * to the argument list; we advance it past the options.
+ */
+
+STATIC void
+options(int cmdline)
+{
+ static char empty[] = "";
+ char *p;
+ int val;
+ int c;
+
+ if (cmdline)
+ minusc = NULL;
+ while ((p = *argptr) != NULL) {
+ argptr++;
+ if ((c = *p++) == '-') {
+ val = 1;
+ if (p[0] == '\0' || (p[0] == '-' && p[1] == '\0')) {
+ if (!cmdline) {
+ /* "-" means turn off -x and -v */
+ if (p[0] == '\0')
+ xflag = vflag = 0;
+ /* "--" means reset params */
+ else if (*argptr == NULL)
+ setparam(argptr);
+ }
+ break; /* "-" or "--" terminates options */
+ }
+ } else if (c == '+') {
+ val = 0;
+ } else {
+ argptr--;
+ break;
+ }
+ while ((c = *p++) != '\0') {
+ if (val == 1 && c == 'c' && cmdline) {
+ /* command is after shell args*/
+ minusc = empty;
+ } else if (c == 'o') {
+ if (*p != '\0')
+ minus_o(p, val + (cmdline ? val : 0));
+ else if (*argptr)
+ minus_o(*argptr++,
+ val + (cmdline ? val : 0));
+ else if (!cmdline)
+ minus_o(NULL, val);
+ else
+ error("arg for %co missing", "+-"[val]);
+ break;
+#ifdef DEBUG
+ } else if (c == 'D') {
+ if (*p) {
+ set_debug(p, val);
+ break;
+ } else if (*argptr)
+ set_debug(*argptr++, val);
+ else
+ set_debug("*$", val);
+#endif
+ } else {
+ setoption(c, val);
+ }
+ }
+ }
+}
+
+static void
+set_opt_val(size_t i, int val)
+{
+ size_t j;
+ int flag;
+
+ if (val && (flag = optlist[i].opt_set)) {
+ /* some options (eg vi/emacs) are mutually exclusive */
+ for (j = 0; j < NOPTS; j++)
+ if (optlist[j].opt_set == flag)
+ optlist[j].val = 0;
+ }
+#ifndef SMALL
+ if (i == _SH_OPT_Xflag)
+ xtracefdsetup(val);
+#endif
+ optlist[i].val = val;
+#ifdef DEBUG
+ if (&optlist[i].val == &debug)
+ opentrace(); /* different "trace" than the -x one... */
+#endif
+}
+
+STATIC void
+minus_o(char *name, int val)
+{
+ size_t i;
+ const char *sep = ": ";
+
+ if (name == NULL) {
+ if (val) {
+ out1str("Current option settings");
+ for (i = 0; i < NOPTS; i++) {
+ if (optlist[i].name == NULL) {
+ out1fmt("%s%c%c", sep,
+ "+-"[optlist[i].val],
+ optlist[i].letter);
+ sep = ", ";
+ }
+ }
+ out1c('\n');
+ for (i = 0; i < NOPTS; i++) {
+ if (optlist[i].name)
+ out1fmt("%-19s %s\n", optlist[i].name,
+ optlist[i].val ? "on" : "off");
+ }
+ } else {
+ out1str("set -o default");
+ for (i = 0; i < NOPTS; i++) {
+ if (optlist[i].val == optlist[i].dflt)
+ continue;
+ if (optlist[i].name)
+ out1fmt(" %co %s",
+ "+-"[optlist[i].val], optlist[i].name);
+ else
+ out1fmt(" %c%c", "+-"[optlist[i].val],
+ optlist[i].letter);
+ }
+ out1c('\n');
+ }
+ } else {
+ if (val == 1 && equal(name, "default")) { /* special case */
+ for (i = 0; i < NOPTS; i++)
+ set_opt_val(i, optlist[i].dflt);
+ return;
+ }
+ if (val)
+ val = 1;
+ for (i = 0; i < NOPTS; i++)
+ if (optlist[i].name && equal(name, optlist[i].name)) {
+ set_opt_val(i, val);
+#ifndef SMALL
+ if (i == _SH_OPT_Xflag)
+ set_opt_val(_SH_OPT_xflag, val);
+#endif
+ return;
+ }
+ error("Illegal option %co %s", "+-"[val], name);
+ }
+}
+
+
+STATIC void
+setoption(int flag, int val)
+{
+ size_t i;
+
+ for (i = 0; i < NOPTS; i++)
+ if (optlist[i].letter == flag) {
+ set_opt_val(i, val);
+#ifndef SMALL
+ if (i == _SH_OPT_Xflag)
+ set_opt_val(_SH_OPT_xflag, val);
+#endif
+ return;
+ }
+ error("Illegal option %c%c", "+-"[val], flag);
+ /* NOTREACHED */
+}
+
+
+
+#ifdef mkinit
+INCLUDE "options.h"
+
+SHELLPROC {
+ int i;
+
+ for (i = 0; optlist[i].name; i++)
+ optlist[i].val = 0;
+ optschanged();
+
+}
+#endif
+
+
+/*
+ * Set the shell parameters.
+ */
+
+void
+setparam(char **argv)
+{
+ char **newparam;
+ char **ap;
+ int nparam;
+
+ for (nparam = 0 ; argv[nparam] ; nparam++)
+ continue;
+ ap = newparam = ckmalloc((nparam + 1) * sizeof *ap);
+ while (*argv) {
+ *ap++ = savestr(*argv++);
+ }
+ *ap = NULL;
+ freeparam(&shellparam);
+ shellparam.malloc = 1;
+ shellparam.nparam = nparam;
+ shellparam.p = newparam;
+ shellparam.optnext = NULL;
+}
+
+
+/*
+ * Free the list of positional parameters.
+ */
+
+void
+freeparam(volatile struct shparam *param)
+{
+ char **ap;
+
+ if (param->malloc) {
+ for (ap = param->p ; *ap ; ap++)
+ ckfree(*ap);
+ ckfree(param->p);
+ }
+}
+
+
+
+/*
+ * The shift builtin command.
+ */
+
+int
+shiftcmd(int argc, char **argv)
+{
+ int n;
+ char **ap1, **ap2;
+
+ if (argc > 2)
+ error("Usage: shift [n]");
+ n = 1;
+ if (argc > 1)
+ n = number(argv[1]);
+ if (n > shellparam.nparam)
+ error("can't shift that many");
+ INTOFF;
+ shellparam.nparam -= n;
+ for (ap1 = shellparam.p ; --n >= 0 ; ap1++) {
+ if (shellparam.malloc)
+ ckfree(*ap1);
+ }
+ ap2 = shellparam.p;
+ while ((*ap2++ = *ap1++) != NULL)
+ continue;
+ shellparam.optnext = NULL;
+ INTON;
+ return 0;
+}
+
+
+
+/*
+ * The set command builtin.
+ */
+
+int
+setcmd(int argc, char **argv)
+{
+ if (argc == 1)
+ return showvars(0, 0, 1, 0);
+ INTOFF;
+ options(0);
+ optschanged();
+ if (*argptr != NULL) {
+ setparam(argptr);
+ }
+ INTON;
+ return 0;
+}
+
+
+void
+getoptsreset(const char *value)
+{
+ /*
+ * This is just to detect the case where OPTIND=1
+ * is executed. Any other string assigned to OPTIND
+ * is OK, but is not a reset. No errors, so cannot use number()
+ */
+ if (is_digit(*value) && strtol(value, NULL, 10) == 1) {
+ shellparam.optnext = NULL;
+ shellparam.reset = 1;
+ }
+}
+
+/*
+ * The getopts builtin. Shellparam.optnext points to the next argument
+ * to be processed. Shellparam.optptr points to the next character to
+ * be processed in the current argument. If shellparam.optnext is NULL,
+ * then it's the first time getopts has been called.
+ */
+
+int
+getoptscmd(int argc, char **argv)
+{
+ char **optbase;
+
+ if (argc < 3)
+ error("usage: getopts optstring var [arg]");
+ else if (argc == 3)
+ optbase = shellparam.p;
+ else
+ optbase = &argv[3];
+
+ if (shellparam.reset == 1) {
+ shellparam.optnext = optbase;
+ shellparam.optptr = NULL;
+ shellparam.reset = 0;
+ }
+
+ return getopts(argv[1], argv[2], optbase, &shellparam.optnext,
+ &shellparam.optptr);
+}
+
+STATIC int
+getopts(char *optstr, char *optvar, char **optfirst, char ***optnext, char **optpptr)
+{
+ char *p, *q;
+ char c = '?';
+ int done = 0;
+ int ind = 0;
+ int err = 0;
+ char s[12];
+
+ if ((p = *optpptr) == NULL || *p == '\0') {
+ /* Current word is done, advance */
+ if (*optnext == NULL)
+ return 1;
+ p = **optnext;
+ if (p == NULL || *p != '-' || *++p == '\0') {
+atend:
+ ind = *optnext - optfirst + 1;
+ *optnext = NULL;
+ p = NULL;
+ done = 1;
+ goto out;
+ }
+ (*optnext)++;
+ if (p[0] == '-' && p[1] == '\0') /* check for "--" */
+ goto atend;
+ }
+
+ c = *p++;
+ for (q = optstr; *q != c; ) {
+ if (*q == '\0') {
+ if (optstr[0] == ':') {
+ s[0] = c;
+ s[1] = '\0';
+ err |= setvarsafe("OPTARG", s, 0);
+ } else {
+ outfmt(&errout, "Illegal option -%c\n", c);
+ (void) unsetvar("OPTARG", 0);
+ }
+ c = '?';
+ goto bad;
+ }
+ if (*++q == ':')
+ q++;
+ }
+
+ if (*++q == ':') {
+ if (*p == '\0' && (p = **optnext) == NULL) {
+ if (optstr[0] == ':') {
+ s[0] = c;
+ s[1] = '\0';
+ err |= setvarsafe("OPTARG", s, 0);
+ c = ':';
+ } else {
+ outfmt(&errout, "No arg for -%c option\n", c);
+ (void) unsetvar("OPTARG", 0);
+ c = '?';
+ }
+ goto bad;
+ }
+
+ if (p == **optnext)
+ (*optnext)++;
+ err |= setvarsafe("OPTARG", p, 0);
+ p = NULL;
+ } else
+ err |= setvarsafe("OPTARG", "", 0);
+ ind = *optnext - optfirst + 1;
+ goto out;
+
+bad:
+ ind = 1;
+ *optnext = NULL;
+ p = NULL;
+out:
+ *optpptr = p;
+ fmtstr(s, sizeof(s), "%d", ind);
+ err |= setvarsafe("OPTIND", s, VNOFUNC);
+ s[0] = c;
+ s[1] = '\0';
+ err |= setvarsafe(optvar, s, 0);
+ if (err) {
+ *optnext = NULL;
+ *optpptr = NULL;
+ flushall();
+ exraise(EXERROR);
+ }
+ return done;
+}
+
+/*
+ * XXX - should get rid of. have all builtins use getopt(3). the
+ * library getopt must have the BSD extension static variable "optreset"
+ * otherwise it can't be used within the shell safely.
+ *
+ * Standard option processing (a la getopt) for builtin routines. The
+ * only argument that is passed to nextopt is the option string; the
+ * other arguments are unnecessary. It return the character, or '\0' on
+ * end of input.
+ */
+
+int
+nextopt(const char *optstring)
+{
+ char *p;
+ const char *q;
+ char c;
+
+ if ((p = optptr) == NULL || *p == '\0') {
+ p = *argptr;
+ if (p == NULL || *p != '-' || *++p == '\0')
+ return '\0';
+ argptr++;
+ if (p[0] == '-' && p[1] == '\0') /* check for "--" */
+ return '\0';
+ }
+ c = *p++;
+ for (q = optstring ; *q != c ; ) {
+ if (*q == '\0')
+ error("Illegal option -%c", c);
+ if (*++q == ':')
+ q++;
+ }
+ if (*++q == ':') {
+ if (*p == '\0' && (p = *argptr++) == NULL)
+ error("No arg for -%c option", c);
+ optionarg = p;
+ p = NULL;
+ }
+ optptr = p;
+ return c;
+}
diff --git a/bin/sh/options.h b/bin/sh/options.h
new file mode 100644
index 0000000..4857d18
--- /dev/null
+++ b/bin/sh/options.h
@@ -0,0 +1,72 @@
+/* $NetBSD: options.h,v 1.27 2017/05/28 00:38:01 kre Exp $ */
+
+/*-
+ * Copyright (c) 1991, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Kenneth Almquist.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * @(#)options.h 8.2 (Berkeley) 5/4/95
+ */
+
+struct shparam {
+ int nparam; /* # of positional parameters (without $0) */
+ unsigned char malloc; /* if parameter list dynamically allocated */
+ unsigned char reset; /* if getopts has been reset */
+ char **p; /* parameter list */
+ char **optnext; /* next parameter to be processed by getopts */
+ char *optptr; /* used by getopts */
+};
+
+/*
+ * Note that option default values can be changed at shell startup
+ * depending upon the environment in which the shell is running.
+ */
+struct optent {
+ const char *name; /* for set -o <name> */
+ const char letter; /* set [+/-]<letter> and $- */
+ const char opt_set; /* mutually exclusive option set */
+ unsigned char val; /* value of <letter>flag */
+ unsigned char dflt; /* default value of flag */
+};
+
+#include "optinit.h"
+
+extern char *minusc; /* argument to -c option */
+extern char *arg0; /* $0 */
+extern struct shparam shellparam; /* $@ */
+extern char **argptr; /* argument list for builtin commands */
+extern char *optionarg; /* set by nextopt */
+extern char *optptr; /* used by nextopt */
+
+void procargs(int, char **);
+void optschanged(void);
+void setparam(char **);
+void freeparam(volatile struct shparam *);
+int nextopt(const char *);
+void getoptsreset(const char *);
diff --git a/bin/sh/output.c b/bin/sh/output.c
new file mode 100644
index 0000000..99bd913
--- /dev/null
+++ b/bin/sh/output.c
@@ -0,0 +1,755 @@
+/* $NetBSD: output.c,v 1.40 2017/11/21 03:42:39 kre Exp $ */
+
+/*-
+ * Copyright (c) 1991, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Kenneth Almquist.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+#ifndef lint
+#if 0
+static char sccsid[] = "@(#)output.c 8.2 (Berkeley) 5/4/95";
+#else
+__RCSID("$NetBSD: output.c,v 1.40 2017/11/21 03:42:39 kre Exp $");
+#endif
+#endif /* not lint */
+
+/*
+ * Shell output routines. We use our own output routines because:
+ * When a builtin command is interrupted we have to discard
+ * any pending output.
+ * When a builtin command appears in back quotes, we want to
+ * save the output of the command in a region obtained
+ * via malloc, rather than doing a fork and reading the
+ * output of the command via a pipe.
+ * Our output routines may be smaller than the stdio routines.
+ */
+
+#include <sys/types.h> /* quad_t */
+#include <sys/param.h> /* BSD4_4 */
+#include <sys/ioctl.h>
+
+#include <stdio.h> /* defines BUFSIZ */
+#include <string.h>
+#include <errno.h>
+#include <unistd.h>
+#include <stdlib.h>
+
+#include "shell.h"
+#include "syntax.h"
+#include "output.h"
+#include "memalloc.h"
+#include "error.h"
+#include "redir.h"
+#include "options.h"
+#include "show.h"
+
+
+#define OUTBUFSIZ BUFSIZ
+#define BLOCK_OUT -2 /* output to a fixed block of memory */
+#define MEM_OUT -3 /* output to dynamically allocated memory */
+
+#ifdef SMALL
+#define CHAIN
+#else
+#define CHAIN ,NULL
+#endif
+
+
+ /* nextc nleft bufsize buf fd flags chain */
+struct output output = {NULL, 0, OUTBUFSIZ, NULL, 1, 0 CHAIN };
+struct output errout = {NULL, 0, 100, NULL, 2, 0 CHAIN };
+struct output memout = {NULL, 0, 0, NULL, MEM_OUT, 0 CHAIN };
+struct output *out1 = &output;
+struct output *out2 = &errout;
+#ifndef SMALL
+struct output *outx = &errout;
+struct output *outxtop = NULL;
+#endif
+
+
+#ifdef mkinit
+
+INCLUDE "output.h"
+INCLUDE "memalloc.h"
+
+RESET {
+ out1 = &output;
+ out2 = &errout;
+ if (memout.buf != NULL) {
+ ckfree(memout.buf);
+ memout.buf = NULL;
+ }
+}
+
+#endif
+
+
+#ifdef notdef /* no longer used */
+/*
+ * Set up an output file to write to memory rather than a file.
+ */
+
+void
+open_mem(char *block, int length, struct output *file)
+{
+ file->nextc = block;
+ file->nleft = --length;
+ file->fd = BLOCK_OUT;
+ file->flags = 0;
+}
+#endif
+
+
+void
+out1str(const char *p)
+{
+ outstr(p, out1);
+}
+
+
+void
+out2str(const char *p)
+{
+ outstr(p, out2);
+}
+
+#ifndef SMALL
+void
+outxstr(const char *p)
+{
+ outstr(p, outx);
+}
+#endif
+
+
+void
+outstr(const char *p, struct output *file)
+{
+ char c = 0;
+
+ while (*p)
+ outc((c = *p++), file);
+ if (file == out2 || (file == outx && c == '\n'))
+ flushout(file);
+}
+
+
+void
+out2shstr(const char *p)
+{
+ outshstr(p, out2);
+}
+
+#ifndef SMALL
+void
+outxshstr(const char *p)
+{
+ outshstr(p, outx);
+}
+#endif
+
+/*
+ * ' is in this list, not because it does not require quoting
+ * (which applies to all the others) but because '' quoting cannot
+ * be used to quote it.
+ */
+static const char norm_chars [] = \
+ "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789/+-=_,.'";
+
+static int
+inquote(const char *p)
+{
+ size_t l = strspn(p, norm_chars);
+ char *s = strchr(p, '\'');
+
+ return s == NULL ? p[l] != '\0' : s - p > (off_t)l;
+}
+
+void
+outshstr(const char *p, struct output *file)
+{
+ int need_q;
+ int inq;
+ char c;
+
+ if (strchr(p, '\'') != NULL && p[1] != '\0') {
+ /*
+ * string contains ' in it, and is not only the '
+ * see if " quoting will work
+ */
+ size_t i = strcspn(p, "\\\"$`");
+
+ if (p[i] == '\0') {
+ /*
+ * string contains no $ ` \ or " chars, perfect...
+ *
+ * We know it contains ' so needs quoting, so
+ * this is easy...
+ */
+ outc('"', file);
+ outstr(p, file);
+ outc('"', file);
+ return;
+ }
+ }
+
+ need_q = p[0] == 0 || p[strspn(p, norm_chars)] != 0;
+
+ /*
+ * Don't emit ' unless something needs quoting before closing '
+ */
+ if (need_q && (p[0] == 0 || inquote(p) != 0)) {
+ outc('\'', file);
+ inq = 1;
+ } else
+ inq = 0;
+
+ while ((c = *p++) != '\0') {
+ if (c != '\'') {
+ outc(c, file);
+ continue;
+ }
+
+ if (inq)
+ outc('\'', file); /* inq = 0, implicit */
+ outc('\\', file);
+ outc(c, file);
+ if (need_q && *p != '\0') {
+ if ((inq = inquote(p)) != 0)
+ outc('\'', file);
+ } else
+ inq = 0;
+ }
+
+ if (inq)
+ outc('\'', file);
+
+ if (file == out2)
+ flushout(file);
+}
+
+
+char out_junk[16];
+
+
+void
+emptyoutbuf(struct output *dest)
+{
+ int offset;
+
+ if (dest->fd == BLOCK_OUT) {
+ dest->nextc = out_junk;
+ dest->nleft = sizeof out_junk;
+ dest->flags |= OUTPUT_ERR;
+ } else if (dest->buf == NULL) {
+ INTOFF;
+ dest->buf = ckmalloc(dest->bufsize);
+ dest->nextc = dest->buf;
+ dest->nleft = dest->bufsize;
+ INTON;
+ VTRACE(DBG_OUTPUT, ("emptyoutbuf now %d @%p for fd %d\n",
+ dest->nleft, dest->buf, dest->fd));
+ } else if (dest->fd == MEM_OUT) {
+ offset = dest->bufsize;
+ INTOFF;
+ dest->bufsize <<= 1;
+ dest->buf = ckrealloc(dest->buf, dest->bufsize);
+ dest->nleft = dest->bufsize - offset;
+ dest->nextc = dest->buf + offset;
+ INTON;
+ } else {
+ flushout(dest);
+ }
+ dest->nleft--;
+}
+
+
+void
+flushall(void)
+{
+ flushout(&output);
+ flushout(&errout);
+}
+
+
+void
+flushout(struct output *dest)
+{
+
+ if (dest->buf == NULL || dest->nextc == dest->buf || dest->fd < 0)
+ return;
+ VTRACE(DBG_OUTPUT, ("flushout fd=%d %zd to write\n", dest->fd,
+ (size_t)(dest->nextc - dest->buf)));
+ if (xwrite(dest->fd, dest->buf, dest->nextc - dest->buf) < 0)
+ dest->flags |= OUTPUT_ERR;
+ dest->nextc = dest->buf;
+ dest->nleft = dest->bufsize;
+}
+
+
+void
+freestdout(void)
+{
+ INTOFF;
+ if (output.buf) {
+ ckfree(output.buf);
+ output.buf = NULL;
+ output.nleft = 0;
+ }
+ INTON;
+}
+
+
+void
+outfmt(struct output *file, const char *fmt, ...)
+{
+ va_list ap;
+
+ va_start(ap, fmt);
+ doformat(file, fmt, ap);
+ va_end(ap);
+}
+
+
+void
+out1fmt(const char *fmt, ...)
+{
+ va_list ap;
+
+ va_start(ap, fmt);
+ doformat(out1, fmt, ap);
+ va_end(ap);
+}
+
+#ifdef DEBUG
+void
+debugprintf(const char *fmt, ...)
+{
+ va_list ap;
+
+ va_start(ap, fmt);
+ doformat(out2, fmt, ap);
+ va_end(ap);
+ flushout(out2);
+}
+#endif
+
+void
+fmtstr(char *outbuf, size_t length, const char *fmt, ...)
+{
+ va_list ap;
+ struct output strout;
+
+ va_start(ap, fmt);
+ strout.nextc = outbuf;
+ strout.nleft = length;
+ strout.fd = BLOCK_OUT;
+ strout.flags = 0;
+ doformat(&strout, fmt, ap);
+ outc('\0', &strout);
+ if (strout.flags & OUTPUT_ERR)
+ outbuf[length - 1] = '\0';
+ va_end(ap);
+}
+
+/*
+ * Formatted output. This routine handles a subset of the printf formats:
+ * - Formats supported: d, u, o, p, X, s, and c.
+ * - The x format is also accepted but is treated like X.
+ * - The l, ll and q modifiers are accepted.
+ * - The - and # flags are accepted; # only works with the o format.
+ * - Width and precision may be specified with any format except c.
+ * - An * may be given for the width or precision.
+ * - The obsolete practice of preceding the width with a zero to get
+ * zero padding is not supported; use the precision field.
+ * - A % may be printed by writing %% in the format string.
+ */
+
+#define TEMPSIZE 24
+
+#ifdef BSD4_4
+#define HAVE_VASPRINTF 1
+#endif
+
+void
+doformat(struct output *dest, const char *f, va_list ap)
+{
+#if HAVE_VASPRINTF
+ char *s;
+
+ vasprintf(&s, f, ap);
+ if (s == NULL)
+ error("Could not allocate formatted output buffer");
+ outstr(s, dest);
+ free(s);
+#else /* !HAVE_VASPRINTF */
+ static const char digit[] = "0123456789ABCDEF";
+ char c;
+ char temp[TEMPSIZE];
+ int flushleft;
+ int sharp;
+ int width;
+ int prec;
+ int islong;
+ int isquad;
+ char *p;
+ int sign;
+#ifdef BSD4_4
+ quad_t l;
+ u_quad_t num;
+#else
+ long l;
+ u_long num;
+#endif
+ unsigned base;
+ int len;
+ int size;
+ int pad;
+
+ while ((c = *f++) != '\0') {
+ if (c != '%') {
+ outc(c, dest);
+ continue;
+ }
+ flushleft = 0;
+ sharp = 0;
+ width = 0;
+ prec = -1;
+ islong = 0;
+ isquad = 0;
+ for (;;) {
+ if (*f == '-')
+ flushleft++;
+ else if (*f == '#')
+ sharp++;
+ else
+ break;
+ f++;
+ }
+ if (*f == '*') {
+ width = va_arg(ap, int);
+ f++;
+ } else {
+ while (is_digit(*f)) {
+ width = 10 * width + digit_val(*f++);
+ }
+ }
+ if (*f == '.') {
+ if (*++f == '*') {
+ prec = va_arg(ap, int);
+ f++;
+ } else {
+ prec = 0;
+ while (is_digit(*f)) {
+ prec = 10 * prec + digit_val(*f++);
+ }
+ }
+ }
+ if (*f == 'l') {
+ f++;
+ if (*f == 'l') {
+ isquad++;
+ f++;
+ } else
+ islong++;
+ } else if (*f == 'q') {
+ isquad++;
+ f++;
+ }
+ switch (*f) {
+ case 'd':
+#ifdef BSD4_4
+ if (isquad)
+ l = va_arg(ap, quad_t);
+ else
+#endif
+ if (islong)
+ l = va_arg(ap, long);
+ else
+ l = va_arg(ap, int);
+ sign = 0;
+ num = l;
+ if (l < 0) {
+ num = -l;
+ sign = 1;
+ }
+ base = 10;
+ goto number;
+ case 'u':
+ base = 10;
+ goto uns_number;
+ case 'o':
+ base = 8;
+ goto uns_number;
+ case 'p':
+ outc('0', dest);
+ outc('x', dest);
+ /*FALLTHROUGH*/
+ case 'x':
+ /* we don't implement 'x'; treat like 'X' */
+ case 'X':
+ base = 16;
+uns_number: /* an unsigned number */
+ sign = 0;
+#ifdef BSD4_4
+ if (isquad)
+ num = va_arg(ap, u_quad_t);
+ else
+#endif
+ if (islong)
+ num = va_arg(ap, unsigned long);
+ else
+ num = va_arg(ap, unsigned int);
+number: /* process a number */
+ p = temp + TEMPSIZE - 1;
+ *p = '\0';
+ while (num) {
+ *--p = digit[num % base];
+ num /= base;
+ }
+ len = (temp + TEMPSIZE - 1) - p;
+ if (prec < 0)
+ prec = 1;
+ if (sharp && *f == 'o' && prec <= len)
+ prec = len + 1;
+ pad = 0;
+ if (width) {
+ size = len;
+ if (size < prec)
+ size = prec;
+ size += sign;
+ pad = width - size;
+ if (flushleft == 0) {
+ while (--pad >= 0)
+ outc(' ', dest);
+ }
+ }
+ if (sign)
+ outc('-', dest);
+ prec -= len;
+ while (--prec >= 0)
+ outc('0', dest);
+ while (*p)
+ outc(*p++, dest);
+ while (--pad >= 0)
+ outc(' ', dest);
+ break;
+ case 's':
+ p = va_arg(ap, char *);
+ pad = 0;
+ if (width) {
+ len = strlen(p);
+ if (prec >= 0 && len > prec)
+ len = prec;
+ pad = width - len;
+ if (flushleft == 0) {
+ while (--pad >= 0)
+ outc(' ', dest);
+ }
+ }
+ prec++;
+ while (--prec != 0 && *p)
+ outc(*p++, dest);
+ while (--pad >= 0)
+ outc(' ', dest);
+ break;
+ case 'c':
+ c = va_arg(ap, int);
+ outc(c, dest);
+ break;
+ default:
+ outc(*f, dest);
+ break;
+ }
+ f++;
+ }
+#endif /* !HAVE_VASPRINTF */
+}
+
+
+
+/*
+ * Version of write which resumes after a signal is caught.
+ */
+
+int
+xwrite(int fd, char *buf, int nbytes)
+{
+ int ntry;
+ int i;
+ int n;
+
+ n = nbytes;
+ ntry = 0;
+ while (n > 0) {
+ i = write(fd, buf, n);
+ if (i > 0) {
+ if ((n -= i) <= 0)
+ return nbytes;
+ buf += i;
+ ntry = 0;
+ } else if (i == 0) {
+ if (++ntry > 10)
+ return nbytes - n;
+ } else if (errno != EINTR) {
+ return -1;
+ }
+ }
+ return nbytes;
+}
+
+#ifndef SMALL
+static void
+xtrace_fd_swap(int from, int to)
+{
+ struct output *o = outxtop;
+
+ while (o != NULL) {
+ if (o->fd == from)
+ o->fd = to;
+ o = o->chain;
+ }
+}
+
+/*
+ * the -X flag is to be set or reset (not necessarily changed)
+ * Do what is needed to make tracing go to where it should
+ *
+ * Note: Xflag has not yet been altered, "on" indicates what it will become
+ */
+
+void
+xtracefdsetup(int on)
+{
+ if (!on) {
+ flushout(outx);
+
+ if (Xflag != 1) /* Was already +X */
+ return; /* so nothing to do */
+
+ outx = out2;
+ CTRACE(DBG_OUTPUT, ("Tracing to stderr\n"));
+ return;
+ }
+
+ if (Xflag == 1) { /* was already set */
+ /*
+ * This is a change of output file only
+ * Just close the current one, and reuse the struct output
+ */
+ if (!(outx->flags & OUTPUT_CLONE))
+ sh_close(outx->fd);
+ } else if (outxtop == NULL) {
+ /*
+ * -X is just turning on, for the forst time,
+ * need a new output struct to become outx
+ */
+ xtrace_clone(1);
+ } else
+ outx = outxtop;
+
+ if (outx != out2) {
+ outx->flags &= ~OUTPUT_CLONE;
+ outx->fd = to_upper_fd(dup(out2->fd));
+ register_sh_fd(outx->fd, xtrace_fd_swap);
+ }
+
+ CTRACE(DBG_OUTPUT, ("Tracing now to fd %d (from %d)\n",
+ outx->fd, out2->fd));
+}
+
+void
+xtrace_clone(int new)
+{
+ struct output *o;
+
+ CTRACE(DBG_OUTPUT,
+ ("xtrace_clone(%d): %sfd=%d buf=%p nleft=%d flags=%x ",
+ new, (outx == out2 ? "out2: " : ""),
+ outx->fd, outx->buf, outx->nleft, outx->flags));
+
+ flushout(outx);
+
+ if (!new && outxtop == NULL && Xflag == 0) {
+ /* following stderr, nothing to save */
+ CTRACE(DBG_OUTPUT, ("+X\n"));
+ return;
+ }
+
+ o = ckmalloc(sizeof(*o));
+ o->fd = outx->fd;
+ o->flags = OUTPUT_CLONE;
+ o->bufsize = outx->bufsize;
+ o->nleft = 0;
+ o->buf = NULL;
+ o->nextc = NULL;
+ o->chain = outxtop;
+ outx = o;
+ outxtop = o;
+
+ CTRACE(DBG_OUTPUT, ("-> fd=%d flags=%x[CLONE]\n", outx->fd, o->flags));
+}
+
+void
+xtrace_pop(void)
+{
+ struct output *o;
+
+ CTRACE(DBG_OUTPUT, ("trace_pop: fd=%d buf=%p nleft=%d flags=%x ",
+ outx->fd, outx->buf, outx->nleft, outx->flags));
+
+ flushout(outx);
+
+ if (outxtop == NULL) {
+ /*
+ * No -X has been used, so nothing much to do
+ */
+ CTRACE(DBG_OUTPUT, ("+X\n"));
+ return;
+ }
+
+ o = outxtop;
+ outx = o->chain;
+ if (outx == NULL)
+ outx = &errout;
+ outxtop = o->chain;
+ if (o != &errout) {
+ if (o->fd >= 0 && !(o->flags & OUTPUT_CLONE))
+ sh_close(o->fd);
+ if (o->buf)
+ ckfree(o->buf);
+ ckfree(o);
+ }
+
+ CTRACE(DBG_OUTPUT, ("-> fd=%d buf=%p nleft=%d flags=%x\n",
+ outx->fd, outx->buf, outx->nleft, outx->flags));
+}
+#endif /* SMALL */
diff --git a/bin/sh/output.h b/bin/sh/output.h
new file mode 100644
index 0000000..a19df43
--- /dev/null
+++ b/bin/sh/output.h
@@ -0,0 +1,109 @@
+/* $NetBSD: output.h,v 1.27 2017/11/21 03:42:39 kre Exp $ */
+
+/*-
+ * Copyright (c) 1991, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Kenneth Almquist.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * @(#)output.h 8.2 (Berkeley) 5/4/95
+ */
+
+#ifndef OUTPUT_INCL
+
+#include <stdarg.h>
+
+struct output {
+ char *nextc;
+ int nleft;
+ int bufsize;
+ char *buf;
+ short fd;
+ short flags;
+#ifndef SMALL
+ struct output *chain;
+#endif
+};
+
+/* flags for ->flags */
+#define OUTPUT_ERR 0x01 /* error occurred on output */
+#define OUTPUT_CLONE 0x02 /* this is a clone of another */
+
+extern struct output output;
+extern struct output errout;
+extern struct output memout;
+extern struct output *out1;
+extern struct output *out2;
+#ifdef SMALL
+#define outx out2
+#else
+extern struct output *outx;
+#endif
+
+void open_mem(char *, int, struct output *);
+void out1str(const char *);
+void out2str(const char *);
+void outstr(const char *, struct output *);
+void out2shstr(const char *);
+#ifdef SMALL
+#define outxstr out2str
+#define outxshstr out2shstr
+#else
+void outxstr(const char *);
+void outxshstr(const char *);
+#endif
+void outshstr(const char *, struct output *);
+void emptyoutbuf(struct output *);
+void flushall(void);
+void flushout(struct output *);
+void freestdout(void);
+void outfmt(struct output *, const char *, ...) __printflike(2, 3);
+void out1fmt(const char *, ...) __printflike(1, 2);
+#ifdef DEBUG
+void debugprintf(const char *, ...) __printflike(1, 2);
+#endif
+void fmtstr(char *, size_t, const char *, ...) __printflike(3, 4);
+void doformat(struct output *, const char *, va_list) __printflike(2, 0);
+int xwrite(int, char *, int);
+#ifdef SMALL
+#define xtracefdsetup(x) do { break; } while (0)
+#define xtrace_clone(x) do { break; } while (0)
+#define xtrace_pop() do { break; } while (0)
+#else
+void xtracefdsetup(int);
+void xtrace_clone(int);
+void xtrace_pop(void);
+#endif
+
+#define outc(c, file) (--(file)->nleft < 0? (emptyoutbuf(file), *(file)->nextc++ = (c)) : (*(file)->nextc++ = (c)))
+#define out1c(c) outc(c, out1)
+#define out2c(c) outc(c, out2)
+#define outxc(c) outc(c, outx)
+
+#define OUTPUT_INCL
+#endif
diff --git a/bin/sh/parser.c b/bin/sh/parser.c
new file mode 100644
index 0000000..6b153aa
--- /dev/null
+++ b/bin/sh/parser.c
@@ -0,0 +1,2756 @@
+/* $NetBSD: parser.c,v 1.164 2019/01/22 14:32:17 kre Exp $ */
+
+/*-
+ * Copyright (c) 1991, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Kenneth Almquist.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+#ifndef lint
+#if 0
+static char sccsid[] = "@(#)parser.c 8.7 (Berkeley) 5/16/95";
+#else
+__RCSID("$NetBSD: parser.c,v 1.164 2019/01/22 14:32:17 kre Exp $");
+#endif
+#endif /* not lint */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <limits.h>
+
+#include "shell.h"
+#include "parser.h"
+#include "nodes.h"
+#include "expand.h" /* defines rmescapes() */
+#include "eval.h" /* defines commandname */
+#include "syntax.h"
+#include "options.h"
+#include "input.h"
+#include "output.h"
+#include "var.h"
+#include "error.h"
+#include "memalloc.h"
+#include "mystring.h"
+#include "alias.h"
+#include "show.h"
+#ifndef SMALL
+#include "myhistedit.h"
+#endif
+#ifdef DEBUG
+#include "nodenames.h"
+#endif
+
+/*
+ * Shell command parser.
+ */
+
+/* values returned by readtoken */
+#include "token.h"
+
+#define OPENBRACE '{'
+#define CLOSEBRACE '}'
+
+struct HereDoc {
+ struct HereDoc *next; /* next here document in list */
+ union node *here; /* redirection node */
+ char *eofmark; /* string indicating end of input */
+ int striptabs; /* if set, strip leading tabs */
+ int startline; /* line number where << seen */
+};
+
+MKINIT struct parse_state parse_state;
+union parse_state_p psp = { .c_current_parser = &parse_state };
+
+static const struct parse_state init_parse_state = { /* all 0's ... */
+ .ps_heredoclist = NULL,
+ .ps_parsebackquote = 0,
+ .ps_doprompt = 0,
+ .ps_needprompt = 0,
+ .ps_lasttoken = 0,
+ .ps_tokpushback = 0,
+ .ps_wordtext = NULL,
+ .ps_checkkwd = 0,
+ .ps_redirnode = NULL,
+ .ps_heredoc = NULL,
+ .ps_quoteflag = 0,
+ .ps_startlinno = 0,
+ .ps_funclinno = 0,
+ .ps_elided_nl = 0,
+};
+
+STATIC union node *list(int);
+STATIC union node *andor(void);
+STATIC union node *pipeline(void);
+STATIC union node *command(void);
+STATIC union node *simplecmd(union node **, union node *);
+STATIC union node *makeword(int);
+STATIC void parsefname(void);
+STATIC int slurp_heredoc(char *const, const int, const int);
+STATIC void readheredocs(void);
+STATIC int peektoken(void);
+STATIC int readtoken(void);
+STATIC int xxreadtoken(void);
+STATIC int readtoken1(int, char const *, int);
+STATIC int noexpand(char *);
+STATIC void linebreak(void);
+STATIC void consumetoken(int);
+STATIC void synexpect(int, const char *) __dead;
+STATIC void synerror(const char *) __dead;
+STATIC void setprompt(int);
+STATIC int pgetc_linecont(void);
+
+static const char EOFhere[] = "EOF reading here (<<) document";
+
+#ifdef DEBUG
+int parsing = 0;
+#endif
+
+/*
+ * Read and parse a command. Returns NEOF on end of file. (NULL is a
+ * valid parse tree indicating a blank line.)
+ */
+
+union node *
+parsecmd(int interact)
+{
+ int t;
+ union node *n;
+
+#ifdef DEBUG
+ parsing++;
+#endif
+ tokpushback = 0;
+ checkkwd = 0;
+ doprompt = interact;
+ if (doprompt)
+ setprompt(1);
+ else
+ setprompt(0);
+ needprompt = 0;
+ t = readtoken();
+#ifdef DEBUG
+ parsing--;
+#endif
+ if (t == TEOF)
+ return NEOF;
+ if (t == TNL)
+ return NULL;
+
+#ifdef DEBUG
+ parsing++;
+#endif
+ tokpushback++;
+ n = list(1);
+#ifdef DEBUG
+ parsing--;
+#endif
+ if (heredoclist)
+ error("%d: Here document (<<%s) expected but not present",
+ heredoclist->startline, heredoclist->eofmark);
+ return n;
+}
+
+
+STATIC union node *
+list(int nlflag)
+{
+ union node *ntop, *n1, *n2, *n3;
+ int tok;
+
+ CTRACE(DBG_PARSE, ("list(%d): entered @%d\n",nlflag,plinno));
+
+ checkkwd = CHKNL | CHKKWD | CHKALIAS;
+ if (nlflag == 0 && tokendlist[peektoken()])
+ return NULL;
+ ntop = n1 = NULL;
+ for (;;) {
+ n2 = andor();
+ tok = readtoken();
+ if (tok == TBACKGND) {
+ if (n2->type == NCMD || n2->type == NPIPE)
+ n2->ncmd.backgnd = 1;
+ else if (n2->type == NREDIR)
+ n2->type = NBACKGND;
+ else {
+ n3 = stalloc(sizeof(struct nredir));
+ n3->type = NBACKGND;
+ n3->nredir.n = n2;
+ n3->nredir.redirect = NULL;
+ n2 = n3;
+ }
+ }
+
+ if (ntop == NULL)
+ ntop = n2;
+ else if (n1 == NULL) {
+ n1 = stalloc(sizeof(struct nbinary));
+ n1->type = NSEMI;
+ n1->nbinary.ch1 = ntop;
+ n1->nbinary.ch2 = n2;
+ ntop = n1;
+ } else {
+ n3 = stalloc(sizeof(struct nbinary));
+ n3->type = NSEMI;
+ n3->nbinary.ch1 = n1->nbinary.ch2;
+ n3->nbinary.ch2 = n2;
+ n1->nbinary.ch2 = n3;
+ n1 = n3;
+ }
+
+ switch (tok) {
+ case TBACKGND:
+ case TSEMI:
+ tok = readtoken();
+ /* FALLTHROUGH */
+ case TNL:
+ if (tok == TNL) {
+ readheredocs();
+ if (nlflag)
+ return ntop;
+ } else if (tok == TEOF && nlflag)
+ return ntop;
+ else
+ tokpushback++;
+
+ checkkwd = CHKNL | CHKKWD | CHKALIAS;
+ if (!nlflag && tokendlist[peektoken()])
+ return ntop;
+ break;
+ case TEOF:
+ pungetc(); /* push back EOF on input */
+ return ntop;
+ default:
+ if (nlflag)
+ synexpect(-1, 0);
+ tokpushback++;
+ return ntop;
+ }
+ }
+}
+
+STATIC union node *
+andor(void)
+{
+ union node *n1, *n2, *n3;
+ int t;
+
+ CTRACE(DBG_PARSE, ("andor: entered @%d\n", plinno));
+
+ n1 = pipeline();
+ for (;;) {
+ if ((t = readtoken()) == TAND) {
+ t = NAND;
+ } else if (t == TOR) {
+ t = NOR;
+ } else {
+ tokpushback++;
+ return n1;
+ }
+ n2 = pipeline();
+ n3 = stalloc(sizeof(struct nbinary));
+ n3->type = t;
+ n3->nbinary.ch1 = n1;
+ n3->nbinary.ch2 = n2;
+ n1 = n3;
+ }
+}
+
+STATIC union node *
+pipeline(void)
+{
+ union node *n1, *n2, *pipenode;
+ struct nodelist *lp, *prev;
+ int negate;
+
+ CTRACE(DBG_PARSE, ("pipeline: entered @%d\n", plinno));
+
+ negate = 0;
+ checkkwd = CHKNL | CHKKWD | CHKALIAS;
+ while (readtoken() == TNOT) {
+ CTRACE(DBG_PARSE, ("pipeline: TNOT recognized\n"));
+#ifndef BOGUS_NOT_COMMAND
+ if (posix && negate)
+ synerror("2nd \"!\" unexpected");
+#endif
+ negate++;
+ }
+ tokpushback++;
+ n1 = command();
+ if (readtoken() == TPIPE) {
+ pipenode = stalloc(sizeof(struct npipe));
+ pipenode->type = NPIPE;
+ pipenode->npipe.backgnd = 0;
+ lp = stalloc(sizeof(struct nodelist));
+ pipenode->npipe.cmdlist = lp;
+ lp->n = n1;
+ do {
+ prev = lp;
+ lp = stalloc(sizeof(struct nodelist));
+ lp->n = command();
+ prev->next = lp;
+ } while (readtoken() == TPIPE);
+ lp->next = NULL;
+ n1 = pipenode;
+ }
+ tokpushback++;
+ if (negate) {
+ CTRACE(DBG_PARSE, ("%snegate pipeline\n",
+ (negate&1) ? "" : "double "));
+ n2 = stalloc(sizeof(struct nnot));
+ n2->type = (negate & 1) ? NNOT : NDNOT;
+ n2->nnot.com = n1;
+ return n2;
+ } else
+ return n1;
+}
+
+
+
+STATIC union node *
+command(void)
+{
+ union node *n1, *n2;
+ union node *ap, **app;
+ union node *cp, **cpp;
+ union node *redir, **rpp;
+ int t;
+#ifdef BOGUS_NOT_COMMAND
+ int negate = 0;
+#endif
+
+ CTRACE(DBG_PARSE, ("command: entered @%d\n", plinno));
+
+ checkkwd = CHKNL | CHKKWD | CHKALIAS;
+ redir = NULL;
+ n1 = NULL;
+ rpp = &redir;
+
+ /* Check for redirection which may precede command */
+ while (readtoken() == TREDIR) {
+ *rpp = n2 = redirnode;
+ rpp = &n2->nfile.next;
+ parsefname();
+ }
+ tokpushback++;
+
+#ifdef BOGUS_NOT_COMMAND /* only in pileline() */
+ while (readtoken() == TNOT) {
+ CTRACE(DBG_PARSE, ("command: TNOT (bogus) recognized\n"));
+ negate++;
+ }
+ tokpushback++;
+#endif
+
+ switch (readtoken()) {
+ case TIF:
+ n1 = stalloc(sizeof(struct nif));
+ n1->type = NIF;
+ n1->nif.test = list(0);
+ consumetoken(TTHEN);
+ n1->nif.ifpart = list(0);
+ n2 = n1;
+ while (readtoken() == TELIF) {
+ n2->nif.elsepart = stalloc(sizeof(struct nif));
+ n2 = n2->nif.elsepart;
+ n2->type = NIF;
+ n2->nif.test = list(0);
+ consumetoken(TTHEN);
+ n2->nif.ifpart = list(0);
+ }
+ if (lasttoken == TELSE)
+ n2->nif.elsepart = list(0);
+ else {
+ n2->nif.elsepart = NULL;
+ tokpushback++;
+ }
+ consumetoken(TFI);
+ checkkwd = CHKKWD | CHKALIAS;
+ break;
+ case TWHILE:
+ case TUNTIL:
+ n1 = stalloc(sizeof(struct nbinary));
+ n1->type = (lasttoken == TWHILE)? NWHILE : NUNTIL;
+ n1->nbinary.ch1 = list(0);
+ consumetoken(TDO);
+ n1->nbinary.ch2 = list(0);
+ consumetoken(TDONE);
+ checkkwd = CHKKWD | CHKALIAS;
+ break;
+ case TFOR:
+ if (readtoken() != TWORD || quoteflag || ! goodname(wordtext))
+ synerror("Bad for loop variable");
+ n1 = stalloc(sizeof(struct nfor));
+ n1->type = NFOR;
+ n1->nfor.var = wordtext;
+ linebreak();
+ if (lasttoken==TWORD && !quoteflag && equal(wordtext,"in")) {
+ app = &ap;
+ while (readtoken() == TWORD) {
+ n2 = makeword(startlinno);
+ *app = n2;
+ app = &n2->narg.next;
+ }
+ *app = NULL;
+ n1->nfor.args = ap;
+ if (lasttoken != TNL && lasttoken != TSEMI)
+ synexpect(TSEMI, 0);
+ } else {
+ static char argvars[5] = {
+ CTLVAR, VSNORMAL|VSQUOTE, '@', '=', '\0'
+ };
+
+ n2 = stalloc(sizeof(struct narg));
+ n2->type = NARG;
+ n2->narg.text = argvars;
+ n2->narg.backquote = NULL;
+ n2->narg.next = NULL;
+ n2->narg.lineno = startlinno;
+ n1->nfor.args = n2;
+ /*
+ * Newline or semicolon here is optional (but note
+ * that the original Bourne shell only allowed NL).
+ */
+ if (lasttoken != TNL && lasttoken != TSEMI)
+ tokpushback++;
+ }
+ checkkwd = CHKNL | CHKKWD | CHKALIAS;
+ if ((t = readtoken()) == TDO)
+ t = TDONE;
+ else if (t == TBEGIN)
+ t = TEND;
+ else
+ synexpect(TDO, 0);
+ n1->nfor.body = list(0);
+ consumetoken(t);
+ checkkwd = CHKKWD | CHKALIAS;
+ break;
+ case TCASE:
+ n1 = stalloc(sizeof(struct ncase));
+ n1->type = NCASE;
+ n1->ncase.lineno = startlinno - elided_nl;
+ consumetoken(TWORD);
+ n1->ncase.expr = makeword(startlinno);
+ linebreak();
+ if (lasttoken != TWORD || !equal(wordtext, "in"))
+ synexpect(-1, "in");
+ cpp = &n1->ncase.cases;
+ checkkwd = CHKNL | CHKKWD;
+ readtoken();
+ /*
+ * Both ksh and bash accept 'case x in esac'
+ * so configure scripts started taking advantage of this.
+ * The page: http://pubs.opengroup.org/onlinepubs/\
+ * 009695399/utilities/xcu_chap02.html contradicts itself,
+ * as to if this is legal; the "Case Conditional Format"
+ * paragraph shows one case is required, but the "Grammar"
+ * section shows a grammar that explicitly allows the no
+ * case option.
+ *
+ * The standard also says (section 2.10):
+ * This formal syntax shall take precedence over the
+ * preceding text syntax description.
+ * ie: the "Grammar" section wins. The text is just
+ * a rough guide (introduction to the common case.)
+ */
+ while (lasttoken != TESAC) {
+ *cpp = cp = stalloc(sizeof(struct nclist));
+ cp->type = NCLIST;
+ app = &cp->nclist.pattern;
+ if (lasttoken == TLP)
+ readtoken();
+ for (;;) {
+ if (lasttoken < TWORD)
+ synexpect(TWORD, 0);
+ *app = ap = makeword(startlinno);
+ checkkwd = CHKNL | CHKKWD;
+ if (readtoken() != TPIPE)
+ break;
+ app = &ap->narg.next;
+ readtoken();
+ }
+ if (lasttoken != TRP)
+ synexpect(TRP, 0);
+ cp->nclist.lineno = startlinno;
+ cp->nclist.body = list(0);
+
+ checkkwd = CHKNL | CHKKWD | CHKALIAS;
+ if ((t = readtoken()) != TESAC) {
+ if (t != TENDCASE && t != TCASEFALL) {
+ synexpect(TENDCASE, 0);
+ } else {
+ if (t == TCASEFALL)
+ cp->type = NCLISTCONT;
+ checkkwd = CHKNL | CHKKWD;
+ readtoken();
+ }
+ }
+ cpp = &cp->nclist.next;
+ }
+ *cpp = NULL;
+ checkkwd = CHKKWD | CHKALIAS;
+ break;
+ case TLP:
+ n1 = stalloc(sizeof(struct nredir));
+ n1->type = NSUBSHELL;
+ n1->nredir.n = list(0);
+ n1->nredir.redirect = NULL;
+ if (n1->nredir.n == NULL)
+ synexpect(-1, 0);
+ consumetoken(TRP);
+ checkkwd = CHKKWD | CHKALIAS;
+ break;
+ case TBEGIN:
+ n1 = list(0);
+ if (posix && n1 == NULL)
+ synexpect(-1, 0);
+ consumetoken(TEND);
+ checkkwd = CHKKWD | CHKALIAS;
+ break;
+
+ case TBACKGND:
+ case TSEMI:
+ case TAND:
+ case TOR:
+ case TPIPE:
+ case TNL:
+ case TEOF:
+ case TRP:
+ case TENDCASE:
+ case TCASEFALL:
+ /*
+ * simple commands must have something in them,
+ * either a word (which at this point includes a=b)
+ * or a redirection. If we reached the end of the
+ * command (which one of these tokens indicates)
+ * when we are just starting, and have not had a
+ * redirect, then ...
+ *
+ * nb: it is still possible to end up with empty
+ * simple commands, if the "command" is a var
+ * expansion that produces nothing:
+ * X= ; $X && $X
+ * --> &&
+ * That is OK and is handled after word expansions.
+ */
+ if (!redir)
+ synexpect(-1, 0);
+ /*
+ * continue to build a node containing the redirect.
+ * the tokpushback means that our ending token will be
+ * read again in simplecmd, causing it to terminate,
+ * so only the redirect(s) will be contained in the
+ * returned n1
+ */
+ /* FALLTHROUGH */
+ case TWORD:
+ tokpushback++;
+ n1 = simplecmd(rpp, redir);
+ goto checkneg;
+ default:
+ synexpect(-1, 0);
+ /* NOTREACHED */
+ }
+
+ /* Now check for redirection which may follow command */
+ while (readtoken() == TREDIR) {
+ *rpp = n2 = redirnode;
+ rpp = &n2->nfile.next;
+ parsefname();
+ }
+ tokpushback++;
+ *rpp = NULL;
+ if (redir) {
+ if (n1 == NULL || n1->type != NSUBSHELL) {
+ n2 = stalloc(sizeof(struct nredir));
+ n2->type = NREDIR;
+ n2->nredir.n = n1;
+ n1 = n2;
+ }
+ n1->nredir.redirect = redir;
+ }
+
+ checkneg:
+#ifdef BOGUS_NOT_COMMAND
+ if (negate) {
+ VTRACE(DBG_PARSE, ("bogus %snegate command\n",
+ (negate&1) ? "" : "double "));
+ n2 = stalloc(sizeof(struct nnot));
+ n2->type = (negate & 1) ? NNOT : NDNOT;
+ n2->nnot.com = n1;
+ return n2;
+ }
+ else
+#endif
+ return n1;
+}
+
+
+STATIC union node *
+simplecmd(union node **rpp, union node *redir)
+{
+ union node *args, **app;
+ union node *n = NULL;
+ int line = 0;
+ int savecheckkwd;
+#ifdef BOGUS_NOT_COMMAND
+ union node *n2;
+ int negate = 0;
+#endif
+
+ CTRACE(DBG_PARSE, ("simple command with%s redir already @%d\n",
+ redir ? "" : "out", plinno));
+
+ /* If we don't have any redirections already, then we must reset */
+ /* rpp to be the address of the local redir variable. */
+ if (redir == 0)
+ rpp = &redir;
+
+ args = NULL;
+ app = &args;
+
+#ifdef BOGUS_NOT_COMMAND /* pipelines get negated, commands do not */
+ while (readtoken() == TNOT) {
+ VTRACE(DBG_PARSE, ("simplcmd: bogus TNOT recognized\n"));
+ negate++;
+ }
+ tokpushback++;
+#endif
+
+ savecheckkwd = CHKALIAS;
+ for (;;) {
+ checkkwd = savecheckkwd;
+ if (readtoken() == TWORD) {
+ if (line == 0)
+ line = startlinno;
+ n = makeword(startlinno);
+ *app = n;
+ app = &n->narg.next;
+ if (savecheckkwd != 0 && !isassignment(wordtext))
+ savecheckkwd = 0;
+ } else if (lasttoken == TREDIR) {
+ if (line == 0)
+ line = startlinno;
+ *rpp = n = redirnode;
+ rpp = &n->nfile.next;
+ parsefname(); /* read name of redirection file */
+ } else if (lasttoken == TLP && app == &args->narg.next
+ && redir == 0) {
+ /* We have a function */
+ consumetoken(TRP);
+ funclinno = plinno;
+ rmescapes(n->narg.text);
+ if (strchr(n->narg.text, '/'))
+ synerror("Bad function name");
+ VTRACE(DBG_PARSE, ("Function '%s' seen @%d\n",
+ n->narg.text, plinno));
+ n->type = NDEFUN;
+ n->narg.lineno = plinno - elided_nl;
+ n->narg.next = command();
+ funclinno = 0;
+ goto checkneg;
+ } else {
+ tokpushback++;
+ break;
+ }
+ }
+
+ if (args == NULL && redir == NULL)
+ synexpect(-1, 0);
+ *app = NULL;
+ *rpp = NULL;
+ n = stalloc(sizeof(struct ncmd));
+ n->type = NCMD;
+ n->ncmd.lineno = line - elided_nl;
+ n->ncmd.backgnd = 0;
+ n->ncmd.args = args;
+ n->ncmd.redirect = redir;
+ n->ncmd.lineno = startlinno;
+
+ checkneg:
+#ifdef BOGUS_NOT_COMMAND
+ if (negate) {
+ VTRACE(DBG_PARSE, ("bogus %snegate simplecmd\n",
+ (negate&1) ? "" : "double "));
+ n2 = stalloc(sizeof(struct nnot));
+ n2->type = (negate & 1) ? NNOT : NDNOT;
+ n2->nnot.com = n;
+ return n2;
+ }
+ else
+#endif
+ return n;
+}
+
+STATIC union node *
+makeword(int lno)
+{
+ union node *n;
+
+ n = stalloc(sizeof(struct narg));
+ n->type = NARG;
+ n->narg.next = NULL;
+ n->narg.text = wordtext;
+ n->narg.backquote = backquotelist;
+ n->narg.lineno = lno;
+ return n;
+}
+
+void
+fixredir(union node *n, const char *text, int err)
+{
+
+ VTRACE(DBG_PARSE, ("Fix redir %s %d\n", text, err));
+ if (!err)
+ n->ndup.vname = NULL;
+
+ if (is_number(text))
+ n->ndup.dupfd = number(text);
+ else if (text[0] == '-' && text[1] == '\0')
+ n->ndup.dupfd = -1;
+ else {
+
+ if (err)
+ synerror("Bad fd number");
+ else
+ n->ndup.vname = makeword(startlinno - elided_nl);
+ }
+}
+
+
+STATIC void
+parsefname(void)
+{
+ union node *n = redirnode;
+
+ if (readtoken() != TWORD)
+ synexpect(-1, 0);
+ if (n->type == NHERE) {
+ struct HereDoc *here = heredoc;
+ struct HereDoc *p;
+
+ if (quoteflag == 0)
+ n->type = NXHERE;
+ VTRACE(DBG_PARSE, ("Here document %d @%d\n", n->type, plinno));
+ if (here->striptabs) {
+ while (*wordtext == '\t')
+ wordtext++;
+ }
+
+ /*
+ * this test is not really necessary, we are not
+ * required to expand wordtext, but there's no reason
+ * it cannot be $$ or something like that - that would
+ * not mean the pid, but literally two '$' characters.
+ * There is no need for limits on what the word can be.
+ * However, it needs to stay literal as entered, not
+ * have $ converted to CTLVAR or something, which as
+ * the parser is, at the minute, is impossible to prevent.
+ * So, leave it like this until the rest of the parser is fixed.
+ */
+ if (!noexpand(wordtext))
+ synerror("Illegal eof marker for << redirection");
+
+ rmescapes(wordtext);
+ here->eofmark = wordtext;
+ here->next = NULL;
+ if (heredoclist == NULL)
+ heredoclist = here;
+ else {
+ for (p = heredoclist ; p->next ; p = p->next)
+ continue;
+ p->next = here;
+ }
+ } else if (n->type == NTOFD || n->type == NFROMFD) {
+ fixredir(n, wordtext, 0);
+ } else {
+ n->nfile.fname = makeword(startlinno - elided_nl);
+ }
+}
+
+/*
+ * Check to see whether we are at the end of the here document. When this
+ * is called, c is set to the first character of the next input line. If
+ * we are at the end of the here document, this routine sets the c to PEOF.
+ * The new value of c is returned.
+ */
+
+static int
+checkend(int c, char * const eofmark, const int striptabs)
+{
+
+ if (striptabs) {
+ while (c == '\t')
+ c = pgetc();
+ }
+ if (c == PEOF) {
+ if (*eofmark == '\0')
+ return (c);
+ synerror(EOFhere);
+ }
+ if (c == *eofmark) {
+ int c2;
+ char *q;
+
+ for (q = eofmark + 1; c2 = pgetc(), *q != '\0' && c2 == *q; q++)
+ if (c2 == '\n') {
+ plinno++;
+ needprompt = doprompt;
+ }
+ if ((c2 == PEOF || c2 == '\n') && *q == '\0') {
+ c = PEOF;
+ if (c2 == '\n') {
+ plinno++;
+ needprompt = doprompt;
+ }
+ } else {
+ pungetc();
+ pushstring(eofmark + 1, q - (eofmark + 1), NULL);
+ }
+ } else if (c == '\n' && *eofmark == '\0') {
+ c = PEOF;
+ plinno++;
+ needprompt = doprompt;
+ }
+ return (c);
+}
+
+
+/*
+ * Input any here documents.
+ */
+
+STATIC int
+slurp_heredoc(char *const eofmark, const int striptabs, const int sq)
+{
+ int c;
+ char *out;
+ int lines = plinno;
+
+ c = pgetc();
+
+ /*
+ * If we hit EOF on the input, and the eofmark is a null string ('')
+ * we consider this empty line to be the eofmark, and exit without err.
+ */
+ if (c == PEOF && *eofmark != '\0')
+ synerror(EOFhere);
+
+ STARTSTACKSTR(out);
+
+ while ((c = checkend(c, eofmark, striptabs)) != PEOF) {
+ do {
+ if (sq) {
+ /*
+ * in single quoted mode (eofmark quoted)
+ * all we look for is \n so we can check
+ * for the epfmark - everything saved literally.
+ */
+ STPUTC(c, out);
+ if (c == '\n') {
+ plinno++;
+ break;
+ }
+ continue;
+ }
+ /*
+ * In double quoted (non-quoted eofmark)
+ * we must handle \ followed by \n here
+ * otherwise we can mismatch the end mark.
+ * All other uses of \ will be handled later
+ * when the here doc is expanded.
+ *
+ * This also makes sure \\ followed by \n does
+ * not suppress the newline (the \ quotes itself)
+ */
+ if (c == '\\') { /* A backslash */
+ STPUTC(c, out);
+ c = pgetc(); /* followed by */
+ if (c == '\n') { /* a newline? */
+ STPUTC(c, out);
+ plinno++;
+ continue; /* don't break */
+ }
+ }
+ STPUTC(c, out); /* keep the char */
+ if (c == '\n') { /* at end of line */
+ plinno++;
+ break; /* look for eofmark */
+ }
+ } while ((c = pgetc()) != PEOF);
+
+ /*
+ * If we have read a line, and reached EOF, without
+ * finding the eofmark, whether the EOF comes before
+ * or immediately after the \n, that is an error.
+ */
+ if (c == PEOF || (c = pgetc()) == PEOF)
+ synerror(EOFhere);
+ }
+ STPUTC('\0', out);
+
+ c = out - stackblock();
+ out = stackblock();
+ grabstackblock(c);
+ wordtext = out;
+
+ VTRACE(DBG_PARSE,
+ ("Slurped a %d line %sheredoc (to '%s')%s: len %d, \"%.*s%s\" @%d\n",
+ plinno - lines, sq ? "quoted " : "", eofmark,
+ striptabs ? " tab stripped" : "", c, (c > 16 ? 16 : c),
+ wordtext, (c > 16 ? "..." : ""), plinno));
+
+ return (plinno - lines);
+}
+
+static char *
+insert_elided_nl(char *str)
+{
+ while (elided_nl > 0) {
+ STPUTC(CTLNONL, str);
+ elided_nl--;
+ }
+ return str;
+}
+
+STATIC void
+readheredocs(void)
+{
+ struct HereDoc *here;
+ union node *n;
+ int line, l;
+
+ line = 0; /*XXX - gcc! obviously unneeded */
+ if (heredoclist)
+ line = heredoclist->startline + 1;
+ l = 0;
+ while (heredoclist) {
+ line += l;
+ here = heredoclist;
+ heredoclist = here->next;
+ if (needprompt) {
+ setprompt(2);
+ needprompt = 0;
+ }
+
+ l = slurp_heredoc(here->eofmark, here->striptabs,
+ here->here->nhere.type == NHERE);
+
+ here->here->nhere.doc = n = makeword(line);
+
+ if (here->here->nhere.type == NHERE)
+ continue;
+
+ /*
+ * Now "parse" here docs that have unquoted eofmarkers.
+ */
+ setinputstring(wordtext, 1, line);
+ VTRACE(DBG_PARSE, ("Reprocessing %d line here doc from %d\n",
+ l, line));
+ readtoken1(pgetc(), DQSYNTAX, 1);
+ n->narg.text = wordtext;
+ n->narg.backquote = backquotelist;
+ popfile();
+ }
+}
+
+STATIC int
+peektoken(void)
+{
+ int t;
+
+ t = readtoken();
+ tokpushback++;
+ return (t);
+}
+
+STATIC int
+readtoken(void)
+{
+ int t;
+#ifdef DEBUG
+ int alreadyseen = tokpushback;
+ int savecheckkwd = checkkwd;
+#endif
+ struct alias *ap;
+
+ top:
+ t = xxreadtoken();
+
+ if (checkkwd & CHKNL) {
+ while (t == TNL) {
+ readheredocs();
+ t = xxreadtoken();
+ }
+ }
+
+ /*
+ * check for keywords and aliases
+ */
+ if (t == TWORD && !quoteflag) {
+ const char *const *pp;
+
+ if (checkkwd & CHKKWD)
+ for (pp = parsekwd; *pp; pp++) {
+ if (**pp == *wordtext && equal(*pp, wordtext)) {
+ lasttoken = t = pp -
+ parsekwd + KWDOFFSET;
+ VTRACE(DBG_PARSE,
+ ("keyword %s recognized @%d\n",
+ tokname[t], plinno));
+ goto out;
+ }
+ }
+
+ if (checkkwd & CHKALIAS &&
+ (ap = lookupalias(wordtext, 1)) != NULL) {
+ VTRACE(DBG_PARSE,
+ ("alias '%s' recognized -> <:%s:>\n",
+ wordtext, ap->val));
+ pushstring(ap->val, strlen(ap->val), ap);
+ goto top;
+ }
+ }
+ out:
+ if (t != TNOT)
+ checkkwd = 0;
+
+ VTRACE(DBG_PARSE, ("%stoken %s %s @%d (chkkwd %x->%x)\n",
+ alreadyseen ? "reread " : "", tokname[t],
+ t == TWORD ? wordtext : "", plinno, savecheckkwd, checkkwd));
+ return (t);
+}
+
+
+/*
+ * Read the next input token.
+ * If the token is a word, we set backquotelist to the list of cmds in
+ * backquotes. We set quoteflag to true if any part of the word was
+ * quoted.
+ * If the token is TREDIR, then we set redirnode to a structure containing
+ * the redirection.
+ * In all cases, the variable startlinno is set to the number of the line
+ * on which the token starts.
+ *
+ * [Change comment: here documents and internal procedures]
+ * [Readtoken shouldn't have any arguments. Perhaps we should make the
+ * word parsing code into a separate routine. In this case, readtoken
+ * doesn't need to have any internal procedures, but parseword does.
+ * We could also make parseoperator in essence the main routine, and
+ * have parseword (readtoken1?) handle both words and redirection.]
+ */
+
+#define RETURN(token) return lasttoken = (token)
+
+STATIC int
+xxreadtoken(void)
+{
+ int c;
+
+ if (tokpushback) {
+ tokpushback = 0;
+ CTRACE(DBG_LEXER,
+ ("xxreadtoken() returns %s (%d) again\n",
+ tokname[lasttoken], lasttoken));
+ return lasttoken;
+ }
+ if (needprompt) {
+ setprompt(2);
+ needprompt = 0;
+ }
+ elided_nl = 0;
+ startlinno = plinno;
+ for (;;) { /* until token or start of word found */
+ c = pgetc_macro();
+ CTRACE(DBG_LEXER, ("xxreadtoken() sees '%c' (%#.2x) ",
+ c&0xFF, c&0x1FF));
+ switch (c) {
+ case ' ': case '\t': case PFAKE:
+ CTRACE(DBG_LEXER, (" ignored\n"));
+ continue;
+ case '#':
+ while ((c = pgetc()) != '\n' && c != PEOF)
+ continue;
+ CTRACE(DBG_LEXER,
+ ("skipped comment to (not incl) \\n\n"));
+ pungetc();
+ continue;
+
+ case '\n':
+ plinno++;
+ CTRACE(DBG_LEXER, ("newline now @%d\n", plinno));
+ needprompt = doprompt;
+ RETURN(TNL);
+ case PEOF:
+ CTRACE(DBG_LEXER, ("EOF -> TEOF (return)\n"));
+ RETURN(TEOF);
+
+ case '&':
+ if (pgetc_linecont() == '&') {
+ CTRACE(DBG_LEXER,
+ ("and another -> TAND (return)\n"));
+ RETURN(TAND);
+ }
+ pungetc();
+ CTRACE(DBG_LEXER, (" -> TBACKGND (return)\n"));
+ RETURN(TBACKGND);
+ case '|':
+ if (pgetc_linecont() == '|') {
+ CTRACE(DBG_LEXER,
+ ("and another -> TOR (return)\n"));
+ RETURN(TOR);
+ }
+ pungetc();
+ CTRACE(DBG_LEXER, (" -> TPIPE (return)\n"));
+ RETURN(TPIPE);
+ case ';':
+ switch (pgetc_linecont()) {
+ case ';':
+ CTRACE(DBG_LEXER,
+ ("and another -> TENDCASE (return)\n"));
+ RETURN(TENDCASE);
+ case '&':
+ CTRACE(DBG_LEXER,
+ ("and '&' -> TCASEFALL (return)\n"));
+ RETURN(TCASEFALL);
+ default:
+ pungetc();
+ CTRACE(DBG_LEXER, (" -> TSEMI (return)\n"));
+ RETURN(TSEMI);
+ }
+ case '(':
+ CTRACE(DBG_LEXER, (" -> TLP (return)\n"));
+ RETURN(TLP);
+ case ')':
+ CTRACE(DBG_LEXER, (" -> TRP (return)\n"));
+ RETURN(TRP);
+
+ case '\\':
+ switch (pgetc()) {
+ case '\n':
+ startlinno = ++plinno;
+ CTRACE(DBG_LEXER, ("\\\n ignored, now @%d\n",
+ plinno));
+ if (doprompt)
+ setprompt(2);
+ else
+ setprompt(0);
+ continue;
+ case PEOF:
+ CTRACE(DBG_LEXER,
+ ("then EOF -> TEOF (return) '\\' dropped\n"));
+ RETURN(TEOF);
+ default:
+ CTRACE(DBG_LEXER, ("not \\\n or EOF: "));
+ pungetc();
+ break;
+ }
+ /* FALLTHROUGH */
+ default:
+ CTRACE(DBG_LEXER, ("getting a word\n"));
+ return readtoken1(c, BASESYNTAX, 0);
+ }
+ }
+#undef RETURN
+}
+
+
+
+/*
+ * If eofmark is NULL, read a word or a redirection symbol. If eofmark
+ * is not NULL, read a here document. In the latter case, eofmark is the
+ * word which marks the end of the document and striptabs is true if
+ * leading tabs should be stripped from the document. The argument firstc
+ * is the first character of the input token or document.
+ *
+ * Because C does not have internal subroutines, I have simulated them
+ * using goto's to implement the subroutine linkage. The following macros
+ * will run code that appears at the end of readtoken1.
+ */
+
+/*
+ * We used to remember only the current syntax, variable nesting level,
+ * double quote state for each var nesting level, and arith nesting
+ * level (unrelated to var nesting) and one prev syntax when in arith
+ * syntax. This worked for simple cases, but can't handle arith inside
+ * var expansion inside arith inside var with some quoted and some not.
+ *
+ * Inspired by FreeBSD's implementation (though it was the obvious way)
+ * though implemented differently, we now have a stack that keeps track
+ * of what we are doing now, and what we were doing previously.
+ * Every time something changes, which will eventually end and should
+ * revert to the previous state, we push this stack, and then pop it
+ * again later (that is every ${} with an operator (to parse the word
+ * or pattern that follows) ${x} and $x are too simple to need it)
+ * $(( )) $( ) and "...". Always. Really, always!
+ *
+ * The stack is implemented as one static (on the C stack) base block
+ * containing LEVELS_PER_BLOCK (8) stack entries, which should be
+ * enough for the vast majority of cases. For torture tests, we
+ * malloc more blocks as needed. All accesses through the inline
+ * functions below.
+ */
+
+/*
+ * varnest & arinest will typically be 0 or 1
+ * (varnest can increment in usages like ${x=${y}} but probably
+ * does not really need to)
+ * parenlevel allows balancing parens inside a $(( )), it is reset
+ * at each new nesting level ( $(( ( x + 3 ${unset-)} )) does not work.
+ * quoted is special - we need to know 2 things ... are we inside "..."
+ * (even if inherited from some previous nesting level) and was there
+ * an opening '"' at this level (so the next will be closing).
+ * "..." can span nesting levels, but cannot be opened in one and
+ * closed in a different one.
+ * To handle this, "quoted" has two fields, the bottom 4 (really 2)
+ * bits are 0, 1, or 2, for un, single, and double quoted (single quoted
+ * is really so special that this setting is not very important)
+ * and 0x10 that indicates that an opening quote has been seen.
+ * The bottom 4 bits are inherited, the 0x10 bit is not.
+ */
+struct tokenstate {
+ const char *ts_syntax;
+ unsigned short ts_parenlevel; /* counters */
+ unsigned short ts_varnest; /* 64000 levels should be enough! */
+ unsigned short ts_arinest;
+ unsigned short ts_quoted; /* 1 -> single, 2 -> double */
+ unsigned short ts_magicq; /* heredoc or word expand */
+};
+
+#define NQ 0x00 /* Unquoted */
+#define SQ 0x01 /* Single Quotes */
+#define DQ 0x02 /* Double Quotes (or equivalent) */
+#define CQ 0x03 /* C style Single Quotes */
+#define QF 0x0F /* Mask to extract previous values */
+#define QS 0x10 /* Quoting started at this level in stack */
+
+#define LEVELS_PER_BLOCK 8
+#define VSS struct statestack
+
+struct statestack {
+ VSS *prev; /* previous block in list */
+ int cur; /* which of our tokenstates is current */
+ struct tokenstate tokenstate[LEVELS_PER_BLOCK];
+};
+
+static inline struct tokenstate *
+currentstate(VSS *stack)
+{
+ return &stack->tokenstate[stack->cur];
+}
+
+#ifdef notdef
+static inline struct tokenstate *
+prevstate(VSS *stack)
+{
+ if (stack->cur != 0)
+ return &stack->tokenstate[stack->cur - 1];
+ if (stack->prev == NULL) /* cannot drop below base */
+ return &stack->tokenstate[0];
+ return &stack->prev->tokenstate[LEVELS_PER_BLOCK - 1];
+}
+#endif
+
+static inline VSS *
+bump_state_level(VSS *stack)
+{
+ struct tokenstate *os, *ts;
+
+ os = currentstate(stack);
+
+ if (++stack->cur >= LEVELS_PER_BLOCK) {
+ VSS *ss;
+
+ ss = (VSS *)ckmalloc(sizeof (struct statestack));
+ ss->cur = 0;
+ ss->prev = stack;
+ stack = ss;
+ }
+
+ ts = currentstate(stack);
+
+ ts->ts_parenlevel = 0; /* parens inside never match outside */
+
+ ts->ts_quoted = os->ts_quoted & QF; /* these are default settings */
+ ts->ts_varnest = os->ts_varnest;
+ ts->ts_arinest = os->ts_arinest; /* when appropriate */
+ ts->ts_syntax = os->ts_syntax; /* they will be altered */
+ ts->ts_magicq = os->ts_magicq;
+
+ return stack;
+}
+
+static inline VSS *
+drop_state_level(VSS *stack)
+{
+ if (stack->cur == 0) {
+ VSS *ss;
+
+ ss = stack;
+ stack = ss->prev;
+ if (stack == NULL)
+ return ss;
+ ckfree(ss);
+ }
+ --stack->cur;
+ return stack;
+}
+
+static inline void
+cleanup_state_stack(VSS *stack)
+{
+ while (stack->prev != NULL) {
+ stack->cur = 0;
+ stack = drop_state_level(stack);
+ }
+}
+
+#define PARSESUB() {goto parsesub; parsesub_return:;}
+#define PARSEARITH() {goto parsearith; parsearith_return:;}
+
+/*
+ * The following macros all assume the existance of a local var "stack"
+ * which contains a pointer to the current struct stackstate
+ */
+
+/*
+ * These are macros rather than inline funcs to avoid code churn as much
+ * as possible - they replace macros of the same name used previously.
+ */
+#define ISDBLQUOTE() (currentstate(stack)->ts_quoted & QS)
+#define SETDBLQUOTE() (currentstate(stack)->ts_quoted = QS | DQ)
+#ifdef notdef
+#define CLRDBLQUOTE() (currentstate(stack)->ts_quoted = \
+ stack->cur != 0 || stack->prev ? \
+ prevstate(stack)->ts_quoted & QF : 0)
+#endif
+
+/*
+ * This set are just to avoid excess typing and line lengths...
+ * The ones that "look like" var names must be implemented to be lvalues
+ */
+#define syntax (currentstate(stack)->ts_syntax)
+#define parenlevel (currentstate(stack)->ts_parenlevel)
+#define varnest (currentstate(stack)->ts_varnest)
+#define arinest (currentstate(stack)->ts_arinest)
+#define quoted (currentstate(stack)->ts_quoted)
+#define magicq (currentstate(stack)->ts_magicq)
+#define TS_PUSH() (stack = bump_state_level(stack))
+#define TS_POP() (stack = drop_state_level(stack))
+
+/*
+ * Called to parse command substitutions. oldstyle is true if the command
+ * is enclosed inside `` (otherwise it was enclosed in "$( )")
+ *
+ * Internally nlpp is a pointer to the head of the linked
+ * list of commands (passed by reference), and savelen is the number of
+ * characters on the top of the stack which must be preserved.
+ */
+static char *
+parsebackq(VSS *const stack, char * const in,
+ struct nodelist **const pbqlist, const int oldstyle)
+{
+ struct nodelist **nlpp;
+ const int savepbq = parsebackquote;
+ union node *n;
+ char *out;
+ char *str = NULL;
+ char *volatile sstr = str;
+ struct jmploc jmploc;
+ struct jmploc *const savehandler = handler;
+ struct parsefile *const savetopfile = getcurrentfile();
+ const int savelen = in - stackblock();
+ int saveprompt;
+ int lno;
+
+ if (setjmp(jmploc.loc)) {
+ popfilesupto(savetopfile);
+ if (sstr)
+ ckfree(__UNVOLATILE(sstr));
+ cleanup_state_stack(stack);
+ parsebackquote = 0;
+ handler = savehandler;
+ CTRACE(DBG_LEXER, ("parsebackq() err (%d), unwinding\n",
+ exception));
+ longjmp(handler->loc, 1);
+ }
+ INTOFF;
+ sstr = str = NULL;
+ if (savelen > 0) {
+ sstr = str = ckmalloc(savelen);
+ memcpy(str, stackblock(), savelen);
+ }
+ handler = &jmploc;
+ INTON;
+ if (oldstyle) {
+ /*
+ * We must read until the closing backquote, giving special
+ * treatment to some slashes, and then push the string and
+ * reread it as input, interpreting it normally.
+ */
+ int pc;
+ int psavelen;
+ char *pstr;
+ int line1 = plinno;
+
+ VTRACE(DBG_PARSE|DBG_LEXER,
+ ("parsebackq: repackaging `` as $( )"));
+ /*
+ * Because the entire `...` is read here, we don't
+ * need to bother the state stack. That will be used
+ * (as appropriate) when the processed string is re-read.
+ */
+ STARTSTACKSTR(out);
+#ifdef DEBUG
+ for (psavelen = 0;;psavelen++) { /* } */
+#else
+ for (;;) {
+#endif
+ if (needprompt) {
+ setprompt(2);
+ needprompt = 0;
+ }
+ pc = pgetc();
+ VTRACE(DBG_LEXER,
+ ("parsebackq() got '%c'(%#.2x) in `` %s", pc&0xFF,
+ pc&0x1FF, pc == '`' ? "terminator\n" : ""));
+ if (pc == '`')
+ break;
+ switch (pc) {
+ case '\\':
+ pc = pgetc();
+ VTRACE(DBG_LEXER, ("then '%c'(%#.2x) ",
+ pc&0xFF, pc&0x1FF));
+#ifdef DEBUG
+ psavelen++;
+#endif
+ if (pc == '\n') { /* keep \ \n for later */
+ plinno++;
+ VTRACE(DBG_LEXER, ("@%d ", plinno));
+ needprompt = doprompt;
+ }
+ if (pc != '\\' && pc != '`' && pc != '$'
+ && (!ISDBLQUOTE() || pc != '"')) {
+ VTRACE(DBG_LEXER, ("keep '\\' "));
+ STPUTC('\\', out);
+ }
+ break;
+
+ case '\n':
+ plinno++;
+ VTRACE(DBG_LEXER, ("@%d ", plinno));
+ needprompt = doprompt;
+ break;
+
+ case PEOF:
+ startlinno = line1;
+ VTRACE(DBG_LEXER, ("EOF\n", plinno));
+ synerror("EOF in backquote substitution");
+ break;
+
+ default:
+ break;
+ }
+ VTRACE(DBG_LEXER, (".\n", plinno));
+ STPUTC(pc, out);
+ }
+ STPUTC('\0', out);
+ VTRACE(DBG_LEXER, ("parsebackq() ``:"));
+ VTRACE(DBG_PARSE|DBG_LEXER, (" read %d", psavelen));
+ psavelen = out - stackblock();
+ VTRACE(DBG_PARSE|DBG_LEXER, (" produced %d\n", psavelen));
+ if (psavelen > 0) {
+ pstr = grabstackstr(out);
+ CTRACE(DBG_LEXER,
+ ("parsebackq() reprocessing as $(%s)\n", pstr));
+ setinputstring(pstr, 1, line1);
+ }
+ }
+ nlpp = pbqlist;
+ while (*nlpp)
+ nlpp = &(*nlpp)->next;
+ *nlpp = stalloc(sizeof(struct nodelist));
+ (*nlpp)->next = NULL;
+ parsebackquote = oldstyle;
+
+ if (oldstyle) {
+ saveprompt = doprompt;
+ doprompt = 0;
+ } else
+ saveprompt = 0;
+
+ lno = -plinno;
+ CTRACE(DBG_LEXER, ("parsebackq() parsing embedded command list\n"));
+ n = list(0);
+ CTRACE(DBG_LEXER, ("parsebackq() parsed $() (%d -> %d)\n", -lno,
+ lno + plinno));
+ lno += plinno;
+
+ if (oldstyle) {
+ if (peektoken() != TEOF)
+ synexpect(-1, 0);
+ doprompt = saveprompt;
+ } else
+ consumetoken(TRP);
+
+ (*nlpp)->n = n;
+ if (oldstyle) {
+ /*
+ * Start reading from old file again, ignoring any pushed back
+ * tokens left from the backquote parsing
+ */
+ CTRACE(DBG_LEXER, ("parsebackq() back to previous input\n"));
+ popfile();
+ tokpushback = 0;
+ }
+
+ while (stackblocksize() <= savelen)
+ growstackblock();
+ STARTSTACKSTR(out);
+ if (str) {
+ memcpy(out, str, savelen);
+ STADJUST(savelen, out);
+ INTOFF;
+ ckfree(str);
+ sstr = str = NULL;
+ INTON;
+ }
+ parsebackquote = savepbq;
+ handler = savehandler;
+ if (arinest || ISDBLQUOTE()) {
+ STPUTC(CTLBACKQ | CTLQUOTE, out);
+ while (--lno >= 0)
+ STPUTC(CTLNONL, out);
+ } else
+ STPUTC(CTLBACKQ, out);
+
+ return out;
+}
+
+/*
+ * Parse a redirection operator. The parameter "out" points to a string
+ * specifying the fd to be redirected. It is guaranteed to be either ""
+ * or a numeric string (for now anyway). The parameter "c" contains the
+ * first character of the redirection operator.
+ *
+ * Note the string "out" is on the stack, which we are about to clobber,
+ * so process it first...
+ */
+
+static void
+parseredir(const char *out, int c)
+{
+ union node *np;
+ int fd;
+
+ fd = (*out == '\0') ? -1 : number(out);
+
+ np = stalloc(sizeof(struct nfile));
+ VTRACE(DBG_LEXER, ("parseredir after '%s%c' ", out, c));
+ if (c == '>') {
+ if (fd < 0)
+ fd = 1;
+ c = pgetc_linecont();
+ VTRACE(DBG_LEXER, ("is '%c'(%#.2x) ", c&0xFF, c&0x1FF));
+ if (c == '>')
+ np->type = NAPPEND;
+ else if (c == '|')
+ np->type = NCLOBBER;
+ else if (c == '&')
+ np->type = NTOFD;
+ else {
+ np->type = NTO;
+ VTRACE(DBG_LEXER, ("unwanted ", c));
+ pungetc();
+ }
+ } else { /* c == '<' */
+ if (fd < 0)
+ fd = 0;
+ c = pgetc_linecont();
+ VTRACE(DBG_LEXER, ("is '%c'(%#.2x) ", c&0xFF, c&0x1FF));
+ switch (c) {
+ case '<':
+ /* if sizes differ, just discard the old one */
+ if (sizeof (struct nfile) != sizeof (struct nhere))
+ np = stalloc(sizeof(struct nhere));
+ np->type = NHERE;
+ np->nhere.fd = 0;
+ heredoc = stalloc(sizeof(struct HereDoc));
+ heredoc->here = np;
+ heredoc->startline = plinno;
+ if ((c = pgetc_linecont()) == '-') {
+ CTRACE(DBG_LEXER, ("and '%c'(%#.2x) ",
+ c & 0xFF, c & 0x1FF));
+ heredoc->striptabs = 1;
+ } else {
+ heredoc->striptabs = 0;
+ pungetc();
+ }
+ break;
+
+ case '&':
+ np->type = NFROMFD;
+ break;
+
+ case '>':
+ np->type = NFROMTO;
+ break;
+
+ default:
+ np->type = NFROM;
+ VTRACE(DBG_LEXER, ("unwanted('%c'0#.2x)", c&0xFF,
+ c&0x1FF));
+ pungetc();
+ break;
+ }
+ }
+ np->nfile.fd = fd;
+
+ VTRACE(DBG_LEXER, (" ->%"PRIdsNT" fd=%d\n", NODETYPENAME(np->type),fd));
+
+ redirnode = np; /* this is the "value" of TRENODE */
+}
+
+/*
+ * Called to parse a backslash escape sequence inside $'...'.
+ * The backslash has already been read.
+ */
+static char *
+readcstyleesc(char *out)
+{
+ int c, vc, i, n;
+ unsigned int v;
+
+ c = pgetc();
+ VTRACE(DBG_LEXER, ("CSTR(\\%c)(\\%#x)", c&0xFF, c&0x1FF));
+ switch (c) {
+ case '\0':
+ case PEOF:
+ synerror("Unterminated quoted string");
+ case '\n':
+ plinno++;
+ VTRACE(DBG_LEXER, ("@%d ", plinno));
+ if (doprompt)
+ setprompt(2);
+ else
+ setprompt(0);
+ return out;
+
+ case '\\':
+ case '\'':
+ case '"':
+ v = c;
+ break;
+
+ case 'a': v = '\a'; break;
+ case 'b': v = '\b'; break;
+ case 'e': v = '\033'; break;
+ case 'f': v = '\f'; break;
+ case 'n': v = '\n'; break;
+ case 'r': v = '\r'; break;
+ case 't': v = '\t'; break;
+ case 'v': v = '\v'; break;
+
+ case '0': case '1': case '2': case '3':
+ case '4': case '5': case '6': case '7':
+ v = c - '0';
+ c = pgetc();
+ if (c >= '0' && c <= '7') {
+ v <<= 3;
+ v += c - '0';
+ c = pgetc();
+ if (c >= '0' && c <= '7') {
+ v <<= 3;
+ v += c - '0';
+ } else
+ pungetc();
+ } else
+ pungetc();
+ break;
+
+ case 'c':
+ c = pgetc();
+ if (c < 0x3f || c > 0x7a || c == 0x60)
+ synerror("Bad \\c escape sequence");
+ if (c == '\\' && pgetc() != '\\')
+ synerror("Bad \\c\\ escape sequence");
+ if (c == '?')
+ v = 127;
+ else
+ v = c & 0x1f;
+ break;
+
+ case 'x':
+ n = 2;
+ goto hexval;
+ case 'u':
+ n = 4;
+ goto hexval;
+ case 'U':
+ n = 8;
+ hexval:
+ v = 0;
+ for (i = 0; i < n; i++) {
+ c = pgetc();
+ if (c >= '0' && c <= '9')
+ v = (v << 4) + c - '0';
+ else if (c >= 'A' && c <= 'F')
+ v = (v << 4) + c - 'A' + 10;
+ else if (c >= 'a' && c <= 'f')
+ v = (v << 4) + c - 'a' + 10;
+ else {
+ pungetc();
+ break;
+ }
+ }
+ if (n > 2 && v > 127) {
+ if (v >= 0xd800 && v <= 0xdfff)
+ synerror("Invalid \\u escape sequence");
+
+ /* XXX should we use iconv here. What locale? */
+ CHECKSTRSPACE(4, out);
+
+ if (v <= 0x7ff) {
+ USTPUTC(0xc0 | v >> 6, out);
+ USTPUTC(0x80 | (v & 0x3f), out);
+ return out;
+ } else if (v <= 0xffff) {
+ USTPUTC(0xe0 | v >> 12, out);
+ USTPUTC(0x80 | ((v >> 6) & 0x3f), out);
+ USTPUTC(0x80 | (v & 0x3f), out);
+ return out;
+ } else if (v <= 0x10ffff) {
+ USTPUTC(0xf0 | v >> 18, out);
+ USTPUTC(0x80 | ((v >> 12) & 0x3f), out);
+ USTPUTC(0x80 | ((v >> 6) & 0x3f), out);
+ USTPUTC(0x80 | (v & 0x3f), out);
+ return out;
+ }
+ if (v > 127)
+ v = '?';
+ }
+ break;
+ default:
+ synerror("Unknown $'' escape sequence");
+ }
+ vc = (char)v;
+ VTRACE(DBG_LEXER, ("->%u(%#x)['%c']", v, v, vc&0xFF));
+
+ /*
+ * If we managed to create a \n from a \ sequence (no matter how)
+ * then we replace it with the magic CRTCNL control char, which
+ * will turn into a \n again later, but in the meantime, never
+ * causes LINENO increments.
+ */
+ if (vc == '\n') {
+ VTRACE(DBG_LEXER, ("CTLCNL."));
+ USTPUTC(CTLCNL, out);
+ return out;
+ }
+
+ /*
+ * We can't handle NUL bytes.
+ * POSIX says we should skip till the closing quote.
+ */
+ if (vc == '\0') {
+ CTRACE(DBG_LEXER, ("\\0: skip to '", v, v, vc&0xFF));
+ while ((c = pgetc()) != '\'') {
+ if (c == '\\')
+ c = pgetc();
+ if (c == PEOF)
+ synerror("Unterminated quoted string");
+ if (c == '\n') {
+ plinno++;
+ if (doprompt)
+ setprompt(2);
+ else
+ setprompt(0);
+ }
+ }
+ pungetc();
+ return out;
+ }
+ CVTRACE(DBG_LEXER, NEEDESC(vc), ("CTLESC-"));
+ VTRACE(DBG_LEXER, ("'%c'(%#.2x)", vc&0xFF, vc&0x1FF));
+ if (NEEDESC(vc))
+ USTPUTC(CTLESC, out);
+ USTPUTC(vc, out);
+ return out;
+}
+
+/*
+ * The lowest level basic tokenizer.
+ *
+ * The next input byte (character) is in firstc, syn says which
+ * syntax tables we are to use (basic, single or double quoted, or arith)
+ * and magicq (used with sqsyntax and dqsyntax only) indicates that the
+ * quote character itself is not special (used parsing here docs and similar)
+ *
+ * The result is the type of the next token (its value, when there is one,
+ * is saved in the relevant global var - must fix that someday!) which is
+ * also saved for re-reading ("lasttoken").
+ *
+ * Overall, this routine does far more parsing than it is supposed to.
+ * That will also need fixing, someday...
+ */
+STATIC int
+readtoken1(int firstc, char const *syn, int oneword)
+{
+ int c;
+ char * out;
+ int len;
+ struct nodelist *bqlist;
+ int quotef;
+ VSS static_stack;
+ VSS *stack = &static_stack;
+
+ stack->prev = NULL;
+ stack->cur = 0;
+
+ syntax = syn;
+
+#ifdef DEBUG
+#define SYNTAX ( syntax == BASESYNTAX ? "BASE" : \
+ syntax == DQSYNTAX ? "DQ" : \
+ syntax == SQSYNTAX ? "SQ" : \
+ syntax == ARISYNTAX ? "ARI" : \
+ "???" )
+#endif
+
+ startlinno = plinno;
+ varnest = 0;
+ quoted = 0;
+ if (syntax == DQSYNTAX)
+ SETDBLQUOTE();
+ quotef = 0;
+ bqlist = NULL;
+ arinest = 0;
+ parenlevel = 0;
+ elided_nl = 0;
+ magicq = oneword;
+
+ CTRACE(DBG_LEXER, ("readtoken1(%c) syntax=%s %s%s(quoted=%x)\n",
+ firstc&0xFF, SYNTAX, magicq ? "magic quotes" : "",
+ ISDBLQUOTE()?" ISDBLQUOTE":"", quoted));
+
+ STARTSTACKSTR(out);
+
+ for (c = firstc ;; c = pgetc_macro()) { /* until of token */
+ if (syntax == ARISYNTAX)
+ out = insert_elided_nl(out);
+ CHECKSTRSPACE(6, out); /* permit 6 calls to USTPUTC */
+ switch (syntax[c]) {
+ case CFAKE:
+ VTRACE(DBG_LEXER, ("CFAKE"));
+ if (syntax == BASESYNTAX && varnest == 0)
+ break;
+ VTRACE(DBG_LEXER, (","));
+ continue;
+ case CNL: /* '\n' */
+ VTRACE(DBG_LEXER, ("CNL"));
+ if (syntax == BASESYNTAX && varnest == 0)
+ break; /* exit loop */
+ USTPUTC(c, out);
+ plinno++;
+ VTRACE(DBG_LEXER, ("@%d,", plinno));
+ if (doprompt)
+ setprompt(2);
+ else
+ setprompt(0);
+ continue;
+
+ case CSBACK: /* single quoted backslash */
+ if ((quoted & QF) == CQ) {
+ out = readcstyleesc(out);
+ continue;
+ }
+ VTRACE(DBG_LEXER, ("ESC:"));
+ USTPUTC(CTLESC, out);
+ /* FALLTHROUGH */
+ case CWORD:
+ VTRACE(DBG_LEXER, ("'%c'", c));
+ USTPUTC(c, out);
+ continue;
+
+ case CCTL:
+ CVTRACE(DBG_LEXER, !magicq || ISDBLQUOTE(),
+ ("%s%sESC:",!magicq?"!m":"",ISDBLQUOTE()?"DQ":""));
+ if (!magicq || ISDBLQUOTE())
+ USTPUTC(CTLESC, out);
+ VTRACE(DBG_LEXER, ("'%c'", c));
+ USTPUTC(c, out);
+ continue;
+ case CBACK: /* backslash */
+ c = pgetc();
+ VTRACE(DBG_LEXER, ("\\'%c'(%#.2x)", c&0xFF, c&0x1FF));
+ if (c == PEOF) {
+ VTRACE(DBG_LEXER, ("EOF, keep \\ "));
+ USTPUTC('\\', out);
+ pungetc();
+ continue;
+ }
+ if (c == '\n') {
+ plinno++;
+ elided_nl++;
+ VTRACE(DBG_LEXER, ("eli \\n (%d) @%d ",
+ elided_nl, plinno));
+ if (doprompt)
+ setprompt(2);
+ else
+ setprompt(0);
+ continue;
+ }
+ CVTRACE(DBG_LEXER, quotef==0, (" QF=1 "));
+ quotef = 1; /* current token is quoted */
+ if (quoted && c != '\\' && c != '`' &&
+ c != '$' && (c != '"' || magicq)) {
+ /*
+ * retain the \ (which we *know* needs CTLESC)
+ * when in "..." and the following char is
+ * not one of the magic few.)
+ * Otherwise the \ has done its work, and
+ * is dropped.
+ */
+ VTRACE(DBG_LEXER, ("ESC:'\\'"));
+ USTPUTC(CTLESC, out);
+ USTPUTC('\\', out);
+ }
+ CVTRACE(DBG_LEXER, NEEDESC(c) || !magicq,
+ ("%sESC:", NEEDESC(c) ? "+" : "m"));
+ VTRACE(DBG_LEXER, ("'%c'(%#.2x)", c&0xFF, c&0x1FF));
+ if (NEEDESC(c))
+ USTPUTC(CTLESC, out);
+ else if (!magicq) {
+ USTPUTC(CTLESC, out);
+ USTPUTC(c, out);
+ continue;
+ }
+ USTPUTC(c, out);
+ continue;
+ case CSQUOTE:
+ if (syntax != SQSYNTAX) {
+ CVTRACE(DBG_LEXER, !magicq, (" CQM "));
+ if (!magicq)
+ USTPUTC(CTLQUOTEMARK, out);
+ CVTRACE(DBG_LEXER, quotef==0, (" QF=1 "));
+ quotef = 1;
+ TS_PUSH();
+ syntax = SQSYNTAX;
+ quoted = SQ;
+ VTRACE(DBG_LEXER, (" TS_PUSH(SQ)"));
+ continue;
+ }
+ if (magicq && arinest == 0 && varnest == 0) {
+ /* Ignore inside quoted here document */
+ VTRACE(DBG_LEXER, ("<<'>>"));
+ USTPUTC(c, out);
+ continue;
+ }
+ /* End of single quotes... */
+ TS_POP();
+ VTRACE(DBG_LEXER, ("SQ TS_POP->%s ", SYNTAX));
+ CVTRACE(DBG_LEXER, syntax == BASESYNTAX, (" CQE "));
+ if (syntax == BASESYNTAX)
+ USTPUTC(CTLQUOTEEND, out);
+ continue;
+ case CDQUOTE:
+ if (magicq && arinest == 0 /* && varnest == 0 */) {
+ VTRACE(DBG_LEXER, ("<<\">>"));
+ /* Ignore inside here document */
+ USTPUTC(c, out);
+ continue;
+ }
+ CVTRACE(DBG_LEXER, quotef==0, (" QF=1 "));
+ quotef = 1;
+ if (arinest) {
+ if (ISDBLQUOTE()) {
+ VTRACE(DBG_LEXER,
+ (" CQE ari(%d", arinest));
+ USTPUTC(CTLQUOTEEND, out);
+ TS_POP();
+ VTRACE(DBG_LEXER, ("%d)TS_POP->%s ",
+ arinest, SYNTAX));
+ } else {
+ VTRACE(DBG_LEXER,
+ (" ari(%d) %s TS_PUSH->DQ CQM ",
+ arinest, SYNTAX));
+ TS_PUSH();
+ syntax = DQSYNTAX;
+ SETDBLQUOTE();
+ USTPUTC(CTLQUOTEMARK, out);
+ }
+ continue;
+ }
+ CVTRACE(DBG_LEXER, magicq, (" MQignDQ "));
+ if (magicq)
+ continue;
+ if (ISDBLQUOTE()) {
+ TS_POP();
+ VTRACE(DBG_LEXER,
+ (" DQ TS_POP->%s CQE ", SYNTAX));
+ USTPUTC(CTLQUOTEEND, out);
+ } else {
+ VTRACE(DBG_LEXER,
+ (" %s TS_POP->DQ CQM ", SYNTAX));
+ TS_PUSH();
+ syntax = DQSYNTAX;
+ SETDBLQUOTE();
+ USTPUTC(CTLQUOTEMARK, out);
+ }
+ continue;
+ case CVAR: /* '$' */
+ VTRACE(DBG_LEXER, ("'$'..."));
+ out = insert_elided_nl(out);
+ PARSESUB(); /* parse substitution */
+ continue;
+ case CENDVAR: /* CLOSEBRACE */
+ if (varnest > 0 && !ISDBLQUOTE()) {
+ VTRACE(DBG_LEXER, ("vn=%d !DQ", varnest));
+ TS_POP();
+ VTRACE(DBG_LEXER, (" TS_POP->%s CEV ", SYNTAX));
+ USTPUTC(CTLENDVAR, out);
+ } else {
+ VTRACE(DBG_LEXER, ("'%c'", c));
+ USTPUTC(c, out);
+ }
+ out = insert_elided_nl(out);
+ continue;
+ case CLP: /* '(' in arithmetic */
+ parenlevel++;
+ VTRACE(DBG_LEXER, ("'('(%d)", parenlevel));
+ USTPUTC(c, out);
+ continue;;
+ case CRP: /* ')' in arithmetic */
+ if (parenlevel > 0) {
+ USTPUTC(c, out);
+ --parenlevel;
+ VTRACE(DBG_LEXER, ("')'(%d)", parenlevel));
+ } else {
+ VTRACE(DBG_LEXER, ("')'(%d)", parenlevel));
+ if (pgetc_linecont() == /*(*/ ')') {
+ out = insert_elided_nl(out);
+ if (--arinest == 0) {
+ TS_POP();
+ USTPUTC(CTLENDARI, out);
+ } else
+ USTPUTC(/*(*/ ')', out);
+ } else {
+ break; /* to synerror() just below */
+#if 0 /* the old way, causes weird errors on bad input */
+ /*
+ * unbalanced parens
+ * (don't 2nd guess - no error)
+ */
+ pungetc();
+ USTPUTC(/*(*/ ')', out);
+#endif
+ }
+ }
+ continue;
+ case CBQUOTE: /* '`' */
+ VTRACE(DBG_LEXER, ("'`' -> parsebackq()\n"));
+ out = parsebackq(stack, out, &bqlist, 1);
+ VTRACE(DBG_LEXER, ("parsebackq() -> readtoken1: "));
+ continue;
+ case CEOF: /* --> c == PEOF */
+ VTRACE(DBG_LEXER, ("EOF "));
+ break; /* will exit loop */
+ default:
+ VTRACE(DBG_LEXER, ("['%c'(%#.2x)]", c&0xFF, c&0x1FF));
+ if (varnest == 0 && !ISDBLQUOTE())
+ break; /* exit loop */
+ USTPUTC(c, out);
+ VTRACE(DBG_LEXER, (","));
+ continue;
+ }
+ VTRACE(DBG_LEXER, (" END TOKEN\n", c&0xFF, c&0x1FF));
+ break; /* break from switch -> break from for loop too */
+ }
+
+ if (syntax == ARISYNTAX) {
+ cleanup_state_stack(stack);
+ synerror(/*((*/ "Missing '))'");
+ }
+ if (syntax != BASESYNTAX && /* ! parsebackquote && */ !magicq) {
+ cleanup_state_stack(stack);
+ synerror("Unterminated quoted string");
+ }
+ if (varnest != 0) {
+ cleanup_state_stack(stack);
+ startlinno = plinno;
+ /* { */
+ synerror("Missing '}'");
+ }
+
+ STPUTC('\0', out);
+ len = out - stackblock();
+ out = stackblock();
+
+ if (!magicq) {
+ if ((c == '<' || c == '>')
+ && quotef == 0 && (*out == '\0' || is_number(out))) {
+ parseredir(out, c);
+ cleanup_state_stack(stack);
+ return lasttoken = TREDIR;
+ } else {
+ pungetc();
+ }
+ }
+
+ VTRACE(DBG_PARSE|DBG_LEXER,
+ ("readtoken1 %sword \"%s\", completed%s (%d) left %d enl\n",
+ (quotef ? "quoted " : ""), out, (bqlist ? " with cmdsubs" : ""),
+ len, elided_nl));
+
+ quoteflag = quotef;
+ backquotelist = bqlist;
+ grabstackblock(len);
+ wordtext = out;
+ cleanup_state_stack(stack);
+ return lasttoken = TWORD;
+/* end of readtoken routine */
+
+
+/*
+ * Parse a substitution. At this point, we have read the dollar sign
+ * and nothing else.
+ */
+
+parsesub: {
+ int subtype;
+ int typeloc;
+ int flags;
+ char *p;
+ static const char types[] = "}-+?=";
+
+ c = pgetc_linecont();
+ VTRACE(DBG_LEXER, ("\"$%c\"(%#.2x)", c&0xFF, c&0x1FF));
+ if (c == '(' /*)*/) { /* $(command) or $((arith)) */
+ if (pgetc_linecont() == '(' /*')'*/ ) {
+ VTRACE(DBG_LEXER, ("\"$((\" ARITH "));
+ out = insert_elided_nl(out);
+ PARSEARITH();
+ } else {
+ VTRACE(DBG_LEXER, ("\"$(\" CSUB->parsebackq()\n"));
+ out = insert_elided_nl(out);
+ pungetc();
+ out = parsebackq(stack, out, &bqlist, 0);
+ VTRACE(DBG_LEXER, ("parseback()->readtoken1(): "));
+ }
+ } else if (c == OPENBRACE || is_name(c) || is_special(c)) {
+ VTRACE(DBG_LEXER, (" $EXP:CTLVAR "));
+ USTPUTC(CTLVAR, out);
+ typeloc = out - stackblock();
+ USTPUTC(VSNORMAL, out);
+ subtype = VSNORMAL;
+ flags = 0;
+ if (c == OPENBRACE) {
+ c = pgetc_linecont();
+ if (c == '#') {
+ if ((c = pgetc_linecont()) == CLOSEBRACE)
+ c = '#';
+ else if (is_name(c) || isdigit(c))
+ subtype = VSLENGTH;
+ else if (is_special(c)) {
+ /*
+ * ${#} is $# - the number of sh params
+ * ${##} is the length of ${#}
+ * ${###} is ${#} with as much nothing
+ * as possible removed from start
+ * ${##1} is ${#} with leading 1 gone
+ * ${##\#} is ${#} with leading # gone
+ *
+ * this stuff is UGLY!
+ */
+ if (pgetc_linecont() == CLOSEBRACE) {
+ pungetc();
+ subtype = VSLENGTH;
+ } else {
+ static char cbuf[2];
+
+ pungetc(); /* would like 2 */
+ cbuf[0] = c; /* so ... */
+ cbuf[1] = '\0';
+ pushstring(cbuf, 1, NULL);
+ c = '#'; /* ${#:...} */
+ subtype = 0; /* .. or similar */
+ }
+ } else {
+ pungetc();
+ c = '#';
+ subtype = 0;
+ }
+ }
+ else
+ subtype = 0;
+ VTRACE(DBG_LEXER, ("${ st=%d ", subtype));
+ }
+ if (is_name(c)) {
+ p = out;
+ do {
+ VTRACE(DBG_LEXER, ("%c", c));
+ STPUTC(c, out);
+ c = pgetc_linecont();
+ } while (is_in_name(c));
+
+#if 0
+ if (out - p == 6 && strncmp(p, "LINENO", 6) == 0) {
+ int i;
+ int linno;
+ char buf[10];
+
+ /*
+ * The "LINENO hack"
+ *
+ * Replace the variable name with the
+ * current line number.
+ */
+ linno = plinno;
+ if (funclinno != 0)
+ linno -= funclinno - 1;
+ snprintf(buf, sizeof(buf), "%d", linno);
+ STADJUST(-6, out);
+ for (i = 0; buf[i] != '\0'; i++)
+ STPUTC(buf[i], out);
+ flags |= VSLINENO;
+ }
+#endif
+ } else if (is_digit(c)) {
+ do {
+ VTRACE(DBG_LEXER, ("%c", c));
+ STPUTC(c, out);
+ c = pgetc_linecont();
+ } while (subtype != VSNORMAL && is_digit(c));
+ }
+ else if (is_special(c)) {
+ VTRACE(DBG_LEXER, ("\"$%c", c));
+ USTPUTC(c, out);
+ c = pgetc_linecont();
+ }
+ else {
+ VTRACE(DBG_LEXER, ("\"$%c(%#.2x)??\n", c&0xFF,c&0x1FF));
+ badsub:
+ cleanup_state_stack(stack);
+ synerror("Bad substitution");
+ }
+
+ STPUTC('=', out);
+ if (subtype == 0) {
+ switch (c) {
+ case ':':
+ flags |= VSNUL;
+ c = pgetc_linecont();
+ /*FALLTHROUGH*/
+ default:
+ p = strchr(types, c);
+ if (p == NULL)
+ goto badsub;
+ subtype = p - types + VSNORMAL;
+ break;
+ case '%':
+ case '#':
+ {
+ int cc = c;
+ subtype = c == '#' ? VSTRIMLEFT :
+ VSTRIMRIGHT;
+ c = pgetc_linecont();
+ if (c == cc)
+ subtype++;
+ else
+ pungetc();
+ break;
+ }
+ }
+ } else {
+ if (subtype == VSLENGTH && c != /*{*/ '}')
+ synerror("no modifiers allowed with ${#var}");
+ pungetc();
+ }
+ if (quoted || arinest)
+ flags |= VSQUOTE;
+ if (subtype >= VSTRIMLEFT && subtype <= VSTRIMRIGHTMAX)
+ flags |= VSPATQ;
+ VTRACE(DBG_LEXER, (" st%d:%x", subtype, flags));
+ *(stackblock() + typeloc) = subtype | flags;
+ if (subtype != VSNORMAL) {
+ TS_PUSH();
+ varnest++;
+ arinest = 0;
+ if (subtype > VSASSIGN) { /* # ## % %% */
+ syntax = BASESYNTAX;
+ quoted = 0;
+ magicq = 0;
+ }
+ VTRACE(DBG_LEXER, (" TS_PUSH->%s vn=%d%s ",
+ SYNTAX, varnest, quoted ? " Q" : ""));
+ }
+ } else if (c == '\'' && syntax == BASESYNTAX) {
+ USTPUTC(CTLQUOTEMARK, out);
+ VTRACE(DBG_LEXER, (" CSTR \"$'\" CQM "));
+ CVTRACE(DBG_LEXER, quotef==0, ("QF=1 "));
+ quotef = 1;
+ TS_PUSH();
+ syntax = SQSYNTAX;
+ quoted = CQ;
+ VTRACE(DBG_LEXER, ("%s->TS_PUSH()->SQ ", SYNTAX));
+ } else {
+ VTRACE(DBG_LEXER, ("$unk -> '$' (pushback '%c'%#.2x)",
+ c & 0xFF, c & 0x1FF));
+ USTPUTC('$', out);
+ pungetc();
+ }
+ goto parsesub_return;
+}
+
+
+/*
+ * Parse an arithmetic expansion (indicate start of one and set state)
+ */
+parsearith: {
+
+#if 0
+ if (syntax == ARISYNTAX) {
+ /*
+ * we collapse embedded arithmetic expansion to
+ * parentheses, which should be equivalent
+ *
+ * XXX It isn't, must fix, soonish...
+ */
+ USTPUTC('(' /*)*/, out);
+ USTPUTC('(' /*)*/, out);
+ /*
+ * Need 2 of them because there will (should be)
+ * two closing ))'s to follow later.
+ */
+ parenlevel += 2;
+ } else
+#endif
+ {
+ VTRACE(DBG_LEXER, (" CTLARI%c ", ISDBLQUOTE()?'"':'_'));
+ USTPUTC(CTLARI, out);
+ if (ISDBLQUOTE())
+ USTPUTC('"',out);
+ else
+ USTPUTC(' ',out);
+
+ VTRACE(DBG_LEXER, ("%s->TS_PUSH->ARI(1)", SYNTAX));
+ TS_PUSH();
+ syntax = ARISYNTAX;
+ arinest = 1;
+ varnest = 0;
+ magicq = 1;
+ }
+ goto parsearith_return;
+}
+
+} /* end of readtoken */
+
+
+
+
+#ifdef mkinit
+INCLUDE "parser.h"
+
+RESET {
+ psp.v_current_parser = &parse_state;
+
+ parse_state.ps_tokpushback = 0;
+ parse_state.ps_checkkwd = 0;
+ parse_state.ps_heredoclist = NULL;
+}
+#endif
+
+/*
+ * Returns true if the text contains nothing to expand (no dollar signs
+ * or backquotes).
+ */
+
+STATIC int
+noexpand(char *text)
+{
+ char *p;
+ char c;
+
+ p = text;
+ while ((c = *p++) != '\0') {
+ if (c == CTLQUOTEMARK || c == CTLQUOTEEND)
+ continue;
+ if (c == CTLESC)
+ p++;
+ else if (BASESYNTAX[(int)c] == CCTL)
+ return 0;
+ }
+ return 1;
+}
+
+
+/*
+ * Return true if the argument is a legal variable name (a letter or
+ * underscore followed by zero or more letters, underscores, and digits).
+ */
+
+int
+goodname(const char *name)
+{
+ const char *p;
+
+ p = name;
+ if (! is_name(*p))
+ return 0;
+ while (*++p) {
+ if (! is_in_name(*p))
+ return 0;
+ }
+ return 1;
+}
+
+int
+isassignment(const char *p)
+{
+ if (!is_name(*p))
+ return 0;
+ while (*++p != '=')
+ if (*p == '\0' || !is_in_name(*p))
+ return 0;
+ return 1;
+}
+
+/*
+ * skip past any \n's, and leave lasttoken set to whatever follows
+ */
+STATIC void
+linebreak(void)
+{
+ while (readtoken() == TNL)
+ ;
+}
+
+/*
+ * The next token must be "token" -- check, then move past it
+ */
+STATIC void
+consumetoken(int token)
+{
+ if (readtoken() != token) {
+ VTRACE(DBG_PARSE, ("consumetoken(%d): expecting %s got %s",
+ token, tokname[token], tokname[lasttoken]));
+ CVTRACE(DBG_PARSE, (lasttoken==TWORD), (" \"%s\"", wordtext));
+ VTRACE(DBG_PARSE, ("\n"));
+ synexpect(token, NULL);
+ }
+}
+
+/*
+ * Called when an unexpected token is read during the parse. The argument
+ * is the token that is expected, or -1 if more than one type of token can
+ * occur at this point.
+ */
+
+STATIC void
+synexpect(int token, const char *text)
+{
+ char msg[64];
+ char *p;
+
+ if (lasttoken == TWORD) {
+ size_t len = strlen(wordtext);
+
+ if (len <= 13)
+ fmtstr(msg, 34, "Word \"%.13s\" unexpected", wordtext);
+ else
+ fmtstr(msg, 34,
+ "Word \"%.10s...\" unexpected", wordtext);
+ } else
+ fmtstr(msg, 34, "%s unexpected", tokname[lasttoken]);
+
+ p = strchr(msg, '\0');
+ if (text)
+ fmtstr(p, 30, " (expecting \"%.10s\")", text);
+ else if (token >= 0)
+ fmtstr(p, 30, " (expecting %s)", tokname[token]);
+
+ synerror(msg);
+ /* NOTREACHED */
+}
+
+
+STATIC void
+synerror(const char *msg)
+{
+ error("%d: Syntax error: %s", startlinno, msg);
+ /* NOTREACHED */
+}
+
+STATIC void
+setprompt(int which)
+{
+ whichprompt = which;
+
+#ifndef SMALL
+ if (!el)
+#endif
+ out2str(getprompt(NULL));
+}
+
+/*
+ * handle getting the next character, while ignoring \ \n
+ * (which is a little tricky as we only have one char of pushback
+ * and we need that one elsewhere).
+ */
+STATIC int
+pgetc_linecont(void)
+{
+ int c;
+
+ while ((c = pgetc()) == '\\') {
+ c = pgetc();
+ if (c == '\n') {
+ plinno++;
+ elided_nl++;
+ VTRACE(DBG_LEXER, ("\"\\n\"drop(el=%d@%d)",
+ elided_nl, plinno));
+ if (doprompt)
+ setprompt(2);
+ else
+ setprompt(0);
+ } else {
+ pungetc();
+ /* Allow the backslash to be pushed back. */
+ pushstring("\\", 1, NULL);
+ return (pgetc());
+ }
+ }
+ return (c);
+}
+
+/*
+ * called by editline -- any expansions to the prompt
+ * should be added here.
+ */
+const char *
+getprompt(void *unused)
+{
+ char *p;
+ const char *cp;
+ int wp;
+
+ if (!doprompt)
+ return "";
+
+ VTRACE(DBG_PARSE|DBG_EXPAND, ("getprompt %d\n", whichprompt));
+
+ switch (wp = whichprompt) {
+ case 0:
+ return "";
+ case 1:
+ p = ps1val();
+ break;
+ case 2:
+ p = ps2val();
+ break;
+ default:
+ return "<internal prompt error>";
+ }
+ if (p == NULL)
+ return "";
+
+ VTRACE(DBG_PARSE|DBG_EXPAND, ("prompt <<%s>>\n", p));
+
+ cp = expandstr(p, plinno);
+ whichprompt = wp; /* history depends on it not changing */
+
+ VTRACE(DBG_PARSE|DBG_EXPAND, ("prompt -> <<%s>>\n", cp));
+
+ return cp;
+}
+
+/*
+ * Expand a string ... used for expanding prompts (PS1...)
+ *
+ * Never return NULL, always some string (return input string if invalid)
+ *
+ * The internal routine does the work, leaving the result on the
+ * stack (or in a static string, or even the input string) and
+ * handles parser recursion, and cleanup after an error while parsing.
+ *
+ * The visible interface copies the result off the stack (if it is there),
+ * and handles stack management, leaving the stack in the exact same
+ * state it was when expandstr() was called (so it can be used part way
+ * through building a stack data structure - as in when PS2 is being
+ * expanded half way through reading a "command line")
+ *
+ * on error, expandonstack() cleans up the parser state, but then
+ * simply jumps out through expandstr() withut doing any stack cleanup,
+ * which is OK, as the error handler must deal with that anyway.
+ *
+ * The split into two funcs is to avoid problems with setjmp/longjmp
+ * and local variables which could otherwise be optimised into bizarre
+ * behaviour.
+ */
+static const char *
+expandonstack(char *ps, int cmdsub, int lineno)
+{
+ union node n;
+ struct jmploc jmploc;
+ struct jmploc *const savehandler = handler;
+ struct parsefile *const savetopfile = getcurrentfile();
+ const int save_x = xflag;
+ struct parse_state new_state = init_parse_state;
+ struct parse_state *const saveparser = psp.v_current_parser;
+ const char *result = NULL;
+
+ if (!setjmp(jmploc.loc)) {
+ handler = &jmploc;
+
+ psp.v_current_parser = &new_state;
+ setinputstring(ps, 1, lineno);
+
+ readtoken1(pgetc(), DQSYNTAX, 1);
+ if (backquotelist != NULL) {
+ if (!cmdsub)
+ result = ps;
+ else if (!promptcmds)
+ result = "-o promptcmds not set: ";
+ }
+ if (result == NULL) {
+ n.narg.type = NARG;
+ n.narg.next = NULL;
+ n.narg.text = wordtext;
+ n.narg.lineno = lineno;
+ n.narg.backquote = backquotelist;
+
+ xflag = 0; /* we might be expanding PS4 ... */
+ expandarg(&n, NULL, 0);
+ result = stackblock();
+ }
+ } else {
+ psp.v_current_parser = saveparser;
+ xflag = save_x;
+ popfilesupto(savetopfile);
+ handler = savehandler;
+
+ if (exception == EXEXIT)
+ longjmp(handler->loc, 1);
+ if (exception == EXINT)
+ exraise(SIGINT);
+ return ps;
+ }
+ psp.v_current_parser = saveparser;
+ xflag = save_x;
+ popfilesupto(savetopfile);
+ handler = savehandler;
+
+
+ if (result == NULL)
+ result = ps;
+
+ return result;
+}
+
+const char *
+expandstr(char *ps, int lineno)
+{
+ const char *result = NULL;
+ struct stackmark smark;
+ static char *buffer = NULL; /* storage for prompt, never freed */
+ static size_t bufferlen = 0;
+
+ setstackmark(&smark);
+ /*
+ * At this point we anticipate that there may be a string
+ * growing on the stack, but we have no idea how big it is.
+ * However we know that it cannot be bigger than the current
+ * allocated stack block, so simply reserve the whole thing,
+ * then we can use the stack without barfing all over what
+ * is there already... (the stack mark undoes this later.)
+ */
+ (void) stalloc(stackblocksize());
+
+ result = expandonstack(ps, 1, lineno);
+
+ if (__predict_true(result == stackblock())) {
+ size_t len = strlen(result) + 1;
+
+ /*
+ * the result (usual case) is on the stack, which we
+ * are just about to discard (popstackmark()) so we
+ * need to move it somewhere safe first.
+ */
+
+ if (__predict_false(len > bufferlen)) {
+ char *new;
+ size_t newlen = bufferlen;
+
+ if (__predict_false(len > (SIZE_MAX >> 4))) {
+ result = "huge prompt: ";
+ goto getout;
+ }
+
+ if (newlen == 0)
+ newlen = 32;
+ while (newlen <= len)
+ newlen <<= 1;
+
+ new = (char *)realloc(buffer, newlen);
+
+ if (__predict_false(new == NULL)) {
+ /*
+ * this should rarely (if ever) happen
+ * but we must do something when it does...
+ */
+ result = "No mem for prompt: ";
+ goto getout;
+ } else {
+ buffer = new;
+ bufferlen = newlen;
+ }
+ }
+ (void)memcpy(buffer, result, len);
+ result = buffer;
+ }
+
+ getout:;
+ popstackmark(&smark);
+
+ return result;
+}
+
+/*
+ * and a simpler version, which does no $( ) expansions, for
+ * use during shell startup when we know we are not parsing,
+ * and so the stack is not in use - we can do what we like,
+ * and do not need to clean up (that's handled externally).
+ *
+ * Simply return the result, even if it is on the stack
+ */
+const char *
+expandenv(char *arg)
+{
+ return expandonstack(arg, 0, 0);
+}
diff --git a/bin/sh/parser.h b/bin/sh/parser.h
new file mode 100644
index 0000000..7545b4f
--- /dev/null
+++ b/bin/sh/parser.h
@@ -0,0 +1,169 @@
+/* $NetBSD: parser.h,v 1.27 2018/12/11 13:31:20 kre Exp $ */
+
+/*-
+ * Copyright (c) 1991, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Kenneth Almquist.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * @(#)parser.h 8.3 (Berkeley) 5/4/95
+ */
+
+/* control characters in argument strings */
+#define CTL_FIRST '\201' /* first 'special' character */
+#define CTLESC '\201' /* escape next character */
+#define CTLVAR '\202' /* variable defn */
+#define CTLENDVAR '\203'
+#define CTLBACKQ '\204'
+#define CTLQUOTE 01 /* ored with CTLBACKQ code if in quotes */
+/* CTLBACKQ | CTLQUOTE == '\205' */
+#define CTLARI '\206' /* arithmetic expression */
+#define CTLENDARI '\207'
+#define CTLQUOTEMARK '\210'
+#define CTLQUOTEEND '\211' /* only inside ${...} */
+#define CTLNONL '\212' /* The \n in a deleted \ \n sequence */
+ /* pure concidence that (CTLNONL & 0x7f) == '\n' */
+#define CTLCNL '\213' /* A $'\n' - newline not counted */
+#define CTL_LAST '\213' /* last 'special' character */
+
+/* variable substitution byte (follows CTLVAR) */
+#define VSTYPE 0x0f /* type of variable substitution */
+#define VSNUL 0x10 /* colon--treat the empty string as unset */
+#define VSLINENO 0x20 /* expansion of $LINENO, the line number
+ follows immediately */
+#define VSPATQ 0x40 /* ensure correct pattern quoting in ${x#pat} */
+#define VSQUOTE 0x80 /* inside double quotes--suppress splitting */
+
+/* values of VSTYPE field */
+#define VSNORMAL 0x1 /* normal variable: $var or ${var} */
+#define VSMINUS 0x2 /* ${var-text} */
+#define VSPLUS 0x3 /* ${var+text} */
+#define VSQUESTION 0x4 /* ${var?message} */
+#define VSASSIGN 0x5 /* ${var=text} */
+#define VSTRIMLEFT 0x6 /* ${var#pattern} */
+#define VSTRIMLEFTMAX 0x7 /* ${var##pattern} */
+#define VSTRIMRIGHT 0x8 /* ${var%pattern} */
+#define VSTRIMRIGHTMAX 0x9 /* ${var%%pattern} */
+#define VSLENGTH 0xa /* ${#var} */
+
+union node *parsecmd(int);
+void fixredir(union node *, const char *, int);
+int goodname(const char *);
+int isassignment(const char *);
+const char *getprompt(void *);
+const char *expandstr(char *, int);
+const char *expandenv(char *);
+
+struct HereDoc;
+union node;
+struct nodelist;
+
+struct parse_state {
+ struct HereDoc *ps_heredoclist; /* list of here documents to read */
+ int ps_parsebackquote; /* nonzero inside backquotes */
+ int ps_doprompt; /* if set, prompt the user */
+ int ps_needprompt; /* true if interactive at line start */
+ int ps_lasttoken; /* last token read */
+ int ps_tokpushback; /* last token pushed back */
+ char *ps_wordtext; /* text of last word returned by readtoken */
+ int ps_checkkwd; /* word expansion flags, see below */
+ struct nodelist *ps_backquotelist; /* list of cmdsubs to process */
+ union node *ps_redirnode; /* node for current redirect */
+ struct HereDoc *ps_heredoc; /* current heredoc << beign parsed */
+ int ps_quoteflag; /* set if (part) of token was quoted */
+ int ps_startlinno; /* line # where last token started */
+ int ps_funclinno; /* line # of the current function */
+ int ps_elided_nl; /* count of \ \n pairs we have seen */
+};
+
+/*
+ * The parser references the elements of struct parse_state quite
+ * frequently - they used to be simple globals, so one memory ref
+ * per access, adding an indirect through global ptr would not be
+ * nice. The following gross hack allows most of that cost to be
+ * avoided, by allowing the compiler to understand that the global
+ * pointer is in fact constant in any function, and so its value can
+ * be cached, rather than needing to be fetched every time in case
+ * some other called function has changed it.
+ *
+ * The rule to make this work is that any function that wants
+ * to alter the global must restore it before it returns (and thus
+ * must have an error trap handler). That means that the struct
+ * used for the new parser state can be a local in that function's
+ * stack frame, it never needs to be malloc'd.
+ */
+
+union parse_state_p {
+ struct parse_state *const c_current_parser;
+ struct parse_state * v_current_parser;
+};
+
+extern union parse_state_p psp;
+
+#define current_parser (psp.c_current_parser)
+
+/*
+ * Perhaps one day emulate "static" by moving most of these definitions into
+ * parser.c ... (only checkkwd & tokpushback are used outside parser.c,
+ * and only in init.c as a RESET activity)
+ */
+#define tokpushback (current_parser->ps_tokpushback)
+#define checkkwd (current_parser->ps_checkkwd)
+
+#define noalias (current_parser->ps_noalias)
+#define heredoclist (current_parser->ps_heredoclist)
+#define parsebackquote (current_parser->ps_parsebackquote)
+#define doprompt (current_parser->ps_doprompt)
+#define needprompt (current_parser->ps_needprompt)
+#define lasttoken (current_parser->ps_lasttoken)
+#define wordtext (current_parser->ps_wordtext)
+#define backquotelist (current_parser->ps_backquotelist)
+#define redirnode (current_parser->ps_redirnode)
+#define heredoc (current_parser->ps_heredoc)
+#define quoteflag (current_parser->ps_quoteflag)
+#define startlinno (current_parser->ps_startlinno)
+#define funclinno (current_parser->ps_funclinno)
+#define elided_nl (current_parser->ps_elided_nl)
+
+/*
+ * Values that can be set in checkkwd
+ */
+#define CHKKWD 0x01 /* turn word into keyword (if it is) */
+#define CHKNL 0x02 /* ignore leading \n's */
+#define CHKALIAS 0x04 /* lookup words as aliases and ... */
+
+/*
+ * NEOF is returned by parsecmd when it encounters an end of file. It
+ * must be distinct from NULL, so we use the address of a variable that
+ * happens to be handy.
+ */
+#define NEOF ((union node *)&psp)
+
+#ifdef DEBUG
+extern int parsing;
+#endif
diff --git a/bin/sh/redir.c b/bin/sh/redir.c
new file mode 100644
index 0000000..751cce7
--- /dev/null
+++ b/bin/sh/redir.c
@@ -0,0 +1,982 @@
+/* $NetBSD: redir.c,v 1.62 2018/11/26 20:03:39 kamil Exp $ */
+
+/*-
+ * Copyright (c) 1991, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Kenneth Almquist.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+#ifndef lint
+#if 0
+static char sccsid[] = "@(#)redir.c 8.2 (Berkeley) 5/4/95";
+#else
+__RCSID("$NetBSD: redir.c,v 1.62 2018/11/26 20:03:39 kamil Exp $");
+#endif
+#endif /* not lint */
+
+#include <sys/types.h>
+#include <sys/param.h> /* PIPE_BUF */
+#include <sys/stat.h>
+#include <signal.h>
+#include <string.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <unistd.h>
+#include <stdlib.h>
+
+/*
+ * Code for dealing with input/output redirection.
+ */
+
+#include "main.h"
+#include "builtins.h"
+#include "shell.h"
+#include "nodes.h"
+#include "jobs.h"
+#include "options.h"
+#include "expand.h"
+#include "redir.h"
+#include "output.h"
+#include "memalloc.h"
+#include "mystring.h"
+#include "error.h"
+#include "show.h"
+
+
+#define EMPTY -2 /* marks an unused slot in redirtab */
+#define CLOSED -1 /* fd was not open before redir */
+#ifndef PIPE_BUF
+# define PIPESIZE 4096 /* amount of buffering in a pipe */
+#else
+# define PIPESIZE PIPE_BUF
+#endif
+
+
+MKINIT
+struct renamelist {
+ struct renamelist *next;
+ int orig;
+ int into;
+};
+
+MKINIT
+struct redirtab {
+ struct redirtab *next;
+ struct renamelist *renamed;
+};
+
+
+MKINIT struct redirtab *redirlist;
+
+/*
+ * We keep track of whether or not fd0 has been redirected. This is for
+ * background commands, where we want to redirect fd0 to /dev/null only
+ * if it hasn't already been redirected.
+ */
+STATIC int fd0_redirected = 0;
+
+/*
+ * And also where to put internal use fds that should be out of the
+ * way of user defined fds (normally)
+ */
+STATIC int big_sh_fd = 0;
+
+STATIC const struct renamelist *is_renamed(const struct renamelist *, int);
+STATIC void fd_rename(struct redirtab *, int, int);
+STATIC void free_rl(struct redirtab *, int);
+STATIC void openredirect(union node *, char[10], int);
+STATIC int openhere(const union node *);
+STATIC int copyfd(int, int, int);
+STATIC void find_big_fd(void);
+
+
+struct shell_fds { /* keep track of internal shell fds */
+ struct shell_fds *nxt;
+ void (*cb)(int, int);
+ int fd;
+};
+
+STATIC struct shell_fds *sh_fd_list;
+
+STATIC void renumber_sh_fd(struct shell_fds *);
+STATIC struct shell_fds *sh_fd(int);
+
+STATIC const struct renamelist *
+is_renamed(const struct renamelist *rl, int fd)
+{
+ while (rl != NULL) {
+ if (rl->orig == fd)
+ return rl;
+ rl = rl->next;
+ }
+ return NULL;
+}
+
+STATIC void
+free_rl(struct redirtab *rt, int reset)
+{
+ struct renamelist *rl, *rn = rt->renamed;
+
+ while ((rl = rn) != NULL) {
+ rn = rl->next;
+ if (rl->orig == 0)
+ fd0_redirected--;
+ VTRACE(DBG_REDIR, ("popredir %d%s: %s",
+ rl->orig, rl->orig==0 ? " (STDIN)" : "",
+ reset ? "" : "no reset\n"));
+ if (reset) {
+ if (rl->into < 0) {
+ VTRACE(DBG_REDIR, ("closed\n"));
+ close(rl->orig);
+ } else {
+ VTRACE(DBG_REDIR, ("from %d\n", rl->into));
+ movefd(rl->into, rl->orig);
+ }
+ }
+ ckfree(rl);
+ }
+ rt->renamed = NULL;
+}
+
+STATIC void
+fd_rename(struct redirtab *rt, int from, int to)
+{
+ /* XXX someday keep a short list (8..10) of freed renamelists XXX */
+ struct renamelist *rl = ckmalloc(sizeof(struct renamelist));
+
+ rl->next = rt->renamed;
+ rt->renamed = rl;
+
+ rl->orig = from;
+ rl->into = to;
+}
+
+/*
+ * Process a list of redirection commands. If the REDIR_PUSH flag is set,
+ * old file descriptors are stashed away so that the redirection can be
+ * undone by calling popredir. If the REDIR_BACKQ flag is set, then the
+ * standard output, and the standard error if it becomes a duplicate of
+ * stdout, is saved in memory.
+ */
+
+void
+redirect(union node *redir, int flags)
+{
+ union node *n;
+ struct redirtab *sv = NULL;
+ int i;
+ int fd;
+ char memory[10]; /* file descriptors to write to memory */
+
+ CTRACE(DBG_REDIR, ("redirect(F=0x%x):%s\n", flags, redir?"":" NONE"));
+ for (i = 10 ; --i >= 0 ; )
+ memory[i] = 0;
+ memory[1] = flags & REDIR_BACKQ;
+ if (flags & REDIR_PUSH) {
+ /*
+ * We don't have to worry about REDIR_VFORK here, as
+ * flags & REDIR_PUSH is never true if REDIR_VFORK is set.
+ */
+ sv = ckmalloc(sizeof (struct redirtab));
+ sv->renamed = NULL;
+ sv->next = redirlist;
+ redirlist = sv;
+ }
+ for (n = redir ; n ; n = n->nfile.next) {
+ fd = n->nfile.fd;
+ VTRACE(DBG_REDIR, ("redir %d (max=%d) ", fd, max_user_fd));
+ if (fd > max_user_fd)
+ max_user_fd = fd;
+ renumber_sh_fd(sh_fd(fd));
+ if ((n->nfile.type == NTOFD || n->nfile.type == NFROMFD) &&
+ n->ndup.dupfd == fd) {
+ /* redirect from/to same file descriptor */
+ /* make sure it stays open */
+ if (fcntl(fd, F_SETFD, 0) < 0)
+ error("fd %d: %s", fd, strerror(errno));
+ VTRACE(DBG_REDIR, ("!cloexec\n"));
+ continue;
+ }
+
+ if ((flags & REDIR_PUSH) && !is_renamed(sv->renamed, fd)) {
+ INTOFF;
+ if (big_sh_fd < 10)
+ find_big_fd();
+ if ((i = fcntl(fd, F_DUPFD, big_sh_fd)) == -1) {
+ switch (errno) {
+ case EBADF:
+ i = CLOSED;
+ break;
+ case EMFILE:
+ case EINVAL:
+ find_big_fd();
+ i = fcntl(fd, F_DUPFD, big_sh_fd);
+ if (i >= 0)
+ break;
+ /* FALLTHRU */
+ default:
+ i = errno;
+ INTON; /* XXX not needed here ? */
+ error("%d: %s", fd, strerror(i));
+ /* NOTREACHED */
+ }
+ }
+ if (i >= 0)
+ (void)fcntl(i, F_SETFD, FD_CLOEXEC);
+ fd_rename(sv, fd, i);
+ VTRACE(DBG_REDIR, ("saved as %d ", i));
+ INTON;
+ }
+ VTRACE(DBG_REDIR, ("%s\n", fd == 0 ? "STDIN" : ""));
+ if (fd == 0)
+ fd0_redirected++;
+ openredirect(n, memory, flags);
+ }
+ if (memory[1])
+ out1 = &memout;
+ if (memory[2])
+ out2 = &memout;
+}
+
+
+STATIC void
+openredirect(union node *redir, char memory[10], int flags)
+{
+ struct stat sb;
+ int fd = redir->nfile.fd;
+ char *fname;
+ int f;
+ int eflags, cloexec;
+
+ /*
+ * We suppress interrupts so that we won't leave open file
+ * descriptors around. This may not be such a good idea because
+ * an open of a device or a fifo can block indefinitely.
+ */
+ INTOFF;
+ if (fd < 10)
+ memory[fd] = 0;
+ switch (redir->nfile.type) {
+ case NFROM:
+ fname = redir->nfile.expfname;
+ if (flags & REDIR_VFORK)
+ eflags = O_NONBLOCK;
+ else
+ eflags = 0;
+ if ((f = open(fname, O_RDONLY|eflags)) < 0)
+ goto eopen;
+ VTRACE(DBG_REDIR, ("openredirect(< '%s') -> %d [%#x]",
+ fname, f, eflags));
+ if (eflags)
+ (void)fcntl(f, F_SETFL, fcntl(f, F_GETFL, 0) & ~eflags);
+ break;
+ case NFROMTO:
+ fname = redir->nfile.expfname;
+ if ((f = open(fname, O_RDWR|O_CREAT, 0666)) < 0)
+ goto ecreate;
+ VTRACE(DBG_REDIR, ("openredirect(<> '%s') -> %d", fname, f));
+ break;
+ case NTO:
+ if (Cflag) {
+ fname = redir->nfile.expfname;
+ if ((f = open(fname, O_WRONLY)) == -1) {
+ if ((f = open(fname, O_WRONLY|O_CREAT|O_EXCL,
+ 0666)) < 0)
+ goto ecreate;
+ } else if (fstat(f, &sb) == -1) {
+ int serrno = errno;
+ close(f);
+ errno = serrno;
+ goto ecreate;
+ } else if (S_ISREG(sb.st_mode)) {
+ close(f);
+ errno = EEXIST;
+ goto ecreate;
+ }
+ VTRACE(DBG_REDIR, ("openredirect(>| '%s') -> %d",
+ fname, f));
+ break;
+ }
+ /* FALLTHROUGH */
+ case NCLOBBER:
+ fname = redir->nfile.expfname;
+ if ((f = open(fname, O_WRONLY|O_CREAT|O_TRUNC, 0666)) < 0)
+ goto ecreate;
+ VTRACE(DBG_REDIR, ("openredirect(> '%s') -> %d", fname, f));
+ break;
+ case NAPPEND:
+ fname = redir->nfile.expfname;
+ if ((f = open(fname, O_WRONLY|O_CREAT|O_APPEND, 0666)) < 0)
+ goto ecreate;
+ VTRACE(DBG_REDIR, ("openredirect(>> '%s') -> %d", fname, f));
+ break;
+ case NTOFD:
+ case NFROMFD:
+ if (redir->ndup.dupfd >= 0) { /* if not ">&-" */
+ if (fd < 10 && redir->ndup.dupfd < 10 &&
+ memory[redir->ndup.dupfd])
+ memory[fd] = 1;
+ else if (copyfd(redir->ndup.dupfd, fd,
+ (flags & REDIR_KEEP) == 0) < 0)
+ error("Redirect (from %d to %d) failed: %s",
+ redir->ndup.dupfd, fd, strerror(errno));
+ VTRACE(DBG_REDIR, ("openredirect: %d%c&%d\n", fd,
+ "<>"[redir->nfile.type==NTOFD], redir->ndup.dupfd));
+ } else {
+ (void) close(fd);
+ VTRACE(DBG_REDIR, ("openredirect: %d%c&-\n", fd,
+ "<>"[redir->nfile.type==NTOFD]));
+ }
+ INTON;
+ return;
+ case NHERE:
+ case NXHERE:
+ VTRACE(DBG_REDIR, ("openredirect: %d<<...", fd));
+ f = openhere(redir);
+ break;
+ default:
+ abort();
+ }
+
+ cloexec = fd > 2 && (flags & REDIR_KEEP) == 0 && !posix;
+ if (f != fd) {
+ VTRACE(DBG_REDIR, (" -> %d", fd));
+ if (copyfd(f, fd, cloexec) < 0) {
+ int e = errno;
+
+ close(f);
+ error("redirect reassignment (fd %d) failed: %s", fd,
+ strerror(e));
+ }
+ close(f);
+ } else if (cloexec)
+ (void)fcntl(f, F_SETFD, FD_CLOEXEC);
+ VTRACE(DBG_REDIR, ("%s\n", cloexec ? " cloexec" : ""));
+
+ INTON;
+ return;
+ ecreate:
+ exerrno = 1;
+ error("cannot create %s: %s", fname, errmsg(errno, E_CREAT));
+ eopen:
+ exerrno = 1;
+ error("cannot open %s: %s", fname, errmsg(errno, E_OPEN));
+}
+
+
+/*
+ * Handle here documents. Normally we fork off a process to write the
+ * data to a pipe. If the document is short, we can stuff the data in
+ * the pipe without forking.
+ */
+
+STATIC int
+openhere(const union node *redir)
+{
+ int pip[2];
+ int len = 0;
+
+ if (pipe(pip) < 0)
+ error("Pipe call failed");
+ if (redir->type == NHERE) {
+ len = strlen(redir->nhere.doc->narg.text);
+ if (len <= PIPESIZE) {
+ xwrite(pip[1], redir->nhere.doc->narg.text, len);
+ goto out;
+ }
+ }
+ VTRACE(DBG_REDIR, (" forking [%d,%d]\n", pip[0], pip[1]));
+ if (forkshell(NULL, NULL, FORK_NOJOB) == 0) {
+ close(pip[0]);
+ signal(SIGINT, SIG_IGN);
+ signal(SIGQUIT, SIG_IGN);
+ signal(SIGHUP, SIG_IGN);
+#ifdef SIGTSTP
+ signal(SIGTSTP, SIG_IGN);
+#endif
+ signal(SIGPIPE, SIG_DFL);
+ if (redir->type == NHERE)
+ xwrite(pip[1], redir->nhere.doc->narg.text, len);
+ else
+ expandhere(redir->nhere.doc, pip[1]);
+ _exit(0);
+ }
+ VTRACE(DBG_REDIR, ("openhere (closing %d)", pip[1]));
+ out:
+ close(pip[1]);
+ VTRACE(DBG_REDIR, (" (pipe fd=%d)", pip[0]));
+ return pip[0];
+}
+
+
+
+/*
+ * Undo the effects of the last redirection.
+ */
+
+void
+popredir(void)
+{
+ struct redirtab *rp = redirlist;
+
+ INTOFF;
+ free_rl(rp, 1);
+ redirlist = rp->next;
+ ckfree(rp);
+ INTON;
+}
+
+/*
+ * Undo all redirections. Called on error or interrupt.
+ */
+
+#ifdef mkinit
+
+INCLUDE "redir.h"
+
+RESET {
+ while (redirlist)
+ popredir();
+}
+
+SHELLPROC {
+ clearredir(0);
+}
+
+#endif
+
+/* Return true if fd 0 has already been redirected at least once. */
+int
+fd0_redirected_p(void)
+{
+ return fd0_redirected != 0;
+}
+
+/*
+ * Discard all saved file descriptors.
+ */
+
+void
+clearredir(int vforked)
+{
+ struct redirtab *rp;
+ struct renamelist *rl;
+
+ for (rp = redirlist ; rp ; rp = rp->next) {
+ if (!vforked)
+ free_rl(rp, 0);
+ else for (rl = rp->renamed; rl; rl = rl->next)
+ if (rl->into >= 0)
+ close(rl->into);
+ }
+}
+
+
+
+/*
+ * Copy a file descriptor to be == to.
+ * cloexec indicates if we want close-on-exec or not.
+ * Returns -1 if any error occurs.
+ */
+
+STATIC int
+copyfd(int from, int to, int cloexec)
+{
+ int newfd;
+
+ if (cloexec && to > 2)
+ newfd = dup3(from, to, O_CLOEXEC);
+ else
+ newfd = dup2(from, to);
+
+ return newfd;
+}
+
+/*
+ * rename fd from to be fd to (closing from).
+ * close-on-exec is never set on 'to' (unless
+ * from==to and it was set on from) - ie: a no-op
+ * returns to (or errors() if an error occurs).
+ *
+ * This is mostly used for rearranging the
+ * results from pipe().
+ */
+int
+movefd(int from, int to)
+{
+ if (from == to)
+ return to;
+
+ (void) close(to);
+ if (copyfd(from, to, 0) != to) {
+ int e = errno;
+
+ (void) close(from);
+ error("Unable to make fd %d: %s", to, strerror(e));
+ }
+ (void) close(from);
+
+ return to;
+}
+
+STATIC void
+find_big_fd(void)
+{
+ int i, fd;
+ static int last_start = 3; /* aim to keep sh fd's under 20 */
+
+ if (last_start < 10)
+ last_start++;
+
+ for (i = (1 << last_start); i >= 10; i >>= 1) {
+ if ((fd = fcntl(0, F_DUPFD, i - 1)) >= 0) {
+ close(fd);
+ break;
+ }
+ }
+
+ fd = (i / 5) * 4;
+ if (fd < 10)
+ fd = 10;
+
+ big_sh_fd = fd;
+}
+
+/*
+ * If possible, move file descriptor fd out of the way
+ * of expected user fd values. Returns the new fd
+ * (which may be the input fd if things do not go well.)
+ * Always set close-on-exec on the result, and close
+ * the input fd unless it is to be our result.
+ */
+int
+to_upper_fd(int fd)
+{
+ int i;
+
+ VTRACE(DBG_REDIR|DBG_OUTPUT, ("to_upper_fd(%d)", fd));
+ if (big_sh_fd < 10)
+ find_big_fd();
+ do {
+ i = fcntl(fd, F_DUPFD_CLOEXEC, big_sh_fd);
+ if (i >= 0) {
+ if (fd != i)
+ close(fd);
+ VTRACE(DBG_REDIR|DBG_OUTPUT, ("-> %d\n", i));
+ return i;
+ }
+ if (errno != EMFILE && errno != EINVAL)
+ break;
+ find_big_fd();
+ } while (big_sh_fd > 10);
+
+ /*
+ * If we wanted to move this fd to some random high number
+ * we certainly do not intend to pass it through exec, even
+ * if the reassignment failed.
+ */
+ (void)fcntl(fd, F_SETFD, FD_CLOEXEC);
+ VTRACE(DBG_REDIR|DBG_OUTPUT, (" fails ->%d\n", fd));
+ return fd;
+}
+
+void
+register_sh_fd(int fd, void (*cb)(int, int))
+{
+ struct shell_fds *fp;
+
+ fp = ckmalloc(sizeof (struct shell_fds));
+ if (fp != NULL) {
+ fp->nxt = sh_fd_list;
+ sh_fd_list = fp;
+
+ fp->fd = fd;
+ fp->cb = cb;
+ }
+}
+
+void
+sh_close(int fd)
+{
+ struct shell_fds **fpp, *fp;
+
+ fpp = &sh_fd_list;
+ while ((fp = *fpp) != NULL) {
+ if (fp->fd == fd) {
+ *fpp = fp->nxt;
+ ckfree(fp);
+ break;
+ }
+ fpp = &fp->nxt;
+ }
+ (void)close(fd);
+}
+
+STATIC struct shell_fds *
+sh_fd(int fd)
+{
+ struct shell_fds *fp;
+
+ for (fp = sh_fd_list; fp != NULL; fp = fp->nxt)
+ if (fp->fd == fd)
+ return fp;
+ return NULL;
+}
+
+STATIC void
+renumber_sh_fd(struct shell_fds *fp)
+{
+ int to;
+
+ if (fp == NULL)
+ return;
+
+#ifndef F_DUPFD_CLOEXEC
+#define F_DUPFD_CLOEXEC F_DUPFD
+#define CLOEXEC(fd) (fcntl((fd), F_SETFD, fcntl((fd),F_GETFD) | FD_CLOEXEC))
+#else
+#define CLOEXEC(fd)
+#endif
+
+ /*
+ * if we have had a collision, and the sh fd was a "big" one
+ * try moving the sh fd base to a higher number (if possible)
+ * so future sh fds are less likely to be in the user's sights
+ * (incl this one when moved)
+ */
+ if (fp->fd >= big_sh_fd)
+ find_big_fd();
+
+ to = fcntl(fp->fd, F_DUPFD_CLOEXEC, big_sh_fd);
+ if (to == -1)
+ to = fcntl(fp->fd, F_DUPFD_CLOEXEC, big_sh_fd/2);
+ if (to == -1)
+ to = fcntl(fp->fd, F_DUPFD_CLOEXEC, fp->fd + 1);
+ if (to == -1)
+ to = fcntl(fp->fd, F_DUPFD_CLOEXEC, 10);
+ if (to == -1)
+ to = fcntl(fp->fd, F_DUPFD_CLOEXEC, 3);
+ if (to == -1)
+ error("insufficient file descriptors available");
+ CLOEXEC(to);
+
+ if (fp->fd == to) /* impossible? */
+ return;
+
+ (*fp->cb)(fp->fd, to);
+ (void)close(fp->fd);
+ fp->fd = to;
+}
+
+static const struct flgnames {
+ const char *name;
+ uint16_t minch;
+ uint32_t value;
+} nv[] = {
+#ifdef O_APPEND
+ { "append", 2, O_APPEND },
+#endif
+#ifdef O_ASYNC
+ { "async", 2, O_ASYNC },
+#endif
+#ifdef O_SYNC
+ { "sync", 2, O_SYNC },
+#endif
+#ifdef O_NONBLOCK
+ { "nonblock", 3, O_NONBLOCK },
+#endif
+#ifdef O_FSYNC
+ { "fsync", 2, O_FSYNC },
+#endif
+#ifdef O_DSYNC
+ { "dsync", 2, O_DSYNC },
+#endif
+#ifdef O_RSYNC
+ { "rsync", 2, O_RSYNC },
+#endif
+#ifdef O_ALT_IO
+ { "altio", 2, O_ALT_IO },
+#endif
+#ifdef O_DIRECT
+ { "direct", 2, O_DIRECT },
+#endif
+#ifdef O_NOSIGPIPE
+ { "nosigpipe", 3, O_NOSIGPIPE },
+#endif
+#ifdef O_CLOEXEC
+ { "cloexec", 2, O_CLOEXEC },
+#endif
+ { 0, 0, 0 }
+};
+#define ALLFLAGS (O_APPEND|O_ASYNC|O_SYNC|O_NONBLOCK|O_DSYNC|O_RSYNC|\
+ O_ALT_IO|O_DIRECT|O_NOSIGPIPE|O_CLOEXEC)
+
+static int
+getflags(int fd, int p)
+{
+ int c, f;
+
+ if (sh_fd(fd) != NULL) {
+ if (!p)
+ return -1;
+ error("Can't get status for fd=%d (%s)", fd,
+ "Bad file descriptor"); /*XXX*/
+ }
+
+ if ((c = fcntl(fd, F_GETFD)) == -1) {
+ if (!p)
+ return -1;
+ error("Can't get status for fd=%d (%s)", fd, strerror(errno));
+ }
+ if ((f = fcntl(fd, F_GETFL)) == -1) {
+ if (!p)
+ return -1;
+ error("Can't get flags for fd=%d (%s)", fd, strerror(errno));
+ }
+ if (c & FD_CLOEXEC)
+ f |= O_CLOEXEC;
+ return f & ALLFLAGS;
+}
+
+static void
+printone(int fd, int p, int verbose, int pfd)
+{
+ int f = getflags(fd, p);
+ const struct flgnames *fn;
+
+ if (f == -1)
+ return;
+
+ if (pfd)
+ outfmt(out1, "%d: ", fd);
+ for (fn = nv; fn->name; fn++) {
+ if (f & fn->value) {
+ outfmt(out1, "%s%s", verbose ? "+" : "", fn->name);
+ f &= ~fn->value;
+ } else if (verbose)
+ outfmt(out1, "-%s", fn->name);
+ else
+ continue;
+ if (f || (verbose && fn[1].name))
+ outfmt(out1, ",");
+ }
+ if (verbose && f) /* f should be normally be 0 */
+ outfmt(out1, " +%#x", f);
+ outfmt(out1, "\n");
+}
+
+static void
+parseflags(char *s, int *p, int *n)
+{
+ int *v, *w;
+ const struct flgnames *fn;
+ size_t len;
+
+ *p = 0;
+ *n = 0;
+ for (s = strtok(s, ","); s; s = strtok(NULL, ",")) {
+ switch (*s++) {
+ case '+':
+ v = p;
+ w = n;
+ break;
+ case '-':
+ v = n;
+ w = p;
+ break;
+ default:
+ error("Missing +/- indicator before flag %s", s-1);
+ }
+
+ len = strlen(s);
+ for (fn = nv; fn->name; fn++)
+ if (len >= fn->minch && strncmp(s,fn->name,len) == 0) {
+ *v |= fn->value;
+ *w &=~ fn->value;
+ break;
+ }
+ if (fn->name == 0)
+ error("Bad flag `%s'", s);
+ }
+}
+
+static void
+setone(int fd, int pos, int neg, int verbose)
+{
+ int f = getflags(fd, 1);
+ int n, cloexec;
+
+ if (f == -1)
+ return;
+
+ cloexec = -1;
+ if ((pos & O_CLOEXEC) && !(f & O_CLOEXEC))
+ cloexec = FD_CLOEXEC;
+ if ((neg & O_CLOEXEC) && (f & O_CLOEXEC))
+ cloexec = 0;
+
+ if (cloexec != -1 && fcntl(fd, F_SETFD, cloexec) == -1)
+ error("Can't set status for fd=%d (%s)", fd, strerror(errno));
+
+ pos &= ~O_CLOEXEC;
+ neg &= ~O_CLOEXEC;
+ f &= ~O_CLOEXEC;
+ n = f;
+ n |= pos;
+ n &= ~neg;
+ if (n != f && fcntl(fd, F_SETFL, n) == -1)
+ error("Can't set flags for fd=%d (%s)", fd, strerror(errno));
+ if (verbose)
+ printone(fd, 1, verbose, 1);
+}
+
+int
+fdflagscmd(int argc, char *argv[])
+{
+ char *num;
+ int verbose = 0, ch, pos = 0, neg = 0;
+ char *setflags = NULL;
+
+ optreset = 1; optind = 1; /* initialize getopt */
+ while ((ch = getopt(argc, argv, ":vs:")) != -1)
+ switch ((char)ch) {
+ case 'v':
+ verbose = 1;
+ break;
+ case 's':
+ if (setflags)
+ goto msg;
+ setflags = optarg;
+ break;
+ case '?':
+ default:
+ msg:
+ error("Usage: fdflags [-v] [-s <flags> fd] [fd...]");
+ /* NOTREACHED */
+ }
+
+ argc -= optind, argv += optind;
+
+ if (setflags)
+ parseflags(setflags, &pos, &neg);
+
+ if (argc == 0) {
+ int i;
+
+ if (setflags)
+ goto msg;
+
+ for (i = 0; i <= max_user_fd; i++)
+ printone(i, 0, verbose, 1);
+ return 0;
+ }
+
+ while ((num = *argv++) != NULL) {
+ int fd = number(num);
+
+ while (num[0] == '0' && num[1] != '\0') /* skip 0's */
+ num++;
+ if (strlen(num) > 5)
+ error("%s too big to be a file descriptor", num);
+
+ if (setflags)
+ setone(fd, pos, neg, verbose);
+ else
+ printone(fd, 1, verbose, argc > 1);
+ }
+ return 0;
+}
+
+#undef MAX /* in case we inherited them from somewhere */
+#undef MIN
+
+#define MIN(a,b) (/*CONSTCOND*/((a)<=(b)) ? (a) : (b))
+#define MAX(a,b) (/*CONSTCOND*/((a)>=(b)) ? (a) : (b))
+
+ /* now make the compiler work for us... */
+#define MIN_REDIR MIN(MIN(MIN(MIN(NTO,NFROM), MIN(NTOFD,NFROMFD)), \
+ MIN(MIN(NCLOBBER,NAPPEND), MIN(NHERE,NXHERE))), NFROMTO)
+#define MAX_REDIR MAX(MAX(MAX(MAX(NTO,NFROM), MAX(NTOFD,NFROMFD)), \
+ MAX(MAX(NCLOBBER,NAPPEND), MAX(NHERE,NXHERE))), NFROMTO)
+
+static const char *redir_sym[MAX_REDIR - MIN_REDIR + 1] = {
+ [NTO - MIN_REDIR]= ">",
+ [NFROM - MIN_REDIR]= "<",
+ [NTOFD - MIN_REDIR]= ">&",
+ [NFROMFD - MIN_REDIR]= "<&",
+ [NCLOBBER - MIN_REDIR]= ">|",
+ [NAPPEND - MIN_REDIR]= ">>",
+ [NHERE - MIN_REDIR]= "<<",
+ [NXHERE - MIN_REDIR]= "<<",
+ [NFROMTO - MIN_REDIR]= "<>",
+};
+
+int
+outredir(struct output *out, union node *n, int sep)
+{
+ if (n == NULL)
+ return 0;
+ if (n->type < MIN_REDIR || n->type > MAX_REDIR ||
+ redir_sym[n->type - MIN_REDIR] == NULL)
+ return 0;
+
+ if (sep)
+ outc(sep, out);
+
+ /*
+ * ugly, but all redir node types have "fd" in same slot...
+ * (and code other places assumes it as well)
+ */
+ if ((redir_sym[n->type - MIN_REDIR][0] == '<' && n->nfile.fd != 0) ||
+ (redir_sym[n->type - MIN_REDIR][0] == '>' && n->nfile.fd != 1))
+ outfmt(out, "%d", n->nfile.fd);
+
+ outstr(redir_sym[n->type - MIN_REDIR], out);
+
+ switch (n->type) {
+ case NHERE:
+ outstr("'...'", out);
+ break;
+ case NXHERE:
+ outstr("...", out);
+ break;
+ case NTOFD:
+ case NFROMFD:
+ if (n->ndup.dupfd < 0)
+ outc('-', out);
+ else
+ outfmt(out, "%d", n->ndup.dupfd);
+ break;
+ default:
+ outstr(n->nfile.expfname, out);
+ break;
+ }
+ return 1;
+}
diff --git a/bin/sh/redir.h b/bin/sh/redir.h
new file mode 100644
index 0000000..b186bb4
--- /dev/null
+++ b/bin/sh/redir.h
@@ -0,0 +1,55 @@
+/* $NetBSD: redir.h,v 1.24 2017/06/30 23:01:21 kre Exp $ */
+
+/*-
+ * Copyright (c) 1991, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Kenneth Almquist.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * @(#)redir.h 8.2 (Berkeley) 5/4/95
+ */
+
+/* flags passed to redirect */
+#define REDIR_PUSH 0x01 /* save previous values of file descriptors */
+#define REDIR_BACKQ 0x02 /* save the command output in memory */
+#define REDIR_VFORK 0x04 /* running under vfork(2), be careful */
+#define REDIR_KEEP 0x08 /* don't close-on-exec */
+
+union node;
+void redirect(union node *, int);
+void popredir(void);
+int fd0_redirected_p(void);
+void clearredir(int);
+int movefd(int, int);
+int to_upper_fd(int);
+void register_sh_fd(int, void (*)(int, int));
+void sh_close(int);
+struct output;
+int outredir(struct output *, union node *, int);
+
+int max_user_fd; /* highest fd used by user */
diff --git a/bin/sh/sh.1 b/bin/sh/sh.1
new file mode 100644
index 0000000..4630689
--- /dev/null
+++ b/bin/sh/sh.1
@@ -0,0 +1,4549 @@
+.\" $NetBSD: sh.1,v 1.217 2019/01/21 14:09:24 kre Exp $
+.\" Copyright (c) 1991, 1993
+.\" The Regents of the University of California. All rights reserved.
+.\"
+.\" This code is derived from software contributed to Berkeley by
+.\" Kenneth Almquist.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\" notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must reproduce the above copyright
+.\" notice, this list of conditions and the following disclaimer in the
+.\" documentation and/or other materials provided with the distribution.
+.\" 3. Neither the name of the University nor the names of its contributors
+.\" may be used to endorse or promote products derived from this software
+.\" without specific prior written permission.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+.\" ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+.\" SUCH DAMAGE.
+.\"
+.\" @(#)sh.1 8.6 (Berkeley) 5/4/95
+.\"
+.Dd December 12, 2018
+.Dt SH 1
+.\" everything except c o and s (keep them ordered)
+.ds flags abCEeFfhIiLmnpquVvXx
+.Os
+.Sh NAME
+.Nm sh
+.Nd command interpreter (shell)
+.Sh SYNOPSIS
+.Nm
+.Bk -words
+.Op Fl \*[flags]
+.Op Cm +\*[flags]
+.Ek
+.Bk -words
+.Op Fl o Ar option_name
+.Op Cm +o Ar option_name
+.Ek
+.Bk -words
+.Op Ar command_file Op Ar argument ...
+.Ek
+.Nm
+.Fl c
+.Bk -words
+.Op Fl s
+.Op Fl \*[flags]
+.Op Cm +\*[flags]
+.Ek
+.Bk -words
+.Op Fl o Ar option_name
+.Op Cm +o Ar option_name
+.Ek
+.Bk -words
+.Ar command_string
+.Op Ar command_name Op Ar argument ...
+.Ek
+.Nm
+.Fl s
+.Bk -words
+.Op Fl \*[flags]
+.Op Cm +\*[flags]
+.Ek
+.Bk -words
+.Op Fl o Ar option_name
+.Op Cm +o Ar option_name
+.Ek
+.Bk -words
+.Op Ar argument ...
+.Ek
+.Sh DESCRIPTION
+.Nm
+is the standard command interpreter for the system.
+The current version of
+.Nm
+is in the process of being changed to conform more closely to the
+POSIX 1003.2 and 1003.2a specifications for the shell.
+This version has many
+features which make it appear similar in some respects to the Korn shell,
+but it is not a Korn shell clone (see
+.Xr ksh 1 ) .
+This man page is not intended
+to be a tutorial or a complete specification of the shell.
+.Ss Overview
+The shell is a command that reads lines from either a file or the
+terminal, interprets them, and generally executes other commands.
+A shell is the program that is running when a user logs into the system.
+(Users can select which shell is executed for them at login with the
+.Xr chsh 1
+command).
+The shell implements a language that has flow control
+constructs, a macro facility that provides a variety of features in
+addition to data storage, along with built in history and line editing
+capabilities.
+It incorporates many features to aid interactive use and
+has the advantage that the interpretative language is common to both
+interactive and non-interactive use (shell scripts).
+That is, commands
+can be typed directly to the running shell or can be put into a file and
+the file can be executed directly by the shell.
+.Ss Invocation
+If no arguments are present and if the standard input,
+and standard error output, of the shell
+are connected to a terminal (or terminals, or if the
+.Fl i
+flag is set),
+and the
+.Fl c
+option is not present, the shell is considered an interactive shell.
+An interactive shell generally prompts before each command and handles
+programming and command errors differently (as described below).
+When first starting,
+the shell inspects argument 0, and if it begins with a dash
+.Sq - ,
+the shell is also considered
+a login shell.
+This is normally done automatically by the system
+when the user first logs in.
+A login shell first reads commands
+from the files
+.Pa /etc/profile
+and
+.Pa .profile
+in the user's home directory
+.Pq \&$HOME ,
+if they exist.
+If the environment variable
+.Ev ENV
+is set on entry to a shell,
+or is set in the
+.Pa .profile
+of a login shell,
+and either the shell is interactive, or the
+.Cm posix
+option is not set,
+the shell then performs parameter and arithmetic
+expansion on the value of
+.Ev ENV ,
+(these are described later)
+and then reads commands from the file name that results.
+If
+.Ev ENV
+contains a command substitution, or one of the
+other expansions fails, or if there are no expansions
+to expand, the value of
+.Ev ENV
+is used as the file name.
+.Pp
+Therefore, a user should place commands that are to be executed only at
+login time in the
+.Pa .profile
+file, and commands that are executed for every shell inside the
+.Ev ENV
+file.
+To set the
+.Ev ENV
+variable to some file, place the following line in your
+.Pa .profile
+of your home directory
+.Pp
+.Dl ENV=$HOME/.shinit; export ENV
+.Pp
+substituting for
+.Dq .shinit
+any filename you wish.
+Since the
+.Ev ENV
+file can be read for every invocation of the shell, including shell scripts
+and non-interactive shells, the following paradigm is useful for
+restricting commands in the
+.Ev ENV
+file to interactive invocations.
+Place commands within the
+.Dq Ic case
+and
+.Dq Ic esac
+below (these commands are described later):
+.Bd -literal -offset indent
+case $- in *i*)
+ # commands for interactive use only
+ ...
+esac
+.Ed
+.Pp
+If command line arguments besides the options have been specified, and
+neither
+.Fl c
+nor
+.Fl s
+was given, then the shell treats the first argument
+as the name of a file from which to read commands (a shell script).
+This also becomes
+.Li $0
+and the remaining arguments are set as the
+positional parameters of the shell
+.Li ( $1 , $2 ,
+etc).
+Otherwise, if
+.Fl c
+was given, then the first argument, which must exist,
+is taken to be a string of
+.Nm
+commands to execute.
+Then if any additional arguments follow the command string,
+those arguments become
+.Li $0 , $1 ,
+\&...
+Otherwise, if additional arguments were given
+(which implies that
+.Fl s
+was set)
+those arguments become
+.Li $1 , $2 ,
+\&...
+If
+.Li $0
+has not been set by the preceding processing, it
+will be set to
+.Va argv\^ Ns [ 0 ]
+as passed to the shell, which will
+usually be the name of the shell itself.
+If
+.Fl s
+was given, or if neither
+.Fl c
+nor any additional (non-option) arguments were present,
+the shell reads commands from its standard input.
+.\"
+.\"
+.Ss Argument List Processing
+.\"
+Currently, all of the single letter options that can meaningfully
+be set using the
+.Ic set
+built-in, have a corresponding name
+that can be used as an argument to the
+.Fl o
+option.
+The
+.Ic set Fl o
+name is provided next to the single letter option in
+the description below.
+Some options have only a long name, they are described after
+the flag options, they are used with
+.Fl o
+or
+.Cm +o
+only, either on the command line, or with the
+.Ic set
+built-in command.
+Other options described are for the command line only.
+Specifying a dash
+.Dq Cm \-
+turns the option on, while using a plus
+.Dq Cm +
+disables the option.
+The following options can be set from the command line and,
+unless otherwise stated, with the
+.Ic set
+built-in (described later).
+.\"
+.\" strlen("quietprofile") == strlen("local_lineno"): pick the latter
+.\" to give the indent as the _ in local_lineno, and the fi ligature in
+.\" quietprofile combine to make "local_lineno' slightly wider when printed
+.\" (in italics) in a variable width font.
+.Bl -tag -width ".Fl L Em local_lineno" -offset indent
+.\"
+.It Fl a Em allexport
+Automatically export any variable to which a value is assigned
+while this flag is set, unless the variable has been marked as
+not for export.
+.It Fl b Em notify
+Enable asynchronous notification of background job completion.
+(Not implemented.)
+.It Fl C Em noclobber
+Don't overwrite existing files with
+.Dq > .
+.It Fl c
+Read commands from the
+.Ar command_string
+operand instead of, or in addition to, from the standard input.
+Special parameter
+.Dv 0 \" $0 (comments like this for searching sources only)
+will be set from the
+.Ar command_name
+operand if given, and the positional parameters
+.Dv ( 1 , 2 ,
+etc.)
+set from the remaining argument operands, if any.
+.Fl c
+is only available at invocation, it cannot be
+.Ic set ,
+and there is no form using
+.Dq Cm \&+ .
+.It Fl E Em emacs
+Enable the built-in emacs style
+command line editor (disables
+.Fl V
+if it has been set).
+(See the
+.Sx Command Line Editing
+section below.)
+.It Fl e Em errexit
+If not interactive, exit immediately if any untested command fails.
+If interactive, and an untested command fails,
+cease all processing of the current command and return to
+prompt for a new command.
+The exit status of a command is considered to be
+explicitly tested if the command is used to control an
+.Ic if ,
+.Ic elif ,
+.Ic while ,
+or
+.Ic until ,
+or if the command is the left hand operand of an
+.Dq &&
+or
+.Dq ||
+operator,
+or if it is a pipeline (or simple command) preceded by the
+.Dq \&!
+operator.
+With pipelines, only the status of the entire pipeline
+(indicated by the last command it contains)
+is tested when
+.Fl e
+is set to determine if the shell should exit.
+.It Fl F Em fork
+Cause the shell to always use
+.Xr fork 2
+instead of attempting
+.Xr vfork 2
+when it needs to create a new process.
+This should normally have no visible effect,
+but can slow execution.
+The
+.Nm
+can be compiled to always use
+.Xr fork 2
+in which case altering the
+.Fl F
+flag has no effect.
+.It Fl f Em noglob
+Disable pathname expansion.
+.It Fl h Em trackall
+Functions defined while this option is set will have paths bound to
+commands to be executed by the function at the time of the definition.
+When off when a function is defined,
+the file system is searched for commands each time the function is invoked.
+(Not implemented.)
+.It Fl I Em ignoreeof
+Ignore EOFs from input when interactive.
+(After a large number of consecutive EOFs the shell will exit anyway.)
+.It Fl i Em interactive
+Force the shell to behave interactively.
+.It Fl L Em local_lineno
+When set, before a function is defined,
+causes the variable
+.Dv LINENO
+when used within the function,
+to refer to the line number defined such that
+first line of the function is line 1.
+When reset,
+.Dv LINENO
+in a function refers to the line number within the file
+within which the definition of the function occurs.
+This option defaults to
+.Dq on
+in this shell.
+For more details see the section
+.Sx LINENO
+below.
+.It Fl m Em monitor
+Turn on job control (set automatically when interactive).
+.It Fl n Em noexec
+Read and parse commands, but do not execute them.
+This is useful for checking the syntax of shell scripts.
+If
+.Fl n
+becomes set in an interactive shell, it will automatically be
+cleared just before the next time the command line prompt
+.Pq Ev PS1
+is written.
+.It Fl p Em nopriv
+Do not attempt to reset effective UID if it does not match UID.
+This is not set by default to help avoid incorrect usage by setuid
+root programs via
+.Xr system 3
+or
+.Xr popen 3 .
+.It Fl q Em quietprofile
+If the
+.Fl v
+or
+.Fl x
+options have been set, do not apply them when reading
+initialization files, these being
+.Pa /etc/profile ,
+.Pa .profile ,
+and the file specified by the
+.Ev ENV
+environment variable.
+.It Fl s Em stdin
+Read commands from standard input (set automatically if
+neither
+.Fl c
+nor file arguments are present).
+If after processing a command_string with the
+.Fl c
+option, the shell has not exited, and the
+.Fl s
+option is set, it will continue reading more commands from standard input.
+This option has no effect when set or reset after the shell has
+already started reading from the command_file, or from standard input.
+Note that the
+.Fl s
+flag being set does not cause the shell to be interactive.
+.It Fl u Em nounset
+Write a message to standard error when attempting to obtain a
+value from a variable that is not set,
+and if the shell is not interactive, exit immediately.
+For interactive shells, instead return immediately to the command prompt
+and read the next command.
+Note that expansions (described later, see
+.Sx Word Expansions
+below) using the
+.Sq \&+ ,
+.Sq \&\- ,
+.Sq \&= ,
+or
+.Sq \&?
+operators test if the variable is set, before attempting to
+obtain its value, and hence are unaffected by
+.Fl u .
+.It Fl V Em vi
+Enable the built-in
+.Xr vi 1
+command line editor (disables
+.Fl E
+if it has been set).
+(See the
+.Sx Command Line Editing
+section below.)
+.It Fl v Em verbose
+The shell writes its input to standard error as it is read.
+Useful for debugging.
+.It Fl X Em xlock
+Cause output from the
+.Ic xtrace
+.Pq Fl x
+option to be sent to standard error as it exists when the
+.Fl X
+option is enabled (regardless of its previous state.)
+For example:
+.Bd -literal -compact
+ set -X 2>/tmp/trace-file
+.Ed
+will arrange for tracing output to be sent to the file named,
+instead of wherever it was previously being sent,
+until the X option is set again, or cleared.
+.Pp
+Each change (set or clear) to
+.Fl X
+is also performed upon
+.Fl x ,
+but not the converse.
+.It Fl x Em xtrace
+Write each command to standard error (preceded by the expanded value of
+.Li $PS4 )
+before it is executed.
+Unless
+.Fl X
+is set,
+.Dq "standard error"
+means that which existed immediately before any redirections to
+be applied to the command are performed.
+Useful for debugging.
+.It "\ \ " Em cdprint
+Make an interactive shell always print the new directory name when
+changed by the
+.Ic cd
+command.
+In a non-interactive shell this option has no effect.
+.It "\ \ " Em nolog
+Prevent the entry of function definitions into the command history (see
+.Ic fc
+in the
+.Sx Built-ins
+section.)
+(Not implemented.)
+.It "\ \ " Em pipefail
+If set when a pipeline is created,
+the way the exit status of the pipeline is determined
+is altered.
+See
+.Sx Pipelines
+below for the details.
+.It "\ \ " Em posix
+Enables closer adherence to the POSIX shell standard.
+This option will default set at shell startup if the
+environment variable
+.Ev POSIXLY_CORRECT
+is present.
+That can be overridden (set or reset) by the
+.Fl o
+option on the command line.
+Currently this option controls whether (!posix) or not (posix)
+the file given by the
+.Ev ENV
+variable is read at startup by a non-interactive shell.
+It also controls whether file descriptors greater than 2
+opened using the
+.Ic exec
+built-in command are passed on to utilities executed
+.Dq ( yes
+in posix mode),
+whether a colon (:) terminates the user name in tilde (~) expansions
+other than in assignment statements
+.Dq ( no
+in posix mode),
+the format of the output of the
+.Ic kill Fl l
+command, where posix mode causes the names of the signals
+be separated by either a single space or newline, and where otherwise
+sufficient spaces are inserted to generate nice looking columns,
+and whether the shell treats
+an empty brace-list compound statement as a syntax error
+(expected by POSIX) or permits it.
+Such statements
+.Dq "{\ }"
+can be useful when defining dummy functions.
+Lastly, in posix mode, only one
+.Dq \&!
+is permitted before a pipeline.
+.It "\ \ " Em promptcmds
+Allows command substitutions (as well as parameter
+and arithmetic expansions, which are always performed)
+upon the prompt strings
+.Ev PS1 ,
+.Ev PS2 ,
+and
+.Ev PS4
+each time, before they are output.
+This option should not be set until after the prompts
+have been set (or verified) to avoid accidentally importing
+unwanted command substitutions from the environment.
+.It "\ \ " Em tabcomplete
+Enables filename completion in the command line editor.
+Typing a tab character will extend the current input word to match a
+filename.
+If more than one filename matches it is only extended to be the common prefix.
+Typing a second tab character will list all the matching names.
+One of the editing modes, either
+.Fl E
+or
+.Fl V ,
+must be enabled for this to work.
+.El
+.Ss Lexical Structure
+The shell reads input in terms of lines from a file and breaks it up into
+words at whitespace (blanks and tabs), and at certain sequences of
+characters that are special to the shell called
+.Dq operators .
+There are two types of operators: control operators and redirection
+operators (their meaning is discussed later).
+The following is a list of operators:
+.Bl -ohang -offset indent
+.It "Control operators:"
+.Dl & && \&( \&) \&; ;; ;& \&| || <newline>
+.It "Redirection operators:"
+.Dl < > >| << >> <& >& <<- <>
+.El
+.Ss Quoting
+Quoting is used to remove the special meaning of certain characters or
+words to the shell, such as operators, whitespace, or keywords.
+There are four types of quoting:
+matched single quotes,
+matched double quotes,
+backslash,
+and
+dollar preceding matched single quotes (enhanced C style strings.)
+.Ss Backslash
+An unquoted backslash preserves the literal meaning of the following
+character, with the exception of
+.Aq newline .
+An unquoted backslash preceding a
+.Aq newline
+is treated as a line continuation, the two characters are simply removed.
+.Ss Single Quotes
+Enclosing characters in single quotes preserves the literal meaning of all
+the characters (except single quotes, making it impossible to put
+single quotes in a single-quoted string).
+.Ss Double Quotes
+Enclosing characters within double quotes preserves the literal
+meaning of all characters except dollar sign
+.Pq Li \&$ ,
+backquote
+.Pq Li \&` ,
+and backslash
+.Pq Li \e .
+The backslash inside double quotes is historically weird, and serves to
+quote only the following characters (and these not in all contexts):
+.Dl $ ` \*q \e <newline> ,
+where a backslash newline is a line continuation as above.
+Otherwise it remains literal.
+.\"
+.\"
+.Ss Dollar Single Quotes ( Li \&$'...' )
+.\"
+.Bd -filled -offset indent
+.Bf Em
+Note: this form of quoting is still somewhat experimental,
+and yet to be included in the POSIX standard.
+This implementation is based upon the current proposals for
+standardization, and is subject to change should the eventual
+adopted text differ.
+.Ef
+.Ed
+.Pp
+Enclosing characters in a matched pair of single quotes, with the
+first immediately preceded by an unquoted dollar sign
+.Pq Li \&$
+provides a quoting mechanism similar to single quotes, except
+that within the sequence of characters, any backslash
+.Pq Li \e ,
+is an escape character, which causes the following character to
+be treated specially.
+Only a subset of the characters that can occur in the string
+are defined after a backslash, others are reserved for future
+definition, and currently generate a syntax error if used.
+The escape sequences are modeled after the similar sequences
+in strings in the C programming language, with some extensions.
+.Pp
+The following characters are treated literally when following
+the escape character (backslash):
+.Dl \e \&' \(dq
+The sequence
+.Dq Li \e\e
+allows the escape character (backslash) to appear in the string literally.
+.Dq Li \e'
+allows a single quote character into the string, such an
+escaped single quote does not terminate the quoted string.
+.Dq Li \e\(dq
+is for compatibility with C strings, the double quote has
+no special meaning in a shell C-style string,
+and does not need to be escaped, but may be.
+.Pp
+A newline following the escape character is treated as a line continuation,
+like the same sequence in a double quoted string,
+or when not quoted \(en
+the two characters, the backslash escape and the newline,
+are removed from the input string.
+.Pp
+The following characters, when escaped, are converted in a
+manner similar to the way they would be in a string in the C language:
+.Dl a b e f n r t v
+An escaped
+.Sq a
+generates an alert (or
+.Sq BEL )
+character, that is, control-G, or 0x07.
+In a similar way,
+.Sq b
+is backspace (0x08),
+.Sq e
+(an extension to C) is escape (0x1B),
+.Sq f
+is form feed (0x0C),
+.Sq n
+is newline (or line feed, 0x0A),
+.Sq r
+is return (0x0D),
+.Sq t
+is horizontal tab (0x09),
+and
+.Sq v
+is vertical tab (0x13).
+.Pp
+In addition to those there are 5 forms that need additional
+data, which is obtained from the subsequent characters.
+An escape
+.Pq Li \e
+followed by one, two or three, octal digits
+.Po So 0 Sc Ns \&.. Ns So 7 Sc Ns Pc
+is processed to form an 8 bit character value.
+If only one or two digits are present, the following
+character must be something other than an octal digit.
+It is safest to always use all 3 digits, with leading
+zeros if needed.
+If all three digits are present, the first must be one of
+.So 0 Sc Ns \&.. Ns So 3 Sc .
+.Pp
+An escape followed by
+.Sq x
+(lower case only) can be followed by one or two
+hexadecimal digits
+.Po So 0 Sc Ns \&.. Ns So 9 Sc , So A Sc Ns \&.. Ns So F Sc , or So a Sc Ns \&.. Ns So f Sc . Pc
+As with octal, if only one hex digit is present, the following
+character must be something other than a hex digit,
+so always giving 2 hex digits is best.
+However, unlike octal, it is unspecified in the standard
+how many hex digits can be consumed.
+This
+.Nm
+takes at most two, but other shells will continue consuming
+characters as long as they remain valid hex digits.
+Consequently, users should ensure that the character
+following the hex escape sequence is something other than
+a hex digit.
+One way to achieve this is to end the
+.Li $'...'
+string immediately
+after the final hex digit, and then, immediately start
+another, so
+.Dl \&$'\ex33'$'4...'
+always gives the character with value 0x33
+.Pq Sq 3 ,
+followed by the character
+.Sq 4 ,
+whereas
+.Dl \&$'\ex334'
+in some other shells would be the hex value 0x334 (10, or more, bits).
+.Pp
+There are two escape sequences beginning with
+.Sq Li \eu
+or
+.Sq Li \eU .
+The former is followed by from 1 to 4 hex digits, the latter by
+from 1 to 8 hex digits.
+Leading zeros can be used to pad the sequences to the maximum
+permitted length, to avoid any possible ambiguity problem with
+the following character, and because there are some shells that
+insist on exactly 4 (or 8) hex digits.
+These sequences are evaluated to form the value of a Unicode code
+point, which is then encoded into UTF-8 form, and entered into the
+string.
+(The code point should be converted to the appropriate
+code point value for the corresponding character in the character
+set given by the current locale, or perhaps the locale in use
+when the shell was started, but is not... currently.)
+Not all values that are possible to write are valid, values that
+specify (known) invalid Unicode code points will be rejected, or
+simply produce
+.Sq \&? .
+.Pp
+Lastly, as another addition to what is available in C, the escape
+character (backslash), followed by
+.Sq c
+(lower case only) followed by one additional character, which must
+be an alphabetic character (a letter), or one of the following:
+.Dl \&@ \&[ \&\e \&] \&^ \&_ \&?
+Other than
+.Sq Li \ec?
+the value obtained is the least significant 5 bits of the
+ASCII value of the character following the
+.Sq Li \ec
+escape sequence.
+That is what is commonly known as the
+.Dq control
+character obtained from the given character.
+The escape sequence
+.Sq Li \ec?
+yields the ASCII DEL character (0x7F).
+Note that to obtain the ASCII FS character (0x1C) this way,
+.Pq "that is control-\e"
+the trailing
+.Sq Li \e
+must be escaped itself, and so for this one case, the full
+escape sequence is
+.Dq Li \ec\e\e .
+The sequence
+.Dq Li \ec\e Ns Ar X\^
+where
+.Sq Ar X\^
+is some character other than
+.Sq Li \e
+is reserved for future use, its meaning is unspecified.
+In this
+.Nm
+an error is generated.
+.Pp
+If any of the preceding escape sequences generate the value
+.Sq \e0
+(a NUL character) that character, and all that follow in the same
+.Li $'...'
+string, are omitted from the resulting word.
+.Pp
+After the
+.Li $'...'
+string has had any included escape sequences
+converted, it is treated as if it had been a single quoted string.
+.\"
+.\"
+.Ss Reserved Words
+.\"
+Reserved words are words that have special meaning to the
+shell and are recognized at the beginning of a line and
+after a control operator.
+The following are reserved words:
+.Bl -column while while while while -offset indent
+.It Ic \&! Ta Ic \&{ Ta Ic \&} Ta Ic case
+.It Ic do Ta Ic done Ta Ic elif Ta Ic else
+.It Ic esac Ta Ic fi Ta Ic for Ta Ic if
+.It Ic in Ta Ic then Ta Ic until Ta Ic while
+.El
+.Pp
+Their meanings are discussed later.
+.\"
+.\"
+.Ss Aliases
+.\"
+An alias is a name and corresponding value set using the
+.Ic alias
+built-in command.
+Whenever a reserved word (see above) may occur,
+and after checking for reserved words, the shell
+checks the word to see if it matches an alias.
+If it does, it replaces it in the input stream with its value.
+For example, if there is an alias called
+.Dq lf
+with the value
+.Dq "ls -F" ,
+then the input:
+.Pp
+.Dl lf foobar Aq return
+.Pp
+would become
+.Pp
+.Dl ls -F foobar Aq return
+.Pp
+Aliases provide a convenient way for naive users to create shorthands for
+commands without having to learn how to create functions with arguments.
+They can also be used to create lexically obscure code.
+This use is strongly discouraged.
+.\"
+.Ss Commands
+.\"
+The shell interprets the words it reads according to a language, the
+specification of which is outside the scope of this man page (refer to the
+BNF in the POSIX 1003.2 document).
+Essentially though, a line is read and if the first
+word of the line (or after a control operator) is not a reserved word,
+then the shell has recognized a simple command.
+Otherwise, a complex
+command or some other special construct may have been recognized.
+.\"
+.\"
+.Ss Simple Commands
+.\"
+If a simple command has been recognized, the shell performs
+the following actions:
+.Bl -enum -offset indent
+.It
+Leading words of the form
+.Dq Ar name Ns Li = Ns Ar value
+are stripped off, the value is expanded, as described below,
+and the results are assigned to the environment of the simple command.
+Redirection operators and their arguments (as described below) are
+stripped off and saved for processing in step 3 below.
+.It
+The remaining words are expanded as described in the
+.Sx Word Expansions
+section below.
+The first remaining word is considered the command name and the
+command is located.
+Any remaining words are considered the arguments of the command.
+If no command name resulted, then the
+.Dq Ar name Ns Li = Ns Ar value
+variable assignments recognized in item 1 affect the current shell.
+.It
+Redirections are performed, from first to last, in the order given,
+as described in the next section.
+.El
+.\"
+.\"
+.Ss Redirections
+.\"
+Redirections are used to change where a command reads its input or sends
+its output.
+In general, redirections open, close, or duplicate an
+existing reference to a file.
+The overall format used for redirection is:
+.Pp
+.Dl Oo Ar n Oc Ns Va redir-op Ar file
+.Pp
+where
+.Va redir-op
+is one of the redirection operators mentioned previously.
+The following is a list of the possible redirections.
+The
+.Op Ar n
+is an optional number, as in
+.Sq Li 3
+(not
+.Li [3] ) ,
+that refers to a file descriptor.
+If present it must occur immediately before the redirection
+operator, with no intervening white space, and becomes a
+part of that operator.
+.Bl -tag -width aaabsfiles -offset indent
+.It Oo Ar n Oc Ns Ic > Ar file
+Redirect standard output (or
+.Ar n )
+to
+.Ar file .
+.It Oo Ar n Oc Ns Ic >| Ar file
+The same, but override the
+.Fl C
+option.
+.It Oo Ar n Oc Ns Ic >> Ar file
+Append standard output (or
+.Ar n )
+to
+.Ar file .
+.It Oo Ar n Oc Ns Ic < Ar file
+Redirect standard input (or
+.Ar n )
+from
+.Ar file .
+.It Oo Ar n1 Oc Ns Ic <& Ns Ar n2
+Duplicate standard input (or
+.Ar n1 )
+from file descriptor
+.Ar n2 .
+.Ar n2
+is expanded if not a digit string, the result must be a number.
+.It Oo Ar n Oc Ns Ic <&-
+Close standard input (or
+.Ar n ) .
+.It Oo Ar n1 Oc Ns Ic >& Ns Ar n2
+Duplicate standard output (or
+.Ar n1 )
+to
+.Ar n2 .
+.It Oo Ar n Oc Ns Ic >&-
+Close standard output (or
+.Ar n ) .
+.It Oo Ar n Oc Ns Ic <> Ar file
+Open
+.Ar file
+for reading and writing on standard input (or
+.Ar n ) .
+.El
+.Pp
+The following redirection is often called a
+.Dq here-document .
+.Bd -unfilled -offset indent
+.Oo Ar n Oc Ns Ic << Ar delimiter
+.Li \&... here-doc-text ...
+.Ar delimiter
+.Ed
+.Pp
+The
+.Dq here-doc-text
+starts immediately after the next unquoted newline character following
+the here-document redirection operator.
+If there is more than one here-document redirection on the same
+line, then the text for the first (from left to right) is read
+first, and subsequent here-doc-text for later here-document redirections
+follows immediately after, until all such redirections have been
+processed.
+.Pp
+All the text on successive lines up to the delimiter,
+which must appear on a line by itself, with nothing other
+than an immediately following newline, is
+saved away and made available to the command on standard input, or file
+descriptor n if it is specified.
+If the delimiter as specified on the initial line is
+quoted, then the here-doc-text is treated literally; otherwise, the text is
+treated much like a double quoted string, except that
+.Sq Li \(dq
+characters have no special meaning, and are not escaped by
+.Sq Li \&\e ,
+and is subjected to parameter expansion, command substitution, and arithmetic
+expansion as described in the
+.Sx Word Expansions
+section below.
+If the operator is
+.Ic <<-
+instead of
+.Ic << ,
+then leading tabs in all lines in the here-doc-text, including before the
+end delimiter, are stripped.
+If the delimiter is not quoted, lines in here-doc-text that end with
+an unquoted
+.Li \e
+are joined to the following line, the
+.Li \e
+and following
+newline are simply removed while reading the here-document,
+which thus guarantees
+that neither of those lines can be the end delimiter.
+.Pp
+It is a syntax error for the end of the input file (or string) to be
+reached before the delimiter is encountered.
+.\"
+.\"
+.Ss Search and Execution
+.\"
+There are three types of commands: shell functions, built-in commands, and
+normal programs \(em and the command is searched for (by name) in that order.
+A command that contains a slash
+.Sq \&/
+in its name is always a normal program.
+They each are executed in a different way.
+.Pp
+When a shell function is executed, all of the shell positional parameters
+(note: excluding
+.Li 0 , \" $0
+which is a special, not positional, parameter, and remains unchanged)
+are set to the arguments of the shell function.
+The variables which are explicitly placed in the environment of
+the command (by placing assignments to them before the function name) are
+made local to the function and are set to the values given,
+and exported for the benefit of programs executed with the function.
+Then the command given in the function definition is executed.
+The positional parameters, and local variables, are restored to
+their original values when the command completes.
+This all occurs within the current shell, and the function
+can alter variables, or other settings, of the shell, but
+not the positional parameters nor their related special parameters.
+.Pp
+Shell built-ins are executed internally to the shell, without spawning a
+new process.
+.Pp
+Otherwise, if the command name doesn't match a function or built-in, the
+command is searched for as a normal program in the file system (as
+described in the next section).
+When a normal program is executed, the shell runs the program,
+passing the arguments and the environment to the program.
+If the program is not a normal executable file, and if it does
+not begin with the
+.Dq magic number
+whose ASCII representation is
+.Dq Li "#!" ,
+so
+.Xr execve 2
+returns
+.Er ENOEXEC
+then) the shell will interpret the program in a sub-shell.
+The child shell will reinitialize itself in this case,
+so that the effect will be as if a
+new shell had been invoked to handle the ad-hoc shell script, except that
+the location of hashed commands located in the parent shell will be
+remembered by the child.
+.Pp
+Note that previous versions of this document and the source code itself
+misleadingly and sporadically refer to a shell script without a magic
+number as a
+.Dq shell procedure .
+.\"
+.\"
+.Ss Path Search
+.\"
+When locating a command, the shell first looks to see if it has a shell
+function by that name.
+Then it looks for a built-in command by that name.
+If a built-in command is not found, one of two things happen:
+.Bl -enum
+.It
+Command names containing a slash are simply executed without performing
+any searches.
+.It
+Otherwise, the shell searches each entry in
+.Ev PATH
+in turn for the command.
+The value of the
+.Ev PATH
+variable should be a series of entries separated by colons.
+Each entry consists of a directory name.
+The current directory may be indicated
+implicitly by an empty directory name, or explicitly by a single period.
+If a directory searched contains an executable file with the same
+name as the command given,
+the search terminates, and that program is executed.
+.El
+.Ss Command Exit Status
+Each command has an exit status that can influence the behavior
+of other shell commands.
+The paradigm is that a command exits
+with zero in normal cases, or to indicate success,
+and non-zero for failure,
+error, or a false indication.
+The man page for each command
+should indicate the various exit codes and what they mean.
+Additionally, the built-in commands return exit codes, as does
+an executed shell function.
+.Pp
+If a command consists entirely of variable assignments then the
+exit status of the command is that of the last command substitution
+if any, otherwise 0.
+.Pp
+If redirections are present, and any fail to be correctly performed,
+any command present is not executed, and an exit status of 2
+is returned.
+.Ss Complex Commands
+Complex commands are combinations of simple commands with control
+operators or reserved words, together creating a larger complex command.
+Overall, a shell program is a:
+.Bl -tag -width XpipelineX
+.It list
+Which is a sequence of one or more AND-OR lists.
+.It "AND-OR list"
+is a sequence of one or more pipelines.
+.It pipeline
+is a sequence of one or more commands.
+.It command
+is one of a simple command, a compound command, or a function definition.
+.It "simple command"
+has been explained above, and is the basic building block.
+.It "compound command"
+provides mechanisms to group lists to achieve different effects.
+.It "function definition"
+allows new simple commands to be created as groupings of existing commands.
+.El
+.Pp
+Unless otherwise stated, the exit status of a list
+is that of the last simple command executed by the list.
+.\"
+.\"
+.Ss Pipelines
+.\"
+A pipeline is a sequence of one or more commands separated
+by the control operator
+.Sq Ic \(ba ,
+and optionally preceded by the
+.Dq Ic \&!
+reserved word.
+Note that
+.Sq Ic \(ba
+is an operator, and so is recognized anywhere it appears unquoted,
+it does not require surrounding white space or other syntax elements.
+On the other hand
+.Dq Ic \&!
+being a reserved word, must be separated from adjacent words by
+white space (or other operators, perhaps redirects) and is only
+recognized as the reserved word when it appears in a command word
+position (such as at the beginning of a pipeline.)
+.Pp
+The standard output of all but
+the last command in the sequence is connected to the standard input
+of the next command.
+The standard output of the last
+command is inherited from the shell, as usual,
+as is the standard input of the first command.
+.Pp
+The format for a pipeline is:
+.Pp
+.Dl [!] command1 Op Li \&| command2 No ...
+.Pp
+The standard output of command1 is connected to the standard input of
+command2.
+The standard input, standard output, or both of each command is
+considered to be assigned by the pipeline before any redirection specified
+by redirection operators that are part of the command are performed.
+.Pp
+If the pipeline is not in the background (discussed later), the shell
+waits for all commands to complete.
+.Pp
+The commands in a pipeline can either be simple commands,
+or one of the compound commands described below.
+The simplest case of a pipeline is a single simple command.
+.Pp
+If the
+.Ic pipefail
+option was set when a pipeline was started,
+the pipeline status is the status of
+the last (lexically last, i.e.: rightmost) command in the
+pipeline to exit with non-zero exit status, or zero, if,
+and only if, all commands in the pipeline exited with a status of zero.
+If the
+.Ic pipefail
+option was not set, which is the default state,
+the pipeline status is the exit
+status of the last (rightmost) command in the pipeline,
+and the exit status of any other commands in the pipeline is ignored.
+.Pp
+If the reserved word
+.Dq Ic \&!
+precedes the pipeline, the exit status
+becomes the logical NOT of the pipeline status as determined above.
+That is, if the pipeline status is zero, the exit status is 1;
+if the pipeline status is other than zero, the exit status is zero.
+If there is no
+.Dq Ic \&!
+reserved word, the pipeline status becomes the exit status.
+.Pp
+Because pipeline assignment of standard input or standard output or both
+takes place before redirection, it can be modified by redirection.
+For example:
+.Pp
+.Dl $ command1 2>&1 \&| command2
+.Pp
+sends both the standard output and standard error of command1
+to the standard input of command2.
+.Pp
+Note that unlike some other shells, each process in the pipeline is a
+child of the invoking shell (unless it is a shell built-in, in which case
+it executes in the current shell \(em but any effect it has on the
+environment is wiped).
+.Pp
+A pipeline is a simple case of an AND-OR-list (described below.)
+A
+.Li \&;
+or
+.Aq newline
+terminator causes the preceding pipeline, or more generally,
+the preceding AND-OR-list to be executed sequentially;
+that is, the shell executes the commands, and waits for them
+to finish before proceeding to following commands.
+An
+.Li \&&
+terminator causes asynchronous (background) execution
+of the preceding AND-OR-list (see the next paragraph below).
+The exit status of an asynchronous AND-OR-list is zero.
+The actual status of the commands,
+after they have completed,
+can be obtained using the
+.Ic wait
+built-in command described later.
+.\"
+.\"
+.Ss Background Commands \(em Ic \&&
+.\"
+If a command, pipeline, or AND-OR-list
+is terminated by the control operator ampersand
+.Pq Li \&& ,
+the
+shell executes the command asynchronously \(em that is, the shell does not
+wait for the command to finish before executing the next command.
+.Pp
+The format for running a command in background is:
+.Pp
+.Dl command1 & Op Li command2 & No ...
+.Pp
+If the shell is not interactive, the standard input of an asynchronous
+command is set to
+.Pa /dev/null .
+The process identifier of the most recent command started in the
+background can be obtained from the value of the special parameter
+.Dq Dv \&! \" $!
+(see
+.Sx Special Parameters )
+provided it is accessed before the next asynchronous command is started.
+.\"
+.\"
+.Ss Lists \(em Generally Speaking
+.\"
+A list is a sequence of one or more commands separated by newlines,
+semicolons, or ampersands, and optionally terminated by one of these three
+characters.
+A shell program, which includes the commands given to an
+interactive shell, is a list.
+Each command in such a list is executed when it is fully parsed.
+Another use of a list is as a complete-command,
+which is parsed in its entirety, and then later the commands in
+the list are executed only if there were no parsing errors.
+.Pp
+The commands in a list are executed in the order they are written.
+If command is followed by an ampersand, the shell starts the
+command and immediately proceeds to the next command; otherwise it waits
+for the command to terminate before proceeding to the next one.
+A newline is equivalent to a
+.Sq Li \&;
+when no other operator is present, and the command being input
+could syntactically correctly be terminated at the point where
+the newline is encountered, otherwise it is just whitespace.
+.\"
+.\"
+.Ss AND-OR Lists (Short-Circuit List Operators)
+.\"
+.Dq Li \&&&
+and
+.Dq Li \&||
+are AND-OR list operators.
+After executing the commands that precede the
+.Dq Li \&&&
+the subsequent command is executed
+if and only if the exit status of the preceding command(s) is zero.
+.Dq Li \&||
+is similar, but executes the subsequent command if and only if the exit status
+of the preceding command is nonzero.
+If a command is not executed, the exit status remains unchanged
+and the following AND-OR list operator (if any) uses that status.
+.Dq Li \&&&
+and
+.Dq Li \&||
+both have the same priority.
+Note that these operators are left-associative, so
+.Dl true || echo bar && echo baz
+writes
+.Dq baz
+and nothing else.
+This is not the way it works in C.
+.\"
+.\"
+.Ss Flow-Control Constructs \(em Ic if , while , until , for , case
+.\"
+These commands are instances of compound commands.
+The syntax of the
+.Ic if
+command is
+.Bd -literal -offset indent
+.Ic if Ar list
+.Ic then Ar list
+.Ic [ elif Ar list
+.Ic then Ar list ] No ...
+.Ic [ else Ar list ]
+.Ic fi
+.Ed
+.Pp
+The first list is executed, and if the exit status of that list is zero,
+the list following the
+.Ic then
+is executed.
+Otherwise the list after an
+.Ic elif
+(if any) is executed and the process repeats.
+When no more
+.Ic elif
+reserved words, and accompanying lists, appear,
+the list after the
+.Ic else
+reserved word, if any, is executed.
+.Pp
+The syntax of the
+.Ic while
+command is
+.Bd -literal -offset indent
+.Ic while Ar list
+.Ic do Ar list
+.Ic done
+.Ed
+.Pp
+The two lists are executed repeatedly while the exit status of the
+first list is zero.
+The
+.Ic until
+command is similar, but has the word
+.Ic until
+in place of
+.Ic while ,
+which causes it to repeat until the exit status of the first list is zero.
+.Pp
+The syntax of the
+.Ic for
+command is
+.Bd -literal -offset indent
+.Ic for Ar variable Op Ic in Ar word No ...
+.Ic do Ar list
+.Ic done
+.Ed
+.Pp
+The words are expanded, or
+.Li \*q$@\*q
+if
+.Ic in
+(and the following words) is not present,
+and then the list is executed repeatedly with the
+variable set to each word in turn.
+If
+.Ic in
+appears after the variable, but no words are
+present, the list is not executed, and the exit status is zero.
+.Ic do
+and
+.Ic done
+may be replaced with
+.Sq Ic \&{
+and
+.Sq Ic \&} ,
+but doing so is non-standard and not recommended.
+.Pp
+The syntax of the
+.Ic break
+and
+.Ic continue
+commands is
+.Bd -literal -offset indent
+.Ic break Op Ar num
+.Ic continue Op Ar num
+.Ed
+.Pp
+.Ic break
+terminates the
+.Ar num
+innermost
+.Ic for , while ,
+or
+.Ic until
+loops.
+.Ic continue
+breaks execution of the
+.Ar num\^ Ns -1
+innermost
+.Ic for , while ,
+or
+.Ic until
+loops, and then continues with the next iteration of the enclosing loop.
+These are implemented as special built-in commands.
+The parameter
+.Ar num ,
+if given, must be an unsigned positive integer (greater than zero).
+If not given, 1 is used.
+.Pp
+The syntax of the
+.Ic case
+command is
+.Bd -literal -offset indent
+.Ic case Ar word Ic in
+.Oo Ic \&( Oc Ar pattern Ns Ic \&) Oo Ar list Oc Ic \&;&
+.Oo Ic \&( Oc Ar pattern Ns Ic \&) Oo Ar list Oc Ic \&;;
+.No \&...
+.Ic esac
+.Ed
+.Pp
+The pattern can actually be one or more patterns (see
+.Sx Shell Patterns
+described later), separated by
+.Dq \(or
+characters.
+.Pp
+.Ar word
+is expanded and matched against each
+.Ar pattern
+in turn,
+from first to last,
+with each pattern being expanded just before the match is attempted.
+When a match is found, pattern comparisons cease, and the associated
+.Ar list ,
+if given,
+is evaluated.
+If the list is terminated with
+.Dq Ic \&;&
+execution then falls through to the following list, if any,
+without evaluating its pattern, or attempting a match.
+When a list terminated with
+.Dq Ic \&;;
+has been executed, or when
+.Ic esac
+is reached, execution of the
+.Ic case
+statement is complete.
+The exit status is that of the last command executed
+from the last list evaluated, if any, or zero otherwise.
+.\"
+.\"
+.Ss Grouping Commands Together
+.\"
+Commands may be grouped by writing either
+.Dl Ic \&( Ns Ar list Ns Ic \&)
+or
+.Dl Ic \&{ Ar list Ns Ic \&; \&}
+These also form compound commands.
+.Pp
+Note that while parentheses are operators, and do not require
+any extra syntax, braces are reserved words, so the opening brace
+must be followed by white space (or some other operator), and the
+closing brace must occur in a position where a new command word might
+otherwise appear.
+.Pp
+The first of these executes the commands in a sub-shell.
+Built-in commands grouped into a
+.Li \&( Ns Ar list Ns \&)
+will not affect the current shell.
+The second form does not fork another shell so is slightly more efficient,
+and allows for commands which do affect the current shell.
+Grouping commands together this way allows you to redirect
+their output as though they were one program:
+.Bd -literal -offset indent
+{ echo -n \*qhello \*q ; echo \*qworld\*q ; } > greeting
+.Ed
+.Pp
+Note that
+.Dq Ic }
+must follow a control operator (here,
+.Dq Ic \&; )
+so that it is recognized as a reserved word and not as another command argument.
+.\"
+.\"
+.Ss Functions
+.\"
+The syntax of a function definition is
+.Pp
+.Dl Ar name Ns Ic \&() Ar command Op Ar redirect No ...
+.Pp
+A function definition is an executable statement; when executed it
+installs a function named name and returns an exit status of zero.
+The command is normally a list enclosed between
+.Dq {
+and
+.Dq } .
+The standard syntax also allows the command to be any of the other
+compound commands, including a sub-shell, all of which are supported.
+As an extension, this shell also allows a simple command
+(or even another function definition) to be
+used, though users should be aware this is non-standard syntax.
+This means that
+.Dl l() ls \*q$@\*q
+works to make
+.Dq l
+an alternative name for the
+.Ic ls
+command.
+.Pp
+If the optional redirect, (see
+.Sx Redirections ) ,
+which may be of any of the normal forms,
+is given, it is applied each time the
+function is called.
+This means that a simple
+.Dq Hello World
+function might be written (in the extended syntax) as:
+.Bd -literal -offset indent
+hello() cat <<EOF
+Hello World!
+EOF
+.Ed
+.Pp
+To be correctly standards conforming this should be re-written as:
+.Bd -literal -offset indent
+hello() { cat; } <<EOF
+Hello World!
+EOF
+.Ed
+.Pp
+Note the distinction between those forms, and
+.Bd -literal -offset indent
+hello() { cat <<EOF
+Hello World!
+EOF
+\&}
+.Ed
+.Pp
+which reads and processes the here-document
+each time the shell executes the function, and which applies
+that input only to the cat command, not to any other commands
+that might appear in the function.
+.Pp
+Variables may be declared to be local to a function by using the
+.Ic local
+command.
+This should usually appear as the first statement of a function,
+though
+.Ic local
+is an executable command which can be used anywhere in a function.
+See
+.Sx Built-ins
+below for its definition.
+.Pp
+The function completes after having executed
+.Ar command
+with exit status set to the status returned by
+.Ar command .
+If
+.Ar command
+is a compound-command
+it can use the
+.Ic return
+command (see
+.Sx Built-ins
+below)
+to finish before completing all of
+.Ar command .
+.Ss Variables and Parameters
+The shell maintains a set of parameters.
+A parameter denoted by a name is called a variable.
+When starting up, the shell turns all the environment
+variables into shell variables, and exports them.
+New variables can be set using the form
+.Pp
+.Dl Ar name Ns Li = Ns Ar value
+.Pp
+Variables set by the user must have a name consisting solely of
+alphabetics, numerics, and underscores \(em the first of which must not be
+numeric.
+A parameter can also be denoted by a number or a special
+character as explained below.
+.Ss Positional Parameters
+A positional parameter is a parameter denoted by a number (n > 0).
+The shell sets these initially to the values of its command line arguments
+that follow the name of the shell script.
+The
+.Ic set
+built-in can also be used to set or reset them, and
+.Ic shift
+can be used to manipulate the list.
+.Pp
+To refer to the 10th (and later) positional parameters,
+the form
+.Li \&${ Ns Ar n Ns Li \&}
+must be used.
+Without the braces, a digit following
+.Dq $
+can only refer to one of the first 9 positional parameters,
+or the special parameter
+.Dv 0 . \" $0
+The word
+.Dq Li $10
+is treated identically to
+.Dq Li ${1}0 .
+.\"
+.\"
+.Ss Special Parameters
+.\"
+A special parameter is a parameter denoted by one of the following special
+characters.
+The value of the parameter is listed next to its character.
+.Bl -tag -width thinhyphena
+.It Dv *
+Expands to the positional parameters, starting from one.
+When the
+expansion occurs within a double-quoted string it expands to a single
+field with the value of each parameter separated by the first character of
+the
+.Ev IFS
+variable, or by a
+.Aq space
+if
+.Ev IFS
+is unset.
+.It Dv @ \" $@
+Expands to the positional parameters, starting from one.
+When the expansion occurs within double quotes, each positional
+parameter expands as a separate argument.
+If there are no positional parameters, the
+expansion of @ generates zero arguments, even when
+.Dv $@
+is double-quoted.
+What this basically means, for example, is
+if
+.Li $1
+is
+.Dq abc
+and
+.Li $2
+is
+.Dq def\ ghi ,
+then
+.Li \*q$@\*q
+expands to
+the two arguments:
+.Pp
+.Sm off
+.Dl \*q abc \*q \ \*q def\ ghi \*q
+.Sm on
+.It Dv #
+Expands to the number of positional parameters.
+.It Dv \&?
+Expands to the exit status of the most recent pipeline.
+.It Dv \- No (hyphen, or minus)
+Expands to the current option flags (the single-letter
+option names concatenated into a string) as specified on
+invocation, by the set built-in command, or implicitly
+by the shell.
+.It Dv $
+Expands to the process ID of the invoked shell.
+A sub-shell retains the same value of
+.Dv $
+as its parent.
+.It Dv \&!
+Expands to the process ID of the most recent background
+command executed from the current shell.
+For a pipeline, the process ID is that of the last command in the pipeline.
+If no background commands have yet been started by the shell, then
+.Dq Dv \&!
+will be unset.
+Once set, the value of
+.Dq Dv \&!
+will be retained until another background command is started.
+.It Dv 0 No (zero) \" $0
+Expands to the name of the shell or shell script.
+.El
+.\"
+.\"
+.Ss Word Expansions
+.\"
+This section describes the various expansions that are performed on words.
+Not all expansions are performed on every word, as explained later.
+.Pp
+Tilde expansions, parameter expansions, command substitutions, arithmetic
+expansions, and quote removals that occur within a single word expand to a
+single field.
+It is only field splitting or pathname expansion that can
+create multiple fields from a single word.
+The single exception to this
+rule is the expansion of the special parameter
+.Dv @ \" $@
+within double quotes, as was described above.
+.Pp
+The order of word expansion is:
+.Bl -enum
+.It
+Tilde Expansion, Parameter Expansion, Command Substitution,
+Arithmetic Expansion (these all occur at the same time).
+.It
+Field Splitting is performed on fields
+generated by step (1) unless the
+.Ev IFS
+variable is null.
+.It
+Pathname Expansion (unless set
+.Fl f
+is in effect).
+.It
+Quote Removal.
+.El
+.Pp
+The $ character is used to introduce parameter expansion, command
+substitution, or arithmetic evaluation.
+.Ss Tilde Expansion (substituting a user's home directory)
+A word beginning with an unquoted tilde character (~) is
+subjected to tilde expansion.
+Provided all of the subsequent characters in the word are unquoted
+up to an unquoted slash (/)
+or when in an assignment or not in posix mode, an unquoted colon (:),
+or if neither of those appear, the end of the word,
+they are treated as a user name
+and are replaced with the pathname of the named user's home directory.
+If the user name is missing (as in
+.Pa ~/foobar ) ,
+the tilde is replaced with the value of the
+.Dv HOME
+variable (the current user's home directory).
+.Pp
+In variable assignments,
+an unquoted tilde immediately after the assignment operator (=), and
+each unquoted tilde immediately after an unquoted colon in the value
+to be assigned is also subject to tilde expansion as just stated.
+.\"
+.\"
+.Ss Parameter Expansion
+.\"
+The format for parameter expansion is as follows:
+.Pp
+.Dl ${ Ns Ar expression Ns Li }
+.Pp
+where
+.Ar expression
+consists of all characters until the matching
+.Sq Li } .
+Any
+.Sq Li }
+escaped by a backslash or within a quoted string, and characters in
+embedded arithmetic expansions, command substitutions, and variable
+expansions, are not examined in determining the matching
+.Sq Li } .
+.Pp
+The simplest form for parameter expansion is:
+.Pp
+.Dl ${ Ns Ar parameter Ns Li }
+.Pp
+The value, if any, of
+.Ar parameter
+is substituted.
+.Pp
+The parameter name or symbol can be enclosed in braces,
+which are optional in this simple case,
+except for positional parameters with more than one digit or
+when parameter is followed by a character that could be interpreted as
+part of the name.
+If a parameter expansion occurs inside double quotes:
+.Bl -enum
+.It
+pathname expansion is not performed on the results of the expansion;
+.It
+field splitting is not performed on the results of the
+expansion, with the exception of the special rules for
+.Dv @ . \" $@
+.El
+.Pp
+In addition, a parameter expansion where braces are used,
+can be modified by using one of the following formats.
+If the
+.Sq Ic \&:
+is omitted in the following modifiers, then the test in the expansion
+applies only to unset parameters, not null ones.
+.Bl -tag -width aaparameterwordaaaaa
+.It Li ${ Ns Ar parameter Ns Ic :- Ns Ar word Ns Li }
+.Sy Use Default Values.
+If
+.Ar parameter
+is unset or null, the expansion of
+.Ar word
+is substituted; otherwise, the value of
+.Ar parameter
+is substituted.
+.It Li ${ Ns Ar parameter Ns Ic := Ns Ar word Ns Li }
+.Sy Assign Default Values.
+If
+.Ar parameter
+is unset or null, the expansion of
+.Ar word
+is assigned to
+.Ar parameter .
+In all cases, the final value of
+.Ar parameter
+is substituted.
+Only variables, not positional parameters or special
+parameters, can be assigned in this way.
+.It Li ${ Ns Ar parameter Ns Ic :? Ns Oo Ar word\^ Oc Ns Li }
+.Sy Indicate Error if Null or Unset.
+If
+.Ar parameter
+is unset or null, the expansion of
+.Ar word
+(or a message indicating it is unset if
+.Ar word
+is omitted)
+is written to standard error and a non-interactive shell exits with
+a nonzero exit status.
+An interactive shell will not exit, but any associated command(s) will
+not be executed.
+If the
+.Ar parameter
+is set, its value is substituted.
+.It Li ${ Ns Ar parameter Ns Ic :+ Ns Ar word Ns Li }
+.Sy Use Alternative Value.
+If
+.Ar parameter
+is unset or null, null is substituted;
+otherwise, the expansion of
+.Ar word
+is substituted.
+The value of
+.Ar parameter
+.Em is not used
+in this expansion.
+.It Li ${ Ns Ic # Ns Ar parameter Ns Li }
+.Sy String Length.
+The length in characters of the value of
+.Ar parameter .
+.El
+.Pp
+The following four varieties of parameter expansion provide for substring
+processing.
+In each case, pattern matching notation (see
+.Sx Shell Patterns ) ,
+rather than regular expression notation, is used to evaluate the patterns.
+If parameter is
+.Dv *
+or
+.Dv @ , \" $@
+the result of the expansion is unspecified.
+Enclosing the full parameter expansion string in double quotes does not
+cause the following four varieties of pattern characters to be quoted,
+whereas quoting characters within the braces has this effect.
+.Bl -tag -width aaparameterwordaaaaa
+.It Li ${ Ns Ar parameter Ns Ic % Ns Ar word Ns Li }
+.Sy Remove Smallest Suffix Pattern.
+The
+.Ar word
+is expanded to produce a pattern.
+The parameter expansion then results in
+.Ar parameter ,
+with the
+smallest portion of the suffix matched by the pattern deleted.
+If the
+.Ar word
+is to start with a
+.Sq Li \&%
+character, it must be quoted.
+.It Li ${ Ns Ar parameter Ns Ic %% Ns Ar word Ns Li }
+.Sy Remove Largest Suffix Pattern.
+The
+.Ar word
+is expanded to produce a pattern.
+The parameter expansion then results in
+.Ar parameter ,
+with the largest
+portion of the suffix matched by the pattern deleted.
+The
+.Dq Ic %%
+pattern operator only produces different results from the
+.Dq Ic \&%
+operator when the pattern contains at least one unquoted
+.Sq Li \&* .
+.It Li ${ Ns Ar parameter Ns Ic \&# Ns Ar word Ns Li }
+.Sy Remove Smallest Prefix Pattern.
+The
+.Ar word
+is expanded to produce a pattern.
+The parameter expansion then results in
+.Ar parameter ,
+with the
+smallest portion of the prefix matched by the pattern deleted.
+If the
+.Ar word
+is to start with a
+.Sq Li \&#
+character, it must be quoted.
+.It Li ${ Ns Ar parameter Ns Ic \&## Ns Ar word Ns Li }
+.Sy Remove Largest Prefix Pattern.
+The
+.Ar word
+is expanded to produce a pattern.
+The parameter expansion then results in
+.Ar parameter ,
+with the largest
+portion of the prefix matched by the pattern deleted.
+This has the same relationship with the
+.Dq Ic \&#
+pattern operator as
+.Dq Ic %%
+has with
+.Dq Ic \&% .
+.El
+.\"
+.\"
+.Ss Command Substitution
+.\"
+Command substitution allows the output of a command to be substituted in
+place of the command (and surrounding syntax).
+Command substitution occurs when a word contains
+a command list enclosed as follows:
+.Pp
+.Dl Ic $( Ns Ar list Ns Ic \&)
+.Pp
+or the older
+.Pq Dq backquoted
+version, which is best avoided:
+.Pp
+.Dl Ic \&` Ns Ar list Ns Ns Ic \&`
+.Pp
+See the section
+.Sx Complex Commands
+above for the definition of
+.Ic list .
+.Pp
+The shell expands the command substitution by executing the
+.Ar list
+in a sub-shell environment and replacing the command substitution with the
+standard output of the
+.Ar list
+after removing any sequence of one or more
+.Ao newline Ac Ns s
+from the end of the substitution.
+(Embedded
+.Ao newline Ac Ns s
+before
+the end of the output are not removed; however, during field splitting,
+they may be used to separate fields
+.Pq "as spaces usually are"
+depending on the value of
+.Ev IFS
+and any quoting that is in effect.)
+.Pp
+Note that if a command substitution includes commands
+to be run in the background,
+the sub-shell running those commands
+will only wait for them to complete if an appropriate
+.Ic wait
+command is included in the command list.
+However, the shell in which the result of the command substitution
+will be used will wait for both the sub-shell to exit and for the
+file descriptor that was initially standard output for the command
+substitution sub-shell to be closed.
+In some circumstances this might not happen until all processes
+started by the command substitution have finished.
+.\" While the exit of the sub-shell closes its standard output,
+.\" any background process left running may still
+.\" have that file descriptor open.
+.\" This includes yet another sub-shell which might have been
+.\" (almost invisibly) created to wait for some other command to complete,
+.\" and even where the background command has had its
+.\" standard output redirected or closed,
+.\" the waiting sub-shell may still have it open.
+.\" Thus there is no guarantee that the result of a command substitution
+.\" will be available in the shell which is to use it before all of
+.\" the commands started by the command substitution have completed,
+.\" though careful coding can often avoid delays beyond the termination
+.\" of the command substitution sub-shell.
+.\" .Pp
+.\" For example, assuming a script were to contain the following
+.\" code (which could be done better other ways, attempting
+.\" this kind of trickery is not recommended):
+.\" .Bd -literal -offset indent
+.\" if [ "$( printf "Ready? " >&2; read ans; printf "${ans}";
+.\" { sleep 120 >/dev/null && kill $$ >/dev/null 2>&1; }& )" = go ]
+.\" then
+.\" printf Working...
+.\" # more code
+.\" fi
+.\" .Ed
+.\" .Pp
+.\" the
+.\" .Dq Working...
+.\" output will not be printed, and code that follows will never be executed.
+.\" Nor will anything later in the script
+.\" .Po
+.\" unless
+.\" .Dv SIGTERM
+.\" is trapped or ignored
+.\" .Pc .
+.\" .Pp
+.\" The intent is to prompt the user, wait for the user to
+.\" answer, then if the answer was
+.\" .Dq go
+.\" do the appropriate work, but set a 2 minute time limit
+.\" on completing that work.
+.\" If the work is not done by then, kill the shell doing it.
+.\" .Pp
+.\" It will usually not work as written, as while the 2 minute
+.\" .Sq sleep
+.\" has its standard output redirected, as does the
+.\" .Sq kill
+.\" that follows (which also redirects standard error,
+.\" so the user would not see an error if the work were
+.\" completed and there was no parent process left to kill);
+.\" the sub-shell waiting for the
+.\" .Sq sleep
+.\" to finish successfully, so it can start the
+.\" .Sq kill ,
+.\" does not.
+.\" It waits, with standard output still open,
+.\" for the 2 minutes until the sleep is done,
+.\" even though the kill is not going to need that file descriptor,
+.\" and there is nothing else which could.
+.\" The command substitution does not complete until after
+.\" the kill has executed and the background sub-shell
+.\" finishes \(en at which time the shell running it is
+.\" presumably dead.
+.\" .Pp
+.\" Rewriting the background sub-shell part of that as
+.\" .Dl "{ sleep 120 && kill $$ 2>&1; } >/dev/null &"
+.\" would allow this
+.\" .Nm
+.\" to perform as expected, but that is not guaranteed,
+.\" not all shells would behave as planned.
+.\" It is advised to avoid starting background processes
+.\" from within a command substitution.
+.\"
+.\"
+.Ss Arithmetic Expansion
+.\"
+Arithmetic expansion provides a mechanism for evaluating an arithmetic
+expression and substituting its value.
+The format for arithmetic expansion is as follows:
+.Pp
+.Dl Ic $(( Ns Ar expression Ns Ic \&))
+.Pp
+The expression in an arithmetic expansion is treated as if it were in
+double quotes, except that a double quote character inside the expression
+is just a normal character (it quotes nothing.)
+The shell expands all tokens in the expression for parameter expansion,
+command substitution, and quote removal (the only quoting character is
+the backslash
+.Sq \&\e ,
+and only when followed by another
+.Sq \&\e ,
+a dollar sign
+.Sq \&$ ,
+a backquote
+.Sq \&`
+or a newline.)
+.Pp
+Next, the shell evaluates the expanded result as an arithmetic expression
+and substitutes the calculated value of that expression.
+.Pp
+Arithmetic expressions use a syntax similar to that
+of the C language, and are evaluated using the
+.Ql intmax_t
+data type (this is an extension to POSIX, which requires only
+.Ql long
+arithmetic.)
+Shell variables may be referenced by name inside an arithmetic
+expression, without needing a
+.Dq \&$
+sign.
+Variables that are not set, or which have an empty (null string) value,
+used this way evaluate as zero (that is,
+.Dq x
+in arithmetic, as an R-Value, is evaluated as
+.Dq ${x:-0} )
+unless the
+.Nm
+.Fl u
+flag is set, in which case a reference to an unset variable is an error.
+Note that unset variables used in the ${var} form expand to a null
+string, which might result in syntax errors.
+Referencing the value of a variable which is not numeric is an error.
+.Pp
+All of the C expression operators applicable to integers are supported,
+and operate as they would in a C expression.
+Use white space, or parentheses, to disambiguate confusing syntax,
+otherwise, as in C, the longest sequence of consecutive characters
+which make a valid token (operator, variable name, or number) is taken
+to be that token, even if the token designated cannot be used
+and a different interpretation could produce a successful parse.
+This means, as an example, that
+.Dq a+++++b
+is parsed as the gibberish sequence
+.Dq "a ++ ++ + b" ,
+rather than as the valid alternative
+.Dq "a ++ + ++ b" .
+Similarly, separate the
+.Sq \&,
+operator from numbers with white space to avoid the possibility
+of confusion with the decimal indicator in some locales (though
+fractional, or floating-point, numbers are not supported in this
+implementation.)
+.Pp
+It should not be necessary to state that the C operators which
+operate on, or produce, pointer types, are not supported.
+Those include unary
+.Dq \&*
+and
+.Dq \&&
+and the struct and array referencing binary operators:
+.Dq \&. ,
+.Dq \&->
+and
+.Dq \&[ .
+.\"
+.\"
+.Ss White Space Splitting (Field Splitting)
+.\"
+After parameter expansion, command substitution, and
+arithmetic expansion the shell scans the results of
+expansions and substitutions that did not occur in double quotes,
+and
+.Dq Li $@
+even if it did,
+for field splitting and multiple fields can result.
+.Pp
+The shell treats each character of the
+.Ev IFS
+as a delimiter and uses the delimiters to split the results of parameter
+expansion and command substitution into fields.
+.Pp
+Non-whitespace characters in
+.Ev IFS
+are treated strictly as parameter separators.
+So adjacent non-whitespace
+.Ev IFS
+characters will produce empty parameters.
+On the other hand, any sequence of whitespace
+characters that occur in
+.Ev IFS
+(known as
+.Ev IFS
+whitespace)
+can occur, leading and trailing
+.Ev IFS
+whitespace, and any
+.Ev IFS
+whitespace surrounding a non whitespace
+.Ev IFS
+delimiter, is removed.
+Any sequence of
+.Ev IFS
+whitespace characters without a non-whitespace
+.Ev IFS
+delimiter acts as a single field separator.
+.Pp
+If
+.Ev IFS
+is unset it is assumed to contain space, tab, and newline,
+all of which are
+.Ev IFS
+whitespace characters.
+If
+.Ev IFS
+is set to a null string, there are no delimiters,
+and no field splitting occurs.
+.Ss Pathname Expansion (File Name Generation)
+Unless the
+.Fl f
+flag is set, file name generation is performed after word splitting is
+complete.
+Each word is viewed as a series of patterns, separated by slashes.
+The process of expansion replaces the word with the names of all
+existing files whose names can be formed by replacing each pattern with a
+string that matches the specified pattern.
+There are two restrictions on
+this: first, a pattern cannot match a string containing a slash, and
+second, a pattern cannot match a string starting with a period unless the
+first character of the pattern is a period.
+The next section describes the
+patterns used for both Pathname Expansion and the
+.Ic case
+command.
+.Ss Shell Patterns
+A pattern consists of normal characters, which match themselves,
+and meta-characters.
+The meta-characters are
+.Dq \&! ,
+.Dq * ,
+.Dq \&? ,
+and
+.Dq \&[ .
+These characters lose their special meanings if they are quoted.
+When command or variable substitution is performed
+and the dollar sign or backquotes are not double-quoted,
+the value of the variable or the output of
+the command is scanned for these characters and they are turned into
+meta-characters.
+.Pp
+An asterisk
+.Pq Dq *
+matches any string of characters.
+A question mark
+.Pq Dq \&?
+matches any single character.
+A left bracket
+.Pq Dq \&[
+introduces a character class.
+The end of the character class is indicated by a right bracket
+.Pq Dq \&] ;
+if this
+.Dq \&]
+is missing then the
+.Dq \&[
+matches a
+.Dq \&[
+rather than introducing a character class.
+A character class matches any of the characters between the square brackets.
+A named class of characters (see
+.Xr wctype 3 )
+may be specified by surrounding the name with
+.Pq Dq [:
+and
+.Pq Dq :] .
+For example,
+.Pq Dq [[:alpha:]]
+is a shell pattern that matches a single letter.
+A range of characters may be specified using a minus sign
+.Pq Dq \(mi .
+The character class may be complemented
+by making an exclamation mark
+.Pq Dq \&!
+the first character of the character class.
+.Pp
+To include a
+.Dq \&]
+in a character class, make it the first character listed (after the
+.Dq \&! ,
+if any).
+To include a
+.Dq \(mi ,
+make it the first (after !) or last character listed.
+If both
+.Dq \&]
+and
+.Dq \(mi
+are to be included, the
+.Dq \&]
+must be first (after !)
+and the
+.Dq \(mi
+last, in the character class.
+.\"
+.\"
+.Ss Built-ins
+.\"
+This section lists the built-in commands which are built-in because they
+need to perform some operation that can't be performed by a separate
+process.
+Or just because they traditionally are.
+In addition to these, there are several other commands that may
+be built in for efficiency (e.g.
+.Xr printf 1 ,
+.Xr echo 1 ,
+.Xr test 1 ,
+etc).
+.Bl -tag -width 5n
+.It Ic \&: Op Ar arg ...
+A null command that returns a 0 (true) exit value.
+Any arguments or redirects are evaluated, then ignored.
+.\"
+.It Ic \&. Ar file
+The dot command reads and executes the commands from the specified
+.Ar file
+in the current shell environment.
+The file does not need to be executable and is looked up from the directories
+listed in the
+.Ev PATH
+variable if its name does not contain a directory separator
+.Pq Sq / .
+The
+.Ic return
+command (see below)
+can be used for a premature return from the sourced file.
+.Pp
+The POSIX standard has been unclear on how loop control keywords (break
+and continue) behave across a dot command boundary.
+This implementation allows them to control loops surrounding the dot command,
+but obviously such behavior should not be relied on.
+It is now permitted by the standard, but not required.
+.\"
+.It Ic alias Op Ar name Ns Op Li = Ns Ar string ...
+If
+.Ar name Ns Li = Ns Ar string
+is specified, the shell defines the alias
+.Ar name
+with value
+.Ar string .
+If just
+.Ar name
+is specified, the value of the alias
+.Ar name
+is printed.
+With no arguments, the
+.Ic alias
+built-in prints the
+names and values of all defined aliases (see
+.Ic unalias ) .
+.\"
+.It Ic bg Op Ar job ...
+Continue the specified jobs (or the current job if no
+jobs are given) in the background.
+.\"
+.It Ic command Oo Fl pVv Oc Ar command Op Ar arg ...
+Execute the specified command but ignore shell functions when searching
+for it.
+(This is useful when you
+have a shell function with the same name as a command.)
+.Bl -tag -width 5n
+.It Fl p
+search for command using a
+.Ev PATH
+that guarantees to find all the standard utilities.
+.It Fl V
+Do not execute the command but
+search for the command and print the resolution of the
+command search.
+This is the same as the
+.Ic type
+built-in.
+.It Fl v
+Do not execute the command but
+search for the command and print the absolute pathname
+of utilities, the name for built-ins or the expansion of aliases.
+.El
+.\"
+.It Ic cd Oo Fl P Oc Op Ar directory Op Ar replace
+Switch to the specified directory (default
+.Ev $HOME ) .
+If
+.Ar replace
+is specified, then the new directory name is generated by replacing
+the first occurrence of the string
+.Ar directory
+in the current directory name with
+.Ar replace .
+Otherwise if
+.Ar directory
+is
+.Sq Li - ,
+then the current working directory is changed to the previous current
+working directory as set in
+.Ev OLDPWD .
+Otherwise if an entry for
+.Ev CDPATH
+appears in the environment of the
+.Ic cd
+command or the shell variable
+.Ev CDPATH
+is set and the directory name does not begin with a slash,
+and its first (or only) component isn't dot or dot dot,
+then the directories listed in
+.Ev CDPATH
+will be searched for the specified directory.
+The format of
+.Ev CDPATH
+is the same as that of
+.Ev PATH .
+.Pp
+The
+.Fl P
+option instructs the shell to update
+.Ev PWD
+with the specified physical directory path and change to that directory.
+This is the default.
+.Pp
+When the directory changes, the variable
+.Ev OLDPWD
+is set to the working directory before the change.
+.Pp
+Some shells also support a
+.Fl L
+option, which instructs the shell to update
+.Ev PWD
+with the logical path and to change the current directory
+accordingly.
+This is not supported.
+.Pp
+In an interactive shell, the
+.Ic cd
+command will print out the name of the
+directory that it actually switched to if this is different from the name
+that the user gave,
+or always if the
+.Cm cdprint
+option is set.
+The destination may be different either because the
+.Ev CDPATH
+mechanism was used
+.\" or because a symbolic link was crossed.
+or if the
+.Ar replace
+argument was used.
+.\"
+.It Ic eval Ar string ...
+Concatenate all the arguments with spaces.
+Then re-parse and execute the command.
+.\"
+.It Ic exec Op Ar command Op Ar arg ...
+Unless
+.Ar command
+is omitted, the shell process is replaced with the
+specified program (which must be a real program, not a shell built-in or
+function).
+Any redirections on the
+.Ic exec
+command are marked as permanent, so that they are not undone when the
+.Ic exec
+command finishes.
+When the
+.Cm posix
+option is not set,
+file descriptors created via such redirections are marked close-on-exec
+(see
+.Xr open 2
+.Dv O_CLOEXEC
+or
+.Xr fcntl 2
+.Dv F_SETFD /
+.Dv FD_CLOEXEC ) ,
+unless the descriptors refer to the standard input,
+output, or error (file descriptors 0, 1, 2).
+Traditionally Bourne-like shells
+(except
+.Xr ksh 1 ) ,
+made those file descriptors available to exec'ed processes.
+To be assured the close-on-exec setting is off,
+redirect the descriptor to (or from) itself,
+either when invoking a command for which the descriptor is wanted open,
+or by using
+.Ic exec
+(perhaps the same
+.Ic exec
+as opened it, after the open)
+to leave the descriptor open in the shell
+and pass it to all commands invoked subsequently.
+Alternatively, see the
+.Ic fdflags
+command below, which can set, or clear, this, and other,
+file descriptor flags.
+.\"
+.It Ic exit Op Ar exitstatus
+Terminate the shell process.
+If
+.Ar exitstatus
+is given it is used as the exit status of the shell; otherwise the
+exit status of the preceding command (the current value of $?) is used.
+.\"
+.It Ic export Oo Fl nx Oc Ar name Ns Oo =value Oc ...
+.It Ic export Oo Fl x Oc Oo Fl p Oo Ar name ... Oc Oc
+.It Ic export Fl q Oo Fl x Oc Ar name ...
+With no options,
+but one or more names,
+the specified names are exported so that they will appear in the
+environment of subsequent commands.
+With
+.Fl n
+the specified names are un-exported.
+Variables can also be un-exported using the
+.Ic unset
+built in command.
+With
+.Fl x
+(exclude) the specified names are marked not to be exported,
+and any that had been exported, will be un-exported.
+Later attempts to export the variable will be refused.
+Note this does not prevent explicitly exporting a variable
+to a single command, script or function by preceding that
+command invocation by a variable assignment to that variable,
+provided the variable is not also read-only.
+That is
+.Bd -literal -offset indent
+export -x FOO # FOO will now not be exported by default
+export FOO # this command will fail (non-fatally)
+FOO=some_value my_command
+.Ed
+.Pp
+still passes the value
+.Pq Li FOO=some_value
+to
+.Li my_command
+through the environment.
+.Pp
+The shell allows the value of a variable to be set at the
+same time it is exported (or unexported, etc) by writing
+.Pp
+.Dl export [-nx] name=value
+.Pp
+With no arguments the export command lists the names of all
+set exported variables,
+or if
+.Fl x
+was given, all set variables marked not for export.
+With the
+.Fl p
+option specified, the output will be formatted suitably for
+non-interactive use, and unset variables are included.
+When
+.Fl p
+is given, variable names, but not values, may also be
+given, in which case output is limited to the variables named.
+.Pp
+With
+.Fl q
+and a list of variable names, the
+.Ic export
+command will exit with status 0 if all the named
+variables have been marked for export, or 1 if
+any are not so marked.
+If
+.Fl x
+is also given,
+the test is instead for variables marked not to be exported.
+.Pp
+Other than with
+.Fl q ,
+the
+.Ic export
+built-in exits with status 0,
+unless an attempt is made to export a variable which has
+been marked as unavailable for export,
+in which cases it exits with status 1.
+In all cases if
+an invalid option, or option combination, is given,
+or an invalid variable name is present,
+.Ic export
+will write a message to the standard error output,
+and exit with a non-zero status.
+A non-interactive shell will terminate.
+.Pp
+Note that there is no restriction upon exporting,
+or un-exporting, read-only variables.
+The no-export flag can be reset by unsetting the variable
+and creating it again \(en provided the variable is not also read-only.
+.\"
+.It Ic fc Oo Fl e Ar editor Oc Op Ar first Op Ar last
+.It Ic fc Fl l Oo Fl nr Oc Op Ar first Op Ar last
+.It Ic fc Fl s Oo Ar old=new Oc Op Ar first
+The
+.Ic fc
+built-in lists, or edits and re-executes, commands previously entered
+to an interactive shell.
+.Bl -tag -width 5n
+.It Fl e Ar editor
+Use the editor named by
+.Ar editor
+to edit the commands.
+The
+.Ar editor
+string is a command name, subject to search via the
+.Ev PATH
+variable.
+The value in the
+.Ev FCEDIT
+variable is used as a default when
+.Fl e
+is not specified.
+If
+.Ev FCEDIT
+is null or unset, the value of the
+.Ev EDITOR
+variable is used.
+If
+.Ev EDITOR
+is null or unset,
+.Xr ed 1
+is used as the editor.
+.It Fl l No (ell)
+List the commands rather than invoking an editor on them.
+The commands are written in the sequence indicated by
+the first and last operands, as affected by
+.Fl r ,
+with each command preceded by the command number.
+.It Fl n
+Suppress command numbers when listing with
+.Fl l .
+.It Fl r
+Reverse the order of the commands listed (with
+.Fl l )
+or edited (with neither
+.Fl l
+nor
+.Fl s ) .
+.It Fl s
+Re-execute the command without invoking an editor.
+.It Ar first
+.It Ar last
+Select the commands to list or edit.
+The number of previous commands that
+can be accessed are determined by the value of the
+.Ev HISTSIZE
+variable.
+The value of
+.Ar first
+or
+.Ar last
+or both are one of the following:
+.Bl -tag -width 5n
+.It Oo Cm + Oc Ns Ar number
+A positive number representing a command number; command numbers can be
+displayed with the
+.Fl l
+option.
+.It Cm \- Ns Ar number
+A negative decimal number representing the command that was executed
+number of commands previously.
+For example, \-1 is the immediately previous command.
+.El
+.It Ar string
+A string indicating the most recently entered command that begins with
+that string.
+If the
+.Ar old Ns Li = Ns Ar new
+operand is not also specified with
+.Fl s ,
+the string form of the first operand cannot contain an embedded equal sign.
+.El
+.Pp
+The following environment variables affect the execution of
+.Ic fc :
+.Bl -tag -width HISTSIZE
+.It Ev FCEDIT
+Name of the editor to use.
+.It Ev HISTSIZE
+The number of previous commands that are accessible.
+.El
+.\"
+.It Ic fg Op Ar job
+Move the specified job or the current job to the foreground.
+A foreground job can interact with the user via standard input,
+and receive signals from the terminal.
+.\"
+.It Ic fdflags Oo Fl v Oc Op Ar fd ...
+.It Ic fdflags Oo Fl v Oc Fl s Ar flags fd Op ...
+Get or set file descriptor flags.
+The
+.Fl v
+argument enables verbose printing, printing flags that are also off, and
+the flags of the file descriptor being set after setting.
+The
+.Fl s
+flag interprets the
+.Ar flags
+argument as a comma separated list of file descriptor flags, each preceded
+with a
+.Dq \(pl
+or a
+.Dq \(mi
+indicating to set or clear the respective flag.
+Valid flags are:
+.Cm append ,
+.Cm async ,
+.Cm sync ,
+.Cm nonblock ,
+.Cm fsync ,
+.Cm dsync ,
+.Cm rsync ,
+.Cm direct ,
+.Cm nosigpipe ,
+and
+.Cm cloexec .
+Unique abbreviations of these names, of at least 2 characters,
+may be used on input.
+See
+.Xr fcntl 2
+and
+.Xr open 2
+for more information.
+.\"
+.It Ic getopts Ar optstring var
+The POSIX
+.Ic getopts
+command, not to be confused with the
+Bell Labs\[en]derived
+.Xr getopt 1 .
+.Pp
+The first argument should be a series of letters, each of which may be
+optionally followed by a colon to indicate that the option requires an
+argument.
+The variable specified is set to the parsed option.
+.Pp
+The
+.Ic getopts
+command deprecates the older
+.Xr getopt 1
+utility due to its handling of arguments containing whitespace.
+.Pp
+The
+.Ic getopts
+built-in may be used to obtain options and their arguments
+from a list of parameters.
+When invoked,
+.Ic getopts
+places the value of the next option from the option string in the list in
+the shell variable specified by
+.Ar var
+and its index in the shell variable
+.Ev OPTIND .
+When the shell is invoked,
+.Ev OPTIND
+is initialized to 1.
+For each option that requires an argument, the
+.Ic getopts
+built-in will place it in the shell variable
+.Ev OPTARG .
+If an option is not allowed for in the
+.Ar optstring ,
+then
+.Ev OPTARG
+will be unset.
+.Pp
+.Ar optstring
+is a string of recognized option letters (see
+.Xr getopt 3 ) .
+If a letter is followed by a colon, the option is expected to have an
+argument which may or may not be separated from it by whitespace.
+If an option character is not found where expected,
+.Ic getopts
+will set the variable
+.Ar var
+to a
+.Sq Li \&? ;
+.Ic getopts
+will then unset
+.Ev OPTARG
+and write output to standard error.
+By specifying a colon as the first character of
+.Ar optstring
+all errors will be ignored.
+.Pp
+A nonzero value is returned when the last option is reached.
+If there are no remaining arguments,
+.Ic getopts
+will set
+.Ar var
+to the special option,
+.Dq Li \-\- ,
+otherwise, it will set
+.Ar var
+to
+.Sq Li \&? .
+.Pp
+The following code fragment shows how one might process the arguments
+for a command that can take the options
+.Fl a
+and
+.Fl b ,
+and the option
+.Fl c ,
+which requires an argument.
+.Bd -literal -offset indent
+while getopts abc: f
+do
+ case $f in
+ a | b) flag=$f;;
+ c) carg=$OPTARG;;
+ \e?) echo $USAGE; exit 1;;
+ esac
+done
+shift $((OPTIND - 1))
+.Ed
+.Pp
+This code will accept any of the following as equivalent:
+.Bd -literal -offset indent
+cmd \-acarg file file
+cmd \-a \-c arg file file
+cmd \-carg -a file file
+cmd \-a \-carg \-\- file file
+.Ed
+.\"
+.It Ic hash Oo Fl rv Oc Op Ar command ...
+The shell maintains a hash table which remembers the
+locations of commands.
+With no arguments whatsoever,
+the
+.Ic hash
+command prints out the contents of this table.
+Entries which have not been looked at since the last
+.Ic cd
+command are marked with an asterisk; it is possible for these entries
+to be invalid.
+.Pp
+With arguments, the
+.Ic hash
+command removes the specified commands from the hash table (unless
+they are functions) and then locates them.
+With the
+.Fl v
+option, hash prints the locations of the commands as it finds them.
+The
+.Fl r
+option causes the hash command to delete all the entries in the hash table
+except for functions.
+.\"
+.It Ic inputrc Ar file
+Read the
+.Ar file
+to set key bindings as defined by
+.Xr editrc 5 .
+.\"
+.It Ic jobid Oo Fl g Ns \&| Ns Fl j Ns \&| Ns Fl p Oc Op Ar job
+With no flags, print the process identifiers of the processes in the job.
+If the
+.Ar job
+argument is omitted, the current job is used.
+Any of the ways to select a job may be used for
+.Ar job ,
+including the
+.Sq Li \&%
+forms, or the process id of the job leader
+.Po
+.Dq Li \&$!
+if the job was created in the background.
+.Pc
+.Pp
+If one of the flags is given, then instead of the list of
+process identifiers, the
+.Ic jobid
+command prints:
+.Bl -tag -width ".Fl g"
+.It Fl g
+the process group, if one was created for this job,
+or nothing otherwise (the job is in the same process
+group as the shell.)
+.It Fl j
+the job identifier (using
+.Dq Li \&% Ns Ar n
+notation, where
+.Ar n
+is a number) is printed.
+.It Fl p
+only the process id of the process group leader is printed.
+.El
+.Pp
+These flags are mutually exclusive.
+.Pp
+.Ic jobid
+exits with status 2 if there is an argument error,
+status 1, if with
+.Fl g
+the job had no separate process group,
+or with
+.Fl p
+there is no process group leader (should not happen),
+and otherwise exits with status 0.
+.\"
+.It Ic jobs Oo Fl l Ns \&| Ns Fl p Oc Op Ar job ...
+Without
+.Ar job
+arguments,
+this command lists out all the background processes
+which are children of the current shell process.
+With
+.Ar job
+arguments, the listed jobs are shown instead.
+Without flags, the output contains the job
+identifier (see
+.Sx Job Control
+below), an indicator character if the job is the current or previous job,
+the current status of the job (running, suspended, or terminated successfully,
+unsuccessfully, or by a signal)
+and a (usually abbreviated) command string.
+.Pp
+With the
+.Fl l
+flag the output is in a longer form, with the process identifiers
+of each process (run from the top level, as in a pipeline), and the
+status of each process, rather than the job status.
+.Pp
+With the
+.Fl p
+flag, the output contains only the process identifier of the lead
+process.
+.Pp
+In an interactive shell, each job shown as completed in the output
+from the jobs command is implicitly waited for, and is removed from
+the jobs table, never to be seen again.
+In an interactive shell, when a background job terminates, the
+.Ic jobs
+command (with that job as an argument) is implicitly run just
+before outputting the next PS1 command prompt, after the job
+terminated.
+This indicates that the job finished, shows its status,
+and cleans up the job table entry for that job.
+Non-interactive shells need to execute
+.Ic wait
+commands to clean up terminated background jobs.
+.\"
+.It Ic local Oo Fl INx Oc Oo Ar variable | \- Oc ...
+Define local variables for a function.
+Local variables have their attributes, and values,
+as they were before the
+.Ic local
+declaration, restored when the function terminates.
+.Pp
+With the
+.Fl N
+flag, variables made local, are unset initially inside
+the function.
+Unless the
+.Fl x
+flag is also given, such variables are also unexported.
+The
+.Fl I
+flag, which is the default in this shell, causes
+the initial value and exported attribute
+of local variables
+to be inherited from the variable
+with the same name in the surrounding
+scope, if there is one.
+If there is not, the variable is initially unset,
+and not exported.
+The
+.Fl N
+and
+.Fl I
+flags are mutually exclusive, if both are given, the last specified applies.
+The read-only and unexportable attributes are always
+inherited, if a variable with the same name already exists.
+.Pp
+The
+.Fl x
+flag (lower case) causes the local variable to be exported,
+while the function runs, unless it has the unexportable attribute.
+This can also be accomplished by using the
+.Ic export
+command, giving the same
+.Ar variable
+names, after the
+.Ic local
+command.
+.Pp
+Making an existing read-only variable local is possible,
+but pointless.
+If an attempt is made to assign an initial value to such
+a variable, the
+.Ic local
+command fails, as does any later attempted assignment.
+If the
+.Ic readonly
+command is applied to a variable that has been declared local,
+the variable cannot be (further) modified within the function,
+or any other functions it calls, however when the function returns,
+the previous status (and value) of the variable is returned.
+.Pp
+Values may be given to local variables on the
+.Ic local
+command line in a similar fashion as used for
+.Ic export
+and
+.Ic readonly .
+These values are assigned immediately after the initialization
+described above.
+Note that any variable references on the command line will have
+been expanded before
+.Ic local
+is executed, so expressions like
+.Pp
+.Dl "local -N X=${X}"
+.Pp
+are well defined, first $X is expanded, and then the command run is
+.Pp
+.Dl "local -N X=old-value-of-X"
+.Pp
+After arranging to preserve the old value and attributes, of
+.Dv X
+.Dq ( old-value-of X )
+.Ic local
+unsets
+.Dv X ,
+unexports it, and then assigns the
+.Dq old-value-of-X
+to
+.Ev X .
+.Pp
+The shell uses dynamic scoping, so that if you make the variable
+.Dv x
+local to
+function
+.Dv f ,
+which then calls function
+.Dv g ,
+references to the variable
+.Dv x
+made inside
+.Dv g
+will refer to the variable
+.Dv x
+declared inside
+.Dv f ,
+not to the global variable named
+.Dv x .
+.Pp
+Another way to view this, is as if the shell just has one flat, global,
+namespace, in which all variables exist.
+The
+.Ic local
+command conceptually copies the variable(s) named to unnamed temporary
+variables, and when the function ends, copies them back again.
+All references to the variables reference the same global variables,
+but while the function is active, after the
+.Ic local
+command has run, the values and attributes of the variables might
+be altered, and later, when the function completes, be restored.
+.Pp
+Note that the positional parameters
+.Dv 1 , \" $1
+.Dv 2 , \" $2
+\&... (see
+.Sx Positional Parameters ) ,
+and the special parameters
+.Dv \&# , \" $#
+.Dv \&* \" $*
+and
+.Dv \&@ \" $@
+(see
+.Sx Special Parameters ) ,
+are always made local in all functions, and are reset inside the
+function to represent the options and arguments passed to the function.
+Note that
+.Li $0
+however retains the value it had outside the function,
+as do all the other special parameters.
+.Pp
+The only special parameter that can optionally be made local is
+.Dq Li \- .
+Making
+.Dq Li \-
+local causes any shell options that are changed via the set command inside the
+function to be restored to their original values when the function
+returns.
+If
+.Fl X
+option is altered after
+.Dq Li \-
+has been made local, then when the function returns, the previous
+destination for
+.Cm xtrace
+output (as of the time of the
+.Ic local
+command) will also be restored.
+If any of the shell's magic variables
+(those which return a value which may vary without
+the variable being explicitly altered,
+e.g.:
+.Dv SECONDS
+or
+.Dv HOSTNAME )
+are made local in a function,
+they will lose their special properties when set
+within the function, including by the
+.Ic local
+command itself
+(if not to be set in the function, there is little point
+in making a variable local)
+but those properties will be restored when the function returns.
+.Pp
+It is an error to use
+.Ic local
+outside the scope of a function definition.
+When used inside a function, it exits with status 0,
+unless an undefined option is used, or an attempt is made to
+assign a value to a read-only variable.
+.Pp
+Note that either
+.Fl I
+or
+.Fl N
+should always be used, or variables made local should always
+be given a value, or explicitly unset, as the default behavior
+(inheriting the earlier value, or starting unset after
+.Ic local )
+differs amongst shell implementations.
+Using
+.Dq Li local \&\-
+is an extension not implemented by most shells.
+.Pp
+See the section
+.Sx LINENO
+below for details of the effects of making the variable
+.Dv LINENO
+local.
+.\"
+.It Ic pwd Op Fl \&LP
+Print the current directory.
+If
+.Fl L
+is specified the cached value (initially set from
+.Ev PWD )
+is checked to see if it refers to the current directory; if it does
+the value is printed.
+Otherwise the current directory name is found using
+.Xr getcwd 3 .
+The environment variable
+.Ev PWD
+is set to the printed value.
+.Pp
+The default is
+.Ic pwd
+.Fl L ,
+but note that the built-in
+.Ic cd
+command doesn't support the
+.Fl L
+option and will cache (almost) the absolute path.
+If
+.Ic cd
+is changed (as unlikely as that is),
+.Ic pwd
+may be changed to default to
+.Ic pwd
+.Fl P .
+.Pp
+If the current directory is renamed and replaced by a symlink to the
+same directory, or the initial
+.Ev PWD
+value followed a symbolic link, then the cached value may not
+be the absolute path.
+.Pp
+The built-in command may differ from the program of the same name because
+the program will use
+.Ev PWD
+and the built-in uses a separately cached value.
+.\"
+.It Ic read Oo Fl p Ar prompt Oc Oo Fl r Oc Ar variable Op Ar ...
+The
+.Ar prompt
+is printed if the
+.Fl p
+option is specified and the standard input is a terminal.
+Then a line is read from the standard input.
+The trailing newline is deleted from the
+line and the line is split as described in the field splitting section of the
+.Sx Word Expansions
+section above, and the pieces are assigned to the variables in order.
+If there are more pieces than variables, the remaining pieces
+(along with the characters in
+.Ev IFS
+that separated them) are assigned to the last variable.
+If there are more variables than pieces,
+the remaining variables are assigned the null string.
+The
+.Ic read
+built-in will indicate success unless EOF is encountered on input, in
+which case failure is returned.
+.Pp
+By default, unless the
+.Fl r
+option is specified, the backslash
+.Dq \e
+acts as an escape character, causing the following character to be treated
+literally.
+If a backslash is followed by a newline, the backslash and the
+newline will be deleted.
+.\"
+.It Ic readonly Ar name Ns Oo =value Oc ...
+.It Ic readonly Oo Fl p Oo Ar name ... Oc Oc
+.It Ic readonly Fl q Ar name ...
+With no options,
+the specified names are marked as read only, so that they cannot be
+subsequently modified or unset.
+The shell allows the value of a variable
+to be set at the same time it is marked read only by writing
+.Pp
+.Dl readonly name=value
+.Pp
+With no arguments the
+.Ic readonly
+command lists the names of all set read only variables.
+With the
+.Fl p
+option specified,
+the output will be formatted suitably for non-interactive use,
+and unset variables are included.
+When the
+.Fl p
+option is given,
+a list of variable names (without values) may also be specified,
+in which case output is limited to the named variables.
+.Pp
+With the
+.Fl q
+option, the
+.Ic readonly
+command tests the read-only status of the variables listed
+and exits with status 0 if all named variables are read-only,
+or with status 1 if any are not read-only.
+.Pp
+Other than as specified for
+.Fl q
+the
+.Ic readonly
+command normally exits with status 0.
+In all cases, if an unknown option, or an invalid option combination,
+or an invalid variable name, is given;
+or a variable which was already read-only is attempted to be set;
+the exit status will not be zero, a diagnostic
+message will be written to the standard error output,
+and a non-interactive shell will terminate.
+.\"
+.It Ic return Op Ar n
+Stop executing the current function or a dot command with return value of
+.Ar n
+or the value of the last executed command, if not specified.
+For portability,
+.Ar n
+should be in the range from 0 to 255.
+.Pp
+The POSIX standard says that the results of
+.Ic return
+outside a function or a dot command are unspecified.
+This implementation treats such a return as a no-op with a return value of 0
+(success, true).
+Use the
+.Ic exit
+command instead, if you want to return from a script or exit
+your shell.
+.\"
+.It Ic set Oo { Fl options | Cm +options | Cm \-- } Oc Ar arg ...
+The
+.Ic set
+command performs four different functions.
+.Pp
+With no arguments, it lists the values of all shell variables.
+.Pp
+With a single option of either
+.Dq Fl o
+or
+.Dq Cm +o
+.Ic set
+outputs the current values of the options.
+In the
+.Fl o
+form, all options are listed, with their current values.
+In the
+.Cm +o
+form, the shell outputs a string that can later be used
+as a command to reset all options to their current values.
+.Pp
+If options are given, it sets the specified option
+flags, or clears them as described in the
+.Sx Argument List Processing
+section.
+In addition to the options listed there,
+when the
+.Dq "option name"
+given to
+.Ic set Fl o
+is
+.Cm default
+all of the options are reset to the values they had immediately
+after
+.Nm
+initialization, before any startup scripts, or other input, had been processed.
+While this may be of use to users or scripts, its primary purpose
+is for use in the output of
+.Dq Ic set Cm +o ,
+to avoid that command needing to list every available option.
+There is no
+.Cm +o default .
+.Pp
+The fourth use of the
+.Ic set
+command is to set the values of the shell's
+positional parameters to the specified arguments.
+To change the positional
+parameters without changing any options, use
+.Dq -\|-
+as the first argument to
+.Ic set .
+If no following arguments are present, the
+.Ic set
+command
+will clear all the positional parameters (equivalent to executing
+.Dq Li shift $# . )
+Otherwise the following arguments become
+.Li \&$1 ,
+.Li \&$2 ,
+\&...,
+and
+.Li \&$#
+is set to the number of arguments present.
+.\"
+.It Ic setvar Ar variable Ar value
+Assigns
+.Ar value
+to
+.Ar variable .
+(In general it is better to write
+.Li variable=value
+rather than using
+.Ic setvar .
+.Ic setvar
+is intended to be used in
+functions that assign values to variables whose names are passed as
+parameters.)
+.\"
+.It Ic shift Op Ar n
+Shift the positional parameters
+.Ar n
+times.
+If
+.Ar n
+is omitted, 1 is assumed.
+Each
+.Ic shift
+sets the value of
+.Li $1
+to the previous value of
+.Li $2 ,
+the value of
+.Li $2
+to the previous value of
+.Li $3 ,
+and so on, decreasing
+the value of
+.Li $#
+by one.
+The shift count must be less than or equal to the number of
+positional parameters (
+.Dq Li $# )
+before the shift.
+.\"
+.It Ic times
+Prints two lines to standard output.
+Each line contains two accumulated time values, expressed
+in minutes and seconds (including fractions of a second.)
+The first value gives the user time consumed, the second the system time.
+.Pp
+The first output line gives the CPU and system times consumed by the
+shell itself.
+The second line gives the accumulated times for children of this
+shell (and their descendants) which have exited, and then been
+successfully waited for by the relevant parent.
+See
+.Xr times 3
+for more information.
+.Pp
+.Ic times
+has no parameters, and exits with an exit status of 0 unless
+an attempt is made to give it an option.
+.\"
+.It Ic trap Ar action signal ...
+.It Ic trap \-
+.It Ic trap Op Fl l
+.It Ic trap Oo Fl p Oc Ar signal ...
+.It Ic trap Ar N signal ...
+.Pp
+Cause the shell to parse and execute action when any of the specified
+signals are received.
+The signals are specified by signal number or as the name of the signal.
+If
+.Ar signal
+is
+.Li 0 \" $0
+or its equivalent,
+.Li EXIT ,
+the action is executed when the shell exits.
+The
+.Ar action
+may be a null (empty) string,
+which causes the specified signals to be ignored.
+With
+.Ar action
+set to
+.Sq Li -
+the specified signals are set to their default actions.
+If the first
+.Ar signal
+is specified in its numeric form, then
+.Ar action
+can be omitted to achieve the same effect.
+This archaic,
+but still standard,
+form should not be relied upon, use the explicit
+.Sq Li -
+action.
+If no signals are specified with an action of
+.Sq Li - ,
+all signals are reset.
+.Pp
+When the shell forks off a sub-shell, it resets trapped (but not ignored)
+signals to the default action.
+On non-interactive shells, the
+.Ic trap
+command has no effect on signals that were
+ignored on entry to the shell.
+On interactive shells, the
+.Ic trap
+command will catch or reset signals ignored on entry.
+.Pp
+Issuing
+.Ic trap
+with option
+.Fl l
+will print a list of valid signal names.
+.Ic trap
+without any arguments causes it to write a list of signals and their
+associated non-default actions to the standard output in a format
+that is suitable as an input to the shell that achieves the same
+trapping results.
+With the
+.Fl p
+flag, trap prints the same information for the signals specified,
+or if none are given, for all signals, including those where the
+action is the default.
+These variants of the trap command may be executed in a sub-shell
+.Pq "such as in a command substitution" ,
+provided they appear as the sole, or first, command in that sub-shell,
+in which case the state of traps from the parent of that
+sub-shell is reported.
+.Pp
+Examples:
+.Pp
+.Dl trap
+.Pp
+List trapped signals and their corresponding actions.
+.Pp
+.Dl trap -l
+.Pp
+Print a list of valid signals.
+.Pp
+.Dl trap '' INT QUIT tstp 30
+.Pp
+Ignore signals INT QUIT TSTP USR1.
+.Pp
+.Dl trap date INT
+.Pp
+Run the
+.Dq date
+command (print the date) upon receiving signal INT.
+.Pp
+.Dl trap HUP INT
+.Pp
+Run the
+.Dq HUP
+command, or function, upon receiving signal INT.
+.Pp
+.Dl trap 1 2
+.Pp
+Reset the actions for signals 1 (HUP) and 2 (INT) to their defaults.
+.Bd -literal -offset indent
+traps=$(trap -p)
+ # more commands ...
+trap 'action' SIG
+ # more commands ...
+eval "$traps"
+.Ed
+.Pp
+Save the trap status, execute commands, changing some traps,
+and then reset all traps to their values at the start of the sequence.
+The
+.Fl p
+option is required in the first command here,
+or any signals that were previously
+untrapped (in their default states)
+and which were altered during the intermediate code,
+would not be reset by the final
+.Ic eval .
+.\"
+.It Ic type Op Ar name ...
+Interpret each
+.Ar name
+as a command and print the resolution of the command search.
+Possible resolutions are:
+shell keyword, alias, shell built-in,
+command, tracked alias and not found.
+For aliases the alias expansion is
+printed; for commands and tracked aliases the complete pathname of the
+command is printed.
+.\"
+.It Ic ulimit Oo Fl H Ns \*(Ba Ns Fl S Oc Op Fl a \*(Ba Fl btfdscmlrpnv Op Ar value
+Inquire about or set the hard or soft limits on processes or set new
+limits.
+The choice between hard limit (which no process is allowed to
+violate, and which may not be raised once it has been lowered) and soft
+limit (which causes processes to be signaled but not necessarily killed,
+and which may be raised) is made with these flags:
+.Bl -tag -width Fl
+.It Fl H
+set or inquire about hard limits
+.It Fl S
+set or inquire about soft limits.
+.El
+.Pp
+If neither
+.Fl H
+nor
+.Fl S
+is specified, the soft limit is displayed or both limits are set.
+If both are specified, the last one wins.
+.Pp
+The limit to be interrogated or set, then, is chosen by specifying
+any one of these flags:
+.Bl -tag -width Fl
+.It Fl a
+show all the current limits
+.It Fl b
+the socket buffer size of a process (bytes)
+.It Fl c
+the largest core dump size that can be produced
+(512-byte blocks)
+.It Fl d
+the data segment size of a process (kilobytes)
+.It Fl f
+the largest file that can be created
+(512-byte blocks)
+.It Fl l
+how much memory a process can lock with
+.Xr mlock 2
+(kilobytes)
+.It Fl m
+the total physical memory that can be
+in use by a process (kilobytes)
+.It Fl n
+the number of files a process can have open at once
+.It Fl p
+the number of processes this user can
+have at one time
+.It Fl r
+the number of threads this user can
+have at one time
+.It Fl s
+the stack size of a process (kilobytes)
+.It Fl t
+CPU time (seconds)
+.It Fl v
+how large a process address space can be
+.El
+.Pp
+If none of these is specified, it is the limit on file size that is shown
+or set.
+If value is specified, the limit is set to that number; otherwise
+the current limit is displayed.
+.Pp
+Limits of an arbitrary process can be displayed or set using the
+.Xr sysctl 8
+utility.
+.It Ic umask Oo Fl S Oc Op Ar mask
+Set the value of umask (see
+.Xr umask 2 )
+to the specified octal value.
+If the argument is omitted, the umask value is printed.
+With
+.Fl S
+a symbolic form is used instead of an octal number.
+.It Ic unalias Oo Fl a Oc Op Ar name
+If
+.Ar name
+is specified, the shell removes that alias.
+If
+.Fl a
+is specified, all aliases are removed.
+.It Ic unset Oo Fl efvx Oc Ar name ...
+If
+.Fl v
+is specified, the specified variables are unset and unexported.
+Readonly variables cannot be unset.
+If
+.Fl f
+is specified, the specified functions are undefined.
+If
+.Fl e
+is given, the specified variables are unexported, but otherwise unchanged,
+alternatively, if
+.Fl x
+is given, the exported status of the variable will be retained,
+even after it is unset.
+.Pp
+If no flags are provided
+.Fl v
+is assumed.
+If
+.Fl f
+is given with one of the other flags,
+then the named variables will be unset, or unexported, and functions
+of the same names will be undefined.
+The
+.Fl e
+and
+.Fl x
+flags both imply
+.Fl v .
+If
+.Fl e
+is given, the
+.Fl x
+flag is ignored.
+.Pp
+The exit status is 0, unless an attempt was made to unset
+a readonly variable, in which case the exit status is 1.
+It is not an error to unset (or undefine) a variable (or function)
+that is not currently set (or defined.)
+.\"
+.It Ic wait Oo Fl n Oc Oo Fl p Ar var Oc Op Ar job ...
+Wait for the specified jobs to complete
+and return the exit status of the last job in the parameter list,
+or 127 if that job is not a current child of the shell.
+.Pp
+If no
+.Ar job
+arguments are given, wait for all jobs to
+complete and then return an exit status of zero
+(including when there were no jobs, and so nothing exited.)
+.Pp
+With the
+.Fl n
+option, wait instead for any one of the given
+.Ar job Ns s,
+or if none are given, any job, to complete, and
+return the exit status of that job.
+If none of the given
+.Ar job
+arguments is a current child of the shell,
+or if no
+.Ar job
+arguments are given and the shell has no unwaited for children,
+then the exit status will be 127.
+.Pp
+The
+.Fl p Ar var
+option allows the process (or job) identifier of the
+job for which the exit status is returned to be obtained.
+The variable named (which must not be readonly) will be
+unset initially, then if a job has exited and its status is
+being returned, set to the identifier from the
+arg list (if given) of that job,
+or the lead process identifier of the job to exit when used with
+.Fl n
+and no job arguments.
+Note that
+.Fl p
+with neither
+.Fl n
+nor
+.Ar job
+arguments is useless, as in that case no job status is
+returned, the variable named is simply unset.
+.Pp
+If the wait is interrupted by a signal,
+its exit status will be greater than 128,
+and
+.Ar var ,
+if given, will remain unset.
+.Pp
+Once waited upon, by specific process number or job-id,
+or by a
+.Ic wait
+with no arguments,
+knowledge of the child is removed from the system,
+and it cannot be waited upon again.
+.Pp
+Note than when a list of jobs are given, more that
+one argument might refer to the same job.
+In that case, if the final argument represents a job
+that is also given earlier in the list, it is not
+defined whether the status returned will be the
+exit status of the job, or 127 indicating that
+the child no longer existed when the wait command
+reached the later argument in the list.
+In this
+.Nm
+the exit status will be that from the job.
+.Nm
+waits for each job exactly once, regardless of
+how many times (or how many different ways) it
+is listed in the arguments to
+.Ic wait .
+That is
+.Bd -literal -offset indent -compact
+wait 100 100 100
+.Ed
+is identical to
+.Bd -literal -offset indent -compact
+wait 100
+.Ed
+.El
+.\"
+.\"
+.Ss Job Control
+.\"
+Each process (or set of processes) started by
+.Nm
+is created as a
+.Dq job
+and added to the jobs table.
+When enabled by the
+.Fl m
+option
+.Pq aka Fl o Cm monitor
+when the job is created,
+.Nm
+places each job (if run from the top level shell)
+into a process group of its own, which allows control
+of the process(es), and its/their descendants, as a unit.
+When the
+.Fl m
+option is off, or when started from a sub-shell environment,
+jobs share the same process group as the parent shell.
+The
+.Fl m
+option is enabled by default in interactive shells with
+a terminal as standard input and standard error.
+.Pp
+Jobs with separate process groups may be stopped, and then later
+resumed in the foreground (with access to the terminal)
+or in the background (where attempting to read from the
+terminal will result in the job stopping.)
+A list of current jobs can be obtained using the
+.Ic jobs
+built-in command.
+Jobs are identified using either the process identifier
+of the lead process of the job (the value available in
+the special parameter
+.Dq Dv \&!
+if the job is started in the background), or using percent
+notation.
+Each job is given a
+.Dq job number
+which is a small integer, starting from 1, and can be
+referenced as
+.Dq Li \&% Ns Ar n
+where
+.Ar n
+is that number.
+Note that this applies to jobs both with and without their own process groups.
+Job numbers are shown in the output from the
+.Ic jobs
+command enclosed in brackets
+.Po
+.Sq Li \&[
+and
+.Sq Li \&]
+.Pc .
+Whenever the job table becomes empty, the numbers begin at one again.
+In addition, there is the concept of a current, and a previous job,
+identified by
+.Dq Li \&%+
+.Po
+or
+.Dq Li \&%%
+or even just
+.Dq Li \&%
+.Pc ,
+and a previous job, identified by
+.Dq Li \&%\- .
+Whenever a background job is started,
+or a job is resumed in the background,
+it becomes the current job.
+The job that was the current job
+(prepare for a big surprise here, drum roll..., wait for it...\&)
+becomes the previous job.
+When the current job terminates, the previous job is
+promoted to be the current job.
+In addition the form
+.Dq Li \&% Ns Ar string\^
+finds the job for which the command starts with
+.Ar string
+and the form
+.Dq Li \&%? Ns Ar string\^
+finds the job which contains the
+.Ar string
+in its command somewhere.
+Both forms require the result to be unambiguous.
+For this purpose the
+.Dq command
+is that shown in the output from the
+.Ic jobs
+command, not the original command line.
+.Pp
+The
+.Ic bg ,
+.Ic fg ,
+.Ic jobid ,
+.Ic jobs ,
+.Ic kill ,
+and
+.Ic wait
+commands all accept job identifiers as arguments, in addition to
+process identifiers (larger integers).
+See the
+.Sx Built-ins
+section above, and
+.Xr kill 1 ,
+for more details of those commands.
+In addition, a job identifier
+(using one of the
+.Dq \&% forms )
+issued as a command, without arguments, is interpreted as
+if it had been given as the argument to the
+.Ic fg
+command.
+.Pp
+To cause a foreground process to stop, enter the terminal's
+.Ic stop
+character (usually control-Z).
+To cause a background process to stop, send it a
+.Dv STOP
+signal, using the kill command.
+A useful function to define is
+.Bd -literal -offset indent
+stop() { kill -s STOP "${@:-%%}"; }
+.Ed
+.Pp
+The
+.Ic fg
+command resumes a stopped job, placing it in the foreground,
+and
+.Ic bg
+resumes a stopped job in the background.
+The
+.Ic jobid
+command provides information about process identifiers, job identifiers,
+and the process group identifier, for a job.
+.Pp
+Whenever a sub-shell is created, the jobs table becomes invalid
+(the sub-shell has no children.)
+However, to enable uses like
+.Bd -literal -offset indent
+PID=$(jobid -p %1)
+.Ed
+.Pp
+the table is only actually cleared in a sub-shell when needed to
+create the first job there (built-in commands run in the foreground
+do not create jobs.)
+Note that in this environment, there is no useful current job
+.Dq ( Li \&%%
+actually refers to the sub-shell itself, but is not accessible)
+but the job which is the current job in the parent can be accessed as
+.Dq Li \&%\- .
+.\"
+.\"
+.Ss Command Line Editing
+.\"
+When
+.Nm
+is being used interactively from a terminal, the current command
+and the command history (see
+.Ic fc
+in the
+.Sx Built-ins
+section)
+can be edited using emacs-mode or vi-mode command-line editing.
+The command
+.Ql set -o emacs
+(or
+.Fl E
+option)
+enables emacs-mode editing.
+The command
+.Ql set -o vi
+(or
+.Fl V
+option)
+enables vi-mode editing and places the current shell process into
+vi insert mode.
+(See the
+.Sx Argument List Processing
+section above.)
+.Pp
+The vi-mode uses commands similar to a subset of those described in the
+.Xr vi 1
+man page.
+With vi-mode
+enabled,
+.Nm sh
+can be switched between insert mode and command mode.
+It's similar to
+.Ic vi :
+pressing the
+.Aq ESC
+key will throw you into vi command mode.
+Pressing the
+.Aq return
+key while in command mode will pass the line to the shell.
+.Pp
+The emacs-mode uses commands similar to a subset available in the
+.Ic emacs
+editor.
+With emacs-mode enabled, special keys can be used to modify the text
+in the buffer using the control key.
+.Pp
+.Nm
+uses the
+.Xr editline 3
+library.
+See
+.Xr editline 7
+for a list of the possible command bindings,
+and the default settings in emacs and vi modes.
+Also see
+.Xr editrc 5
+for the commands that can be given to configure
+.Xr editline 7
+in the file named by the
+.Ev EDITRC
+parameter,
+or a file used with the
+.Ic inputrc
+built-in command,
+or using
+.Xr editline 7 Ap s
+configuration command line.
+.Pp
+When command line editing is enabled, the
+.Xr editline 7
+functions control printing of the
+.Ev PS1
+and
+.Ev PS2
+prompts when required.
+As, in this mode, the command line editor needs to
+keep track of what characters are in what position on
+the command line, care needs to be taken when setting
+the prompts.
+Normal printing characters are handled automatically,
+however mode setting sequences, which do not actually display
+on the terminal, need to be identified to
+.Xr editline 7 .
+This is done, when needed, by choosing a character that
+is not needed anywhere in the prompt, including in the mode
+setting sequences, any single character is acceptable,
+and assigning it to the shell parameter
+.Dv PSlit .
+Then that character should be used, in pairs, in the
+prompt string.
+Between each pair of
+.Dv PSlit
+characters are mode setting sequences, which affect the printing
+attributes of the following (normal) characters of the prompt,
+but do not themselves appear visibly, nor change the terminal's
+cursor position.
+.Pp
+Each such sequence, that is
+.Dv PSlit
+character, mode setting character sequence, and another
+.Dv PSlit
+character, must currently be followed by at least one following
+normal prompt character, or it will be ignored.
+That is, a
+.Dv PSlit
+character cannot be the final character of
+.Ev PS1
+or
+.Ev PS2 ,
+nor may two
+.Dv PSlit
+delimited sequences appear adjacent to each other.
+Each sequence can contain as many mode altering sequences as are
+required however.
+Only the first character from
+.Dv PSlit
+will be used.
+When set
+.Dv PSlit
+should usually be set to a string containing just one
+character, then it can simply be embedded in
+.Ev PS1
+(or
+.Ev PS2 )
+as in
+.Pp
+.D1 Li PS1=\*q${PSlit} Ns Ar mset\^ Ns Li ${PSlit}XYZ${PSlit} Ns Ar mclr\^ Ns Li ${PSlit}ABC\*q
+.Pp
+The prompt visible will be
+.Dq XYZABC
+with the
+.Dq XYZ
+part shown according as defined by the mode setting characters
+.Ar mset ,
+and then cleared again by
+.Ar mclr .
+See
+.Xr tput 1
+for one method to generate appropriate mode sequences.
+Note that both parts, XYZ and ABC, must each contain at least one
+character.
+.Pp
+If
+.Dv PSlit
+is unset, which is its initial state, or set to a null string,
+no literal character will be defined,
+and all characters of the prompt strings will be assumed
+to be visible characters (which includes spaces etc.)
+To allow smooth use of prompts, without needing redefinition, when
+.Xr editline 7
+is disabled, the character chosen should be one which will be
+ignored by the terminal if received, as when
+.Xr editline 7
+is not in use, the prompt strings are simply written to the terminal.
+For example, setting:
+.\" XXX: PS1 line is too long for -offset indent
+.Bd -literal -offset left
+ PSlit="$(printf\ '\e1')"
+ PS1="${PSlit}$(tput\ bold\ blink)${PSlit}\e$${PSlit}$(tput\ sgr0)${PSlit}\ "
+.Ed
+.Pp
+will arrange for the primary prompt to be a bold blinking dollar sign,
+if supported by the current terminal, followed by an (ordinary) space,
+and, as the SOH (control-A) character
+.Pq Sq \e1
+will not normally affect
+a terminal, this same prompt will usually work with
+.Xr editline 7
+enabled or disabled.
+.Sh ENVIRONMENT
+.Bl -tag -width MAILCHECK
+.It Ev CDPATH
+The search path used with the
+.Ic cd
+built-in.
+.It Ev EDITRC
+Gives the name of the file containing commands for
+.Xr editline 7 .
+See
+.Xr editrc 5
+for possible content and format.
+The file is processed, when in interactive mode with
+command line editing enabled, whenever
+.Ev EDITRC
+is set (even with no actual value change,)
+and if command line editing changes from disabled to enabled,
+or the editor style used is changed.
+(See the
+.Fl E
+and
+.Fl V
+options of the
+.Ic set
+built-in command, described in
+.Sx Built-ins
+above, which are documented further above in
+.Sx Argument List Processing . )
+If unset
+.Dq $HOME/.editrc
+is used.
+.It Ev ENV
+Names the file sourced at startup by the shell.
+Unused by this shell after initialization,
+but is usually passed through the environment to
+descendant shells.
+.It Ev EUSER
+Set to the login name of the effective user id running the shell,
+as returned by
+.Bd -compact -literal -offset indent
+getpwuid(geteuid())->pw_name
+.Ed
+.Po
+See
+.Xr getpwuid 3
+and
+.Xr geteuid 2
+for more details.
+.Pc
+This is obtained each time
+.Ev EUSER
+is expanded, so changes to the shell's execution identity
+cause updates without further action.
+If unset, it returns nothing.
+If set it loses its special properties, and is simply a variable.
+.It Ev HISTSIZE
+The number of lines in the history buffer for the shell.
+.It Ev HOME
+Set automatically by
+.Xr login 1
+from the user's login directory in the password file
+.Pq Xr passwd 5 .
+This environment variable also functions as the default argument for the
+.Ic cd
+built-in.
+.It Ev HOSTNAME
+Set to the current hostname of the system, as returned by
+.Xr gethostname 3 .
+This is obtained each time
+.Ev HOSTNAME
+is expanded, so changes to the system's name are reflected
+without further action.
+If unset, it returns nothing.
+If set it loses its special properties, and is simply a variable.
+.It Ev IFS
+Input Field Separators.
+This is normally set to
+.Aq space ,
+.Aq tab ,
+and
+.Aq newline .
+See the
+.Sx White Space Splitting
+section for more details.
+.It Ev LANG
+The string used to specify localization information that allows users
+to work with different culture-specific and language conventions.
+See
+.Xr nls 7 .
+.It Dv LINENO
+The current line number in the script or function.
+See the section
+.Sx LINENO
+below for more details.
+.It Ev MAIL
+The name of a mail file, that will be checked for the arrival of new mail.
+Overridden by
+.Ev MAILPATH .
+The check occurs just before
+.Ev PS1
+is written, immediately after reporting jobs which have changed status,
+in interactive shells only.
+New mail is considered to have arrived if the monitored file
+has increased in size since the last check.
+.\" .It Ev MAILCHECK
+.\" The frequency in seconds that the shell checks for the arrival of mail
+.\" in the files specified by the
+.\" .Ev MAILPATH
+.\" or the
+.\" .Ev MAIL
+.\" file.
+.\" If set to 0, the check will occur at each prompt.
+.It Ev MAILPATH
+A colon
+.Dq \&:
+separated list of file names, for the shell to check for incoming mail.
+This environment setting overrides the
+.Ev MAIL
+setting.
+There is a maximum of 10 mailboxes that can be monitored at once.
+.It Ev PATH
+The default search path for executables.
+See the
+.Sx Path Search
+section above.
+.It Ev POSIXLY_CORRECT
+If set in the environment upon initialization of the shell,
+then the shell option
+.Ic posix
+will be set.
+.Po
+See the description of the
+.Ic set
+command in the
+.Sx Built-ins
+section.
+.Pc
+After initialization it is unused by the shell,
+but is usually passed through the environment to
+descendant processes, including other instances of the shell,
+which may interpret it in a similar way.
+.It Ev PPID
+The process identified of the parent process of the
+current shell.
+This value is set at shell startup, ignoring
+any value in the environment, and then made readonly.
+.It Ev PS1
+The primary prompt string, which defaults to
+.Dq Li "$ " ,
+unless you are the superuser, in which case it defaults to
+.Dq Li "# " .
+This string is subject to parameter, arithmetic, and if
+enabled by setting the
+.Ic promptcmds
+option, command substitution before being output.
+During execution of commands used by command substitution,
+execution tracing, the
+.Ic xtrace
+.Ic ( set Fl x )
+option is temporarily disabled.
+If
+.Ic promptcmds
+is not set and the prompt string uses command substitution,
+the prompt used will be an appropriate error string.
+For other expansion errors, a message will be output,
+and the unexpanded string will then be used as the prompt.
+.It Ev PS2
+The secondary prompt string, which defaults to
+.Dq Li "> " .
+After expansion (as for
+.Ev PS1 )
+it is written whenever more input is required to complete the
+current command.
+.It Ev PS4
+Output, after expansion like
+.Ev PS1 ,
+before each line when execution trace
+.Ic ( set Fl x )
+is enabled.
+.Ev PS4
+defaults to
+.Dq Li "+ " .
+.It Ev PSc
+Initialized by the shell, ignoring any value from the environment,
+to a single character string, either
+.Sq \&#
+or
+.Sq \&$ ,
+depending upon whether the current user is the superuser or not.
+This is intended for use when building a custom
+.Ev PS1 .
+.It Ev PSlit
+Defines the character which may be embedded in pairs, in
+.Ev PS1
+or
+.Ev PS2
+to indicate to
+.Xr editline 7
+that the characters between each pair of occurrences of the
+.Dv PSlit
+character will not appear in the visible prompt, and will not
+cause the terminal's cursor to change position, but rather set terminal
+attributes for the following prompt character(s) at least one of
+which must be present.
+See
+.Sx Command Line Editing
+above for more information.
+.It Ev RANDOM
+Returns a different pseudo-random integer,
+in the range [0,32767] each time it is accessed.
+.Ev RANDOM
+can be assigned an integer value to seed the PRNG.
+If the value assigned is a constant, then the
+sequence of values produces on subsequent references of
+.Ev RANDOM
+will repeat after the next time the same constant is assigned.
+Note, this is not guaranteed to remain constant from one version
+of the shell to another \(en the PRNG algorithm, or seeding
+method is subject to change.
+If
+.Ev RANDOM
+is assigned an empty value (null string) then the next time
+.Ev RANDOM
+is accessed, it will be seeded from a more genuinely random source.
+The sequence of pseudo-random numbers generated will not be able to
+be generated again (except by luck, whether good or bad, depends!)
+This is also how the initial seed is generated, if none has been
+assigned before
+.Ev RANDOM
+is first accessed after shell initialization.
+Should the error message
+.Dq "RANDOM initialisation failed"
+appear on standard error, it indicates that the source
+of good random numbers was not available, and
+.Ev RANDOM
+has instead been seeded with a more predictable value.
+The following sequence of random numbers will
+not be as unpredictable as they otherwise would be.
+.It Ev SECONDS
+Returns the number of seconds since the current shell was started.
+If unset, it remains unset, and returns nothing, unless set again.
+If set, it loses its special properties, and becomes a normal variable.
+.It Ev START_TIME
+Initialized by the shell to the number of seconds since the Epoch
+(see
+.Xr localtime 3 )
+when the shell was started.
+The value of
+.Dl $(( Ns Ev START_TIME + Ev SECONDS Ns ))
+represents the current time, if
+.Ev START_TIME
+has not been modified, and
+.Ev SECONDS
+has not been set or unset.
+.It Ev TERM
+The default terminal setting for the shell.
+This is inherited by
+children of the shell, and is used in the history editing modes.
+.\" This is explicitly last, not in sort order - please leave!
+.It Ev ToD
+When referenced, uses the value of
+.Ev ToD_FORMAT
+(or
+.Dq \&%T
+if
+.Ev ToD_FORMAT
+is unset) as the format argument to
+.Xr strftime 3
+to encode the current time of day, in the time zone
+defined by
+.Ev TZ
+if set, or current local time if not, and returns the result.
+If unset
+.Ev ToD
+returns nothing.
+If set, it loses its special properties, and becomes a normal variable.
+.It Ev ToD_FORMAT
+Can be set to the
+.Xr strftime 3
+format string to be used when expanding
+.Ev ToD .
+Initially unset.
+.It Ev TZ
+If set, gives the time zone
+(see
+.Xr localtime 3 ,
+.Xr environ 7 )
+to use when formatting
+.Ev ToD
+and if exported, other utilities that deal with times.
+If unset, the system's local wall clock time zone is used.
+.It Ev NETBSD_SHELL
+Unlike the variables previously mentioned,
+this variable is somewhat strange,
+in that it cannot be set,
+inherited from the environment,
+modified, or exported from the shell.
+If set, by the shell, it indicates that the shell is the
+.Ic sh
+defined by this manual page, and gives its version information.
+It can also give information in additional space separated words,
+after the version string.
+If the shell was built as part of a reproducible build,
+the relevant date that was used for that build will be included.
+Finally, any non-standard compilation options,
+which may affect features available,
+that were used when building the shell will be listed.
+.Ev NETBSD_SHELL
+behaves like any other variable that has the read-only
+and un-exportable attributes set.
+.El
+.Ss Dv LINENO
+.Dv LINENO
+is in many respects a normal shell variable, containing an
+integer value. and can be expanded using any of the forms
+mentioned above which can be used for any other variable.
+.Pp
+.Dv LINENO
+can be exported, made readonly, or unset, as with any other
+variable, with similar effects.
+Note that while being readonly prevents later attempts to
+set, or unset,
+.Dv LINENO ,
+it does not prevent its value changing.
+References to
+.Dv LINENO
+.Pq "when not unset"
+always obtain the current line number.
+However,
+.Dv LINENO
+should normally not ever be set or unset.
+In this shell setting
+.Dv LINENO
+reverses the effect of an earlier
+.Ic unset ,
+but does not otherwise affect the value obtained.
+If unset,
+.Dv LINENO
+should not normally be set again, doing so is not portable.
+If
+.Dv LINENO
+is set or unset, different shells act differently.
+The value of
+.Dv LINENO
+is never imported from the environment when the shell is
+started, though if present there, as with any other variable,
+.Dv LINENO
+will be exported by this shell.
+.Pp
+.Dv LINENO
+is set automatically by the shell to be the number of the source
+line on which it occurs.
+When exported,
+.Dv LINENO
+is exported with its value set to the line number it would have
+had had it been referenced on the command line of the command to
+which it is exported.
+Line numbers are counted from 1, which is the first line the shell
+reads from any particular file.
+For this shell, standard input, including in an interactive shell,
+the user's terminal, is just another file and lines are counted
+there as well.
+However note that not all shells count interactive
+lines this way, it is not wise to rely upon
+.Dv LINENO
+having a useful value, except in a script, or a function.
+.Pp
+The role of
+.Dv LINENO
+in functions is less clear.
+In some shells,
+.Dv LINENO
+continues to refer to the line number in the script which defines
+the function,
+in others lines count from one within the function, always (and
+resume counting normally once the function definition is complete)
+and others count in functions from one if the function is defined
+interactively, but otherwise just reference the line number in the
+script in which the function is defined.
+This shell gives the user the option to choose.
+If the
+.Fl L
+flag (the
+.Ic local_lineno
+option, see
+.Sx Argument List Processing )
+is set, when the function is defined, then the function
+defaults to counting lines with one being the first line of the
+function.
+When the
+.Fl L
+flag is not set, the shell counts lines in a function definition
+in the same continuous sequence as the lines that surround the
+function definition.
+Further, if
+.Dv LINENO
+is made local
+(see
+.Sx Built-ins
+above)
+inside the function, the function can decide which
+behavior it prefers.
+If
+.Dv LINENO
+is made local and inherited, and not given a value, as in
+.Dl local Fl I Dv LINENO
+then from that point in the function,
+.Dv LINENO
+will give the line number as if lines are counted in sequence
+with the lines that surround the function definition (and
+any other function definitions in which this is nested.)
+If
+.Dv LINENO
+is made local, and in that same command, given a value, as
+.Dl local Oo Fl I Ns | Ns Fl N Oc Dv LINENO Ns = Ns Ar value
+then
+.Dv LINENO
+will give the line number as if lines are counted from one
+from the beginning of the function.
+The value nominally assigned in this case is irrelevant, and ignored.
+For completeness, if lineno is made local and unset, as in
+.Dl local Fl N Dv LINENO
+then
+.Dv LINENO
+is simply unset inside the function, and gives no value at all.
+.Pp
+Now for some technical details.
+The line on which
+.Dv LINENO
+occurs in a parameter expansion, is the line that contains the
+.Sq \&$
+that begins the expansion of
+.Dv LINENO .
+In the case of nested expansions, that
+.Sq \&$
+is the one that actually has
+.Dv LINENO
+as its parameter.
+In an arithmetic expansion, where no
+.Sq \&$
+is used to evaluate
+.Dv LINENO
+but
+.Dv LINENO
+is simply referenced as a variable, then the value is the
+line number of the line that contains the
+.Sq L
+of
+.Dv LINENO .
+For functions line one of the function definition (when relevant)
+is the line that contains the first character of the
+function name in the definition.
+When exported, the line number of the command is the line number
+where the first character of the word which becomes the command name occurs.
+.Pp
+When the shell opens a new file, for any reason,
+it counts lines from one in that file,
+and then resumes its original counting once it resumes reading the
+previous input stream.
+When handling a string passed to
+.Ic eval
+the line number starts at the line on which the string starts,
+and then if the string contains internal newline characters,
+those characters increase the line number.
+This means that references to
+.Dv LINENO
+in such a case can produce values larger than would be
+produced by a reference on the line after the
+.Ic eval .
+.Sh FILES
+.Bl -item
+.It
+.Pa $HOME/.profile
+.It
+.Pa /etc/profile
+.El
+.Sh EXIT STATUS
+Errors that are detected by the shell, such as a syntax error, will cause the
+shell to exit with a non-zero exit status.
+If the shell is not an
+interactive shell, the execution of the shell file will be aborted.
+Otherwise
+the shell will return the exit status of the last command executed, or
+if the exit built-in is used with a numeric argument, it will return the
+argument.
+.Sh SEE ALSO
+.Xr csh 1 ,
+.Xr echo 1 ,
+.Xr getopt 1 ,
+.Xr ksh 1 ,
+.Xr login 1 ,
+.Xr printf 1 ,
+.Xr test 1 ,
+.Xr editline 3 ,
+.Xr getopt 3 ,
+.\" .Xr profile 4 ,
+.Xr editrc 5 ,
+.Xr passwd 5 ,
+.Xr editline 7 ,
+.Xr environ 7 ,
+.Xr nls 7 ,
+.Xr sysctl 8
+.Sh HISTORY
+A
+.Nm
+command appeared in
+.At v1 .
+It was replaced in
+.At v7
+with a version that introduced the basis of the current syntax.
+That was, however, unmaintainable so we wrote this one.
+.Sh BUGS
+Setuid shell scripts should be avoided at all costs, as they are a
+significant security risk.
+.Pp
+The characters generated by filename completion should probably be quoted
+to ensure that the filename is still valid after the input line has been
+processed.
+.Pp
+Job control of compound statements (loops, etc) is a complete mess.
+.Pp
+Many, many, more.
+(But less than there were...)
diff --git a/bin/sh/shell.h b/bin/sh/shell.h
new file mode 100644
index 0000000..318d56e
--- /dev/null
+++ b/bin/sh/shell.h
@@ -0,0 +1,224 @@
+/* $NetBSD: shell.h,v 1.29 2019/01/22 13:48:28 kre Exp $ */
+
+/*-
+ * Copyright (c) 1991, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Kenneth Almquist.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * @(#)shell.h 8.2 (Berkeley) 5/4/95
+ */
+
+/*
+ * The follow should be set to reflect the type of system you have:
+ * JOBS -> 1 if you have Berkeley job control, 0 otherwise.
+ * define BSD if you are running 4.2 BSD or later.
+ * define SYSV if you are running under System V.
+ * define DEBUG=1 to compile in debugging ('set -o debug' to turn on)
+ * define DEBUG=2 to compile in and enable debugging.
+ * define DEBUG=3 for DEBUG==2 + enable most standard debug output
+ * define DEBUG=4 for DEBUG==2 + enable absolutely everything
+ * define DO_SHAREDVFORK to indicate that vfork(2) shares its address
+ * with its parent.
+ * define BOGUS_NOT_COMMAND to allow ! reserved words in weird places
+ * (traditional ash behaviour.)
+ *
+ * When debugging is on, debugging info will be written to ./trace and
+ * a quit signal will generate a core dump.
+ */
+
+#ifndef SHELL_H
+#define SHELL_H
+#include <sys/param.h>
+
+#define JOBS 1
+#ifndef BSD
+#define BSD 1
+#endif
+
+#ifndef DO_SHAREDVFORK
+#if defined(__NetBSD_Version__) && __NetBSD_Version__ >= 104000000
+#define DO_SHAREDVFORK
+#endif
+#endif
+
+typedef void *pointer;
+#ifndef NULL
+#define NULL (void *)0
+#endif
+#ifndef STATIC
+#define STATIC /* empty */
+#endif
+#define MKINIT /* empty */
+
+#include <sys/cdefs.h>
+
+extern const char nullstr[1]; /* null string */
+
+#ifdef SMALL
+#undef DEBUG
+#endif
+
+#ifdef DEBUG
+
+extern uint64_t DFlags;
+extern int ShNest;
+
+/*
+ * This is selected as there are 26 letters in ascii - not that that
+ * matters for anything, just makes it easier to assign a different
+ * command letter to each debug option. We currently use only 18
+ * so this could be reduced, but that is of no real benefit. It can also
+ * be increased, but that both limits the maximum value tha can be
+ * used with DBG_EXTRAS(), and causes problems with verbose option naming.
+ */
+#define DBG_VBOSE_SHIFT 27
+#define DBG_EXTRAS(n) ((DBG_VBOSE_SHIFT * 2) + (n))
+
+/*
+ * Macros to enable tracing, so the mainainer can control
+ * just how much debug output is dumped to the trace file
+ *
+ * In the X forms, "xtra" can be any legal C statement(s) without (bare) commas
+ * executed if the relevant debug flag is enabled, after any tracing output.
+ */
+#define CTRACE(when, param) do { \
+ if ((DFlags & (when)) != 0) \
+ trace param; \
+ } while (/*CONSTCOND*/ 0)
+
+#define CCTRACE(when,cond,param) do { \
+ if ((cond) && (DFlags & (when)) != 0) \
+ trace param; \
+ } while (/*CONSTCOND*/ 0)
+
+#define CTRACEV(when, param) do { \
+ if ((DFlags & (when)) != 0) \
+ tracev param; \
+ } while (/*CONSTCOND*/ 0)
+
+#define XTRACE(when, param, xtra) do { \
+ if ((DFlags & (when)) != 0) { \
+ trace param; \
+ xtra; \
+ } \
+ } while (/*CONSTCOND*/ 0)
+
+#define VTRACE(when, param) do { \
+ if ((DFlags & \
+ (when)<<DBG_VBOSE_SHIFT) != 0) \
+ trace param; \
+ } while (/*CONSTCOND*/ 0)
+
+#define CVTRACE(when,cond,param) do { \
+ if ((cond) && (DFlags & \
+ (when)<<DBG_VBOSE_SHIFT) != 0) \
+ trace param; \
+ } while (/*CONSTCOND*/ 0)
+
+#define VTRACEV(when, param) do { \
+ if ((DFlags & \
+ (when)<<DBG_VBOSE_SHIFT) != 0) \
+ tracev param; \
+ } while (/*CONSTCOND*/ 0)
+
+#define VXTRACE(when, param, xtra) do { \
+ if ((DFlags & \
+ (when)<<DBG_VBOSE_SHIFT) != 0) {\
+ trace param; \
+ xtra; \
+ } \
+ } while (/*CONSTCOND*/ 0)
+
+#define SHELL_FORKED() ShNest++
+#define VFORK_BLOCK { const int _ShNest = ShNest;
+#define VFORK_END }
+#define VFORK_UNDO() ShNest = _ShNest
+
+#define DBG_ALWAYS (1LL << 0)
+#define DBG_PARSE (1LL << 1) /* r (read commands) */
+#define DBG_EVAL (1LL << 2) /* e */
+#define DBG_EXPAND (1LL << 3) /* x */
+#define DBG_JOBS (1LL << 4) /* j */
+#define DBG_PROCS (1LL << 5) /* p */
+#define DBG_REDIR (1LL << 6) /* f (fds) */
+#define DBG_CMDS (1LL << 7) /* c */
+#define DBG_ERRS (1LL << 8) /* z (?) */
+#define DBG_WAIT (1LL << 9) /* w */
+#define DBG_TRAP (1LL << 10) /* t */
+#define DBG_VARS (1LL << 11) /* v */
+#define DBG_INPUT (1LL << 12) /* i */
+#define DBG_OUTPUT (1LL << 13) /* o */
+#define DBG_MEM (1LL << 14) /* m */
+#define DBG_ARITH (1LL << 15) /* a */
+#define DBG_HISTORY (1LL << 16) /* h */
+#define DBG_SIG (1LL << 17) /* s */
+#define DBG_MATCH (1LL << 18) /* g (glob) */
+#define DBG_LEXER (1LL << 19) /* l */
+
+/*
+ * reserved extras: b=builtins y=alias
+ * still free: d k n q u
+ */
+
+ /* use VTRACE(DBG_ALWAYS, (...)) to test this one */
+#define DBG_VERBOSE (1LL << DBG_VBOSE_SHIFT)
+
+ /* DBG_EXTRAS 0 .. 9 (max) only - non-alpha options (no VTRACE !!) */
+#define DBG_U0 (1LL << DBG_EXTRAS(0)) /* 0 - ad-hoc extra flags */
+#define DBG_U1 (1LL << DBG_EXTRAS(1)) /* 1 - for short term */
+#define DBG_U2 (1LL << DBG_EXTRAS(2)) /* 2 - extra tracing */
+#define DBG_U3 (1LL << DBG_EXTRAS(3)) /* 3 - when needed */
+ /* 4, 5, & 6 currently free */
+#define DBG_LINE (1LL << DBG_EXTRAS(7)) /* @ ($LINENO) */
+#define DBG_PID (1LL << DBG_EXTRAS(8)) /* $ ($$) */
+#define DBG_NEST (1LL << DBG_EXTRAS(9)) /* ^ */
+
+/* 26 lower case, 26 upper case, always, verbose, and 10 extras: 64 bits */
+
+extern void set_debug(const char *, int);
+
+#else /* DEBUG */
+
+#define CTRACE(when, param) /* conditional normal trace */
+#define CCTRACE(when, cond, param) /* more conditional normal trace */
+#define CTRACEV(when, param) /* conditional varargs trace */
+#define XTRACE(when, param, extra) /* conditional trace, plus more */
+#define VTRACE(when, param) /* conditional verbose trace */
+#define CVTRACE(when, cond, param) /* more conditional verbose trace */
+#define VTRACEV(when, param) /* conditional verbose varargs trace */
+#define VXTRACE(when, param, extra) /* cond verbose trace, plus more */
+
+#define SHELL_FORKED()
+#define VFORK_BLOCK
+#define VFORK_END
+#define VFORK_UNDO()
+
+#endif /* DEBUG */
+
+#endif /* SHELL_H */
diff --git a/bin/sh/show.c b/bin/sh/show.c
new file mode 100644
index 0000000..fa9f5aa
--- /dev/null
+++ b/bin/sh/show.c
@@ -0,0 +1,1175 @@
+/* $NetBSD: show.c,v 1.52 2019/01/22 13:48:28 kre Exp $ */
+
+/*-
+ * Copyright (c) 1991, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * Copyright (c) 2017 The NetBSD Foundation, Inc. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Kenneth Almquist.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+#ifndef lint
+#if 0
+static char sccsid[] = "@(#)show.c 8.3 (Berkeley) 5/4/95";
+#else
+__RCSID("$NetBSD: show.c,v 1.52 2019/01/22 13:48:28 kre Exp $");
+#endif
+#endif /* not lint */
+
+#include <stdio.h>
+#include <stdarg.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <limits.h>
+
+#include <sys/types.h>
+#include <sys/uio.h>
+
+#include "shell.h"
+#include "parser.h"
+#include "nodes.h"
+#include "mystring.h"
+#include "show.h"
+#include "options.h"
+#include "redir.h"
+#include "error.h"
+#include "syntax.h"
+#include "input.h"
+#include "output.h"
+#include "var.h"
+#include "builtins.h"
+
+#define DEFINE_NODENAMES
+#include "nodenames.h" /* does almost nothing if !defined(DEBUG) */
+
+#define TR_STD_WIDTH 60 /* tend to fold lines wider than this */
+#define TR_IOVECS 10 /* number of lines or trace (max) / write */
+
+typedef struct traceinfo {
+ int tfd; /* file descriptor for open trace file */
+ int nxtiov; /* the buffer we should be writing to */
+ char lastc; /* the last non-white character output */
+ uint8_t supr; /* char classes to suppress after \n */
+ pid_t pid; /* process id of process that opened that file */
+ size_t llen; /* number of chars in current output line */
+ size_t blen; /* chars used in current buffer being filled */
+ char * tracefile; /* name of the tracefile */
+ struct iovec lines[TR_IOVECS]; /* filled, flling, pending buffers */
+} TFILE;
+
+/* These are auto turned off when non white space is printed */
+#define SUP_NL 0x01 /* don't print \n */
+#define SUP_SP 0x03 /* suppress spaces */
+#define SUP_WSP 0x04 /* suppress all white space */
+
+#ifdef DEBUG /* from here to end of file ... */
+
+TFILE tracedata, *tracetfile;
+FILE *tracefile; /* just for histedit */
+
+uint64_t DFlags; /* currently enabled debug flags */
+int ShNest; /* depth of shell (internal) nesting */
+
+static void shtree(union node *, int, int, int, TFILE *);
+static void shcmd(union node *, TFILE *);
+static void shsubsh(union node *, TFILE *);
+static void shredir(union node *, TFILE *, int);
+static void sharg(union node *, TFILE *);
+static void indent(int, TFILE *);
+static void trstring(const char *);
+static void trace_putc(char, TFILE *);
+static void trace_puts(const char *, TFILE *);
+static void trace_flush(TFILE *, int);
+static char *trace_id(TFILE *);
+static void trace_fd_swap(int, int);
+
+inline static int trlinelen(TFILE *);
+
+
+/*
+ * These functions are the externally visible interface
+ * (but only for a DEBUG shell.)
+ */
+
+void
+opentrace(void)
+{
+ char *s;
+ int fd;
+ int i;
+ pid_t pid;
+
+ if (debug != 1) {
+ /* leave fd open because libedit might be using it */
+ if (tracefile)
+ fflush(tracefile);
+ if (tracetfile)
+ trace_flush(tracetfile, 1);
+ return;
+ }
+#if DBG_PID == 1 /* using old shell.h, old tracing method */
+ DFlags = DBG_PID; /* just force DBG_PID on, and leave it ... */
+#endif
+ pid = getpid();
+ if (asprintf(&s, "trace.%jd", (intmax_t)pid) <= 0) {
+ debug = 0;
+ error("Cannot asprintf tracefilename");
+ };
+
+ fd = open(s, O_WRONLY|O_APPEND|O_CREAT, 0666);
+ if (fd == -1) {
+ debug = 0;
+ error("Can't open tracefile: %s (%s)\n", s, strerror(errno));
+ }
+ fd = to_upper_fd(fd);
+ if (fd <= 2) {
+ (void) close(fd);
+ debug = 0;
+ error("Attempt to use fd %d as tracefile thwarted\n", fd);
+ }
+ register_sh_fd(fd, trace_fd_swap);
+
+ /*
+ * This stuff is just so histedit has a FILE * to use
+ */
+ if (tracefile)
+ (void) fclose(tracefile); /* also closes tfd */
+ tracefile = fdopen(fd, "a"); /* don't care if it is NULL */
+ if (tracefile) /* except here... */
+ setlinebuf(tracefile);
+
+ /*
+ * Now the real tracing setup
+ */
+ if (tracedata.tfd > 0 && tracedata.tfd != fd)
+ (void) close(tracedata.tfd); /* usually done by fclose() */
+
+ tracedata.tfd = fd;
+ tracedata.pid = pid;
+ tracedata.nxtiov = 0;
+ tracedata.blen = 0;
+ tracedata.llen = 0;
+ tracedata.lastc = '\0';
+ tracedata.supr = SUP_NL | SUP_WSP;
+
+#define replace(f, v) do { \
+ if (tracedata.f != NULL) \
+ free(tracedata.f); \
+ tracedata.f = v; \
+ } while (/*CONSTCOND*/ 0)
+
+ replace(tracefile, s);
+
+ for (i = 0; i < TR_IOVECS; i++) {
+ replace(lines[i].iov_base, NULL);
+ tracedata.lines[i].iov_len = 0;
+ }
+
+#undef replace
+
+ tracetfile = &tracedata;
+
+ trace_puts("\nTracing started.\n", tracetfile);
+}
+
+void
+trace(const char *fmt, ...)
+{
+ va_list va;
+ char *s;
+
+ if (debug != 1 || !tracetfile)
+ return;
+ va_start(va, fmt);
+ (void) vasprintf(&s, fmt, va);
+ va_end(va);
+
+ trace_puts(s, tracetfile);
+ free(s);
+ if (tracetfile->llen == 0)
+ trace_flush(tracetfile, 0);
+}
+
+void
+tracev(const char *fmt, va_list va)
+{
+ va_list ap;
+ char *s;
+
+ if (debug != 1 || !tracetfile)
+ return;
+ va_copy(ap, va);
+ (void) vasprintf(&s, fmt, ap);
+ va_end(ap);
+
+ trace_puts(s, tracetfile);
+ free(s);
+ if (tracetfile->llen == 0)
+ trace_flush(tracetfile, 0);
+}
+
+
+void
+trputs(const char *s)
+{
+ if (debug != 1 || !tracetfile)
+ return;
+ trace_puts(s, tracetfile);
+}
+
+void
+trputc(int c)
+{
+ if (debug != 1 || !tracetfile)
+ return;
+ trace_putc(c, tracetfile);
+}
+
+void
+showtree(union node *n)
+{
+ TFILE *fp;
+
+ if ((fp = tracetfile) == NULL)
+ return;
+
+ trace_puts("showtree(", fp);
+ if (n == NULL)
+ trace_puts("NULL", fp);
+ else if (n == NEOF)
+ trace_puts("NEOF", fp);
+ else
+ trace("%p", n);
+ trace_puts(") called\n", fp);
+ if (n != NULL && n != NEOF)
+ shtree(n, 1, 1, 1, fp);
+}
+
+void
+trargs(char **ap)
+{
+ if (debug != 1 || !tracetfile)
+ return;
+ while (*ap) {
+ trstring(*ap++);
+ if (*ap)
+ trace_putc(' ', tracetfile);
+ }
+ trace_putc('\n', tracetfile);
+}
+
+void
+trargstr(union node *n)
+{
+ sharg(n, tracetfile);
+}
+
+
+/*
+ * Beyond here we just have the implementation of all of that
+ */
+
+
+inline static int
+trlinelen(TFILE * fp)
+{
+ return fp->llen;
+}
+
+static void
+shtree(union node *n, int ind, int ilvl, int nl, TFILE *fp)
+{
+ struct nodelist *lp;
+ const char *s;
+
+ if (n == NULL) {
+ if (nl)
+ trace_putc('\n', fp);
+ return;
+ }
+
+ indent(ind, fp);
+ switch (n->type) {
+ case NSEMI:
+ s = NULL;
+ goto binop;
+ case NAND:
+ s = " && ";
+ goto binop;
+ case NOR:
+ s = " || ";
+binop:
+ shtree(n->nbinary.ch1, 0, ilvl, 0, fp);
+ if (s != NULL)
+ trace_puts(s, fp);
+ if (trlinelen(fp) >= TR_STD_WIDTH) {
+ trace_putc('\n', fp);
+ indent(ind < 0 ? 2 : ind + 1, fp);
+ } else if (s == NULL) {
+ if (fp->lastc != '&')
+ trace_puts("; ", fp);
+ else
+ trace_putc(' ', fp);
+ }
+ shtree(n->nbinary.ch2, 0, ilvl, nl, fp);
+ break;
+ case NCMD:
+ shcmd(n, fp);
+ if (n->ncmd.backgnd)
+ trace_puts(" &", fp);
+ if (nl && trlinelen(fp) > 0)
+ trace_putc('\n', fp);
+ break;
+ case NPIPE:
+ for (lp = n->npipe.cmdlist ; lp ; lp = lp->next) {
+ shtree(lp->n, 0, ilvl, 0, fp);
+ if (lp->next) {
+ trace_puts(" |", fp);
+ if (trlinelen(fp) >= TR_STD_WIDTH) {
+ trace_putc('\n', fp);
+ indent((ind < 0 ? ilvl : ind) + 1, fp);
+ } else
+ trace_putc(' ', fp);
+ }
+ }
+ if (n->npipe.backgnd)
+ trace_puts(" &", fp);
+ if (nl || trlinelen(fp) >= TR_STD_WIDTH)
+ trace_putc('\n', fp);
+ break;
+ case NBACKGND:
+ case NSUBSHELL:
+ shsubsh(n, fp);
+ if (n->type == NBACKGND)
+ trace_puts(" &", fp);
+ if (nl && trlinelen(fp) > 0)
+ trace_putc('\n', fp);
+ break;
+ case NDEFUN:
+ trace_puts(n->narg.text, fp);
+ trace_puts("() {\n", fp);
+ indent(ind, fp);
+ shtree(n->narg.next, (ind < 0 ? ilvl : ind) + 1, ilvl+1, 1, fp);
+ indent(ind, fp);
+ trace_puts("}\n", fp);
+ break;
+ case NDNOT:
+ trace_puts("! ", fp);
+ /* FALLTHROUGH */
+ case NNOT:
+ trace_puts("! ", fp);
+ shtree(n->nnot.com, -1, ilvl, nl, fp);
+ break;
+ case NREDIR:
+ shtree(n->nredir.n, -1, ilvl, 0, fp);
+ shredir(n->nredir.redirect, fp, n->nredir.n == NULL);
+ if (nl)
+ trace_putc('\n', fp);
+ break;
+
+ case NIF:
+ itsif:
+ trace_puts("if ", fp);
+ shtree(n->nif.test, -1, ilvl, 0, fp);
+ if (trlinelen(fp) > 0 && trlinelen(fp) < TR_STD_WIDTH) {
+ if (fp->lastc != '&')
+ trace_puts(" ;", fp);
+ } else
+ indent(ilvl, fp);
+ trace_puts(" then ", fp);
+ if (nl || trlinelen(fp) > TR_STD_WIDTH - 24)
+ indent(ilvl+1, fp);
+ shtree(n->nif.ifpart, -1, ilvl + 1, 0, fp);
+ if (trlinelen(fp) > 0 && trlinelen(fp) < TR_STD_WIDTH) {
+ if (fp->lastc != '&')
+ trace_puts(" ;", fp);
+ } else
+ indent(ilvl, fp);
+ if (n->nif.elsepart && n->nif.elsepart->type == NIF) {
+ if (nl || trlinelen(fp) > TR_STD_WIDTH - 24)
+ indent(ilvl, fp);
+ n = n->nif.elsepart;
+ trace_puts(" el", fp);
+ goto itsif;
+ }
+ if (n->nif.elsepart) {
+ if (nl || trlinelen(fp) > TR_STD_WIDTH - 24)
+ indent(ilvl+1, fp);
+ trace_puts(" else ", fp);
+ shtree(n->nif.elsepart, -1, ilvl + 1, 0, fp);
+ if (fp->lastc != '&')
+ trace_puts(" ;", fp);
+ }
+ trace_puts(" fi", fp);
+ if (nl)
+ trace_putc('\n', fp);
+ break;
+
+ case NWHILE:
+ trace_puts("while ", fp);
+ goto aloop;
+ case NUNTIL:
+ trace_puts("until ", fp);
+ aloop:
+ shtree(n->nbinary.ch1, -1, ilvl, 0, fp);
+ if (trlinelen(fp) > 0 && trlinelen(fp) < TR_STD_WIDTH) {
+ if (fp->lastc != '&')
+ trace_puts(" ;", fp);
+ } else
+ trace_putc('\n', fp);
+ trace_puts(" do ", fp);
+ shtree(n->nbinary.ch1, -1, ilvl + 1, 1, fp);
+ trace_puts(" done ", fp);
+ if (nl)
+ trace_putc('\n', fp);
+ break;
+
+ case NFOR:
+ trace_puts("for ", fp);
+ trace_puts(n->nfor.var, fp);
+ if (n->nfor.args) {
+ union node *argp;
+
+ trace_puts(" in ", fp);
+ for (argp = n->nfor.args; argp; argp=argp->narg.next) {
+ sharg(argp, fp);
+ trace_putc(' ', fp);
+ }
+ if (trlinelen(fp) > 0 && trlinelen(fp) < TR_STD_WIDTH) {
+ if (fp->lastc != '&')
+ trace_putc(';', fp);
+ } else
+ trace_putc('\n', fp);
+ }
+ trace_puts(" do ", fp);
+ shtree(n->nfor.body, -1, ilvl + 1, 0, fp);
+ if (fp->lastc != '&')
+ trace_putc(';', fp);
+ trace_puts(" done", fp);
+ if (nl)
+ trace_putc('\n', fp);
+ break;
+
+ case NCASE:
+ trace_puts("case ", fp);
+ sharg(n->ncase.expr, fp);
+ trace_puts(" in", fp);
+ if (nl)
+ trace_putc('\n', fp);
+ {
+ union node *cp;
+
+ for (cp = n->ncase.cases ; cp ; cp = cp->nclist.next) {
+ union node *patp;
+
+ if (nl || trlinelen(fp) > TR_STD_WIDTH - 16)
+ indent(ilvl, fp);
+ else
+ trace_putc(' ', fp);
+ trace_putc('(', fp);
+ patp = cp->nclist.pattern;
+ while (patp != NULL) {
+ trace_putc(' ', fp);
+ sharg(patp, fp);
+ trace_putc(' ', fp);
+ if ((patp = patp->narg.next) != NULL)
+ trace_putc('|', fp);
+ }
+ trace_putc(')', fp);
+ if (nl)
+ indent(ilvl + 1, fp);
+ else
+ trace_putc(' ', fp);
+ shtree(cp->nclist.body, -1, ilvl+2, 0, fp);
+ if (cp->type == NCLISTCONT)
+ trace_puts(" ;&", fp);
+ else
+ trace_puts(" ;;", fp);
+ if (nl)
+ trace_putc('\n', fp);
+ }
+ }
+ if (nl) {
+ trace_putc('\n', fp);
+ indent(ind, fp);
+ } else
+ trace_putc(' ', fp);
+ trace_puts("esac", fp);
+ if (nl)
+ trace_putc('\n', fp);
+ break;
+
+ default: {
+ char *str;
+
+ asprintf(&str, "<node type %d [%s]>", n->type,
+ NODETYPENAME(n->type));
+ trace_puts(str, fp);
+ free(str);
+ if (nl)
+ trace_putc('\n', fp);
+ }
+ break;
+ }
+}
+
+
+static void
+shcmd(union node *cmd, TFILE *fp)
+{
+ union node *np;
+ int first;
+
+ first = 1;
+ for (np = cmd->ncmd.args ; np ; np = np->narg.next) {
+ if (! first)
+ trace_putc(' ', fp);
+ sharg(np, fp);
+ first = 0;
+ }
+ shredir(cmd->ncmd.redirect, fp, first);
+}
+
+static void
+shsubsh(union node *cmd, TFILE *fp)
+{
+ trace_puts(" ( ", fp);
+ shtree(cmd->nredir.n, -1, 3, 0, fp);
+ trace_puts(" ) ", fp);
+ shredir(cmd->ncmd.redirect, fp, 1);
+}
+
+static void
+shredir(union node *np, TFILE *fp, int first)
+{
+ const char *s;
+ int dftfd;
+ char buf[106];
+
+ for ( ; np ; np = np->nfile.next) {
+ if (! first)
+ trace_putc(' ', fp);
+ switch (np->nfile.type) {
+ case NTO: s = ">"; dftfd = 1; break;
+ case NCLOBBER: s = ">|"; dftfd = 1; break;
+ case NAPPEND: s = ">>"; dftfd = 1; break;
+ case NTOFD: s = ">&"; dftfd = 1; break;
+ case NFROM: s = "<"; dftfd = 0; break;
+ case NFROMFD: s = "<&"; dftfd = 0; break;
+ case NFROMTO: s = "<>"; dftfd = 0; break;
+ case NXHERE: /* FALLTHROUGH */
+ case NHERE: s = "<<"; dftfd = 0; break;
+ default: s = "*error*"; dftfd = 0; break;
+ }
+ if (np->nfile.fd != dftfd) {
+ sprintf(buf, "%d", np->nfile.fd);
+ trace_puts(buf, fp);
+ }
+ trace_puts(s, fp);
+ if (np->nfile.type == NTOFD || np->nfile.type == NFROMFD) {
+ if (np->ndup.vname)
+ sharg(np->ndup.vname, fp);
+ else {
+ if (np->ndup.dupfd < 0)
+ trace_puts("-", fp);
+ else {
+ sprintf(buf, "%d", np->ndup.dupfd);
+ trace_puts(buf, fp);
+ }
+ }
+ } else
+ if (np->nfile.type == NHERE || np->nfile.type == NXHERE) {
+ if (np->nfile.type == NHERE)
+ trace_putc('\\', fp);
+ trace_puts("!!!\n", fp);
+ s = np->nhere.doc->narg.text;
+ if (strlen(s) > 100) {
+ memmove(buf, s, 100);
+ buf[100] = '\0';
+ strcat(buf, " ...\n");
+ s = buf;
+ }
+ trace_puts(s, fp);
+ trace_puts("!!! ", fp);
+ } else {
+ sharg(np->nfile.fname, fp);
+ }
+ first = 0;
+ }
+}
+
+static void
+sharg(union node *arg, TFILE *fp)
+{
+ char *p, *s;
+ struct nodelist *bqlist;
+ int subtype = 0;
+ int quoted = 0;
+
+ if (arg->type != NARG) {
+ asprintf(&s, "<node type %d> ! NARG\n", arg->type);
+ trace_puts(s, fp);
+ abort(); /* no need to free s, better not to */
+ }
+
+ bqlist = arg->narg.backquote;
+ for (p = arg->narg.text ; *p ; p++) {
+ switch (*p) {
+ case CTLESC:
+ trace_putc('\\', fp);
+ trace_putc(*++p, fp);
+ break;
+
+ case CTLNONL:
+ trace_putc('\\', fp);
+ trace_putc('\n', fp);
+ break;
+
+ case CTLVAR:
+ subtype = *++p;
+ if (!quoted != !(subtype & VSQUOTE))
+ trace_putc('"', fp);
+ trace_putc('$', fp);
+ trace_putc('{', fp); /*}*/
+ if ((subtype & VSTYPE) == VSLENGTH)
+ trace_putc('#', fp);
+ if (subtype & VSLINENO)
+ trace_puts("LINENO=", fp);
+
+ while (*++p != '=')
+ trace_putc(*p, fp);
+
+ if (subtype & VSNUL)
+ trace_putc(':', fp);
+
+ switch (subtype & VSTYPE) {
+ case VSNORMAL:
+ /* { */
+ trace_putc('}', fp);
+ if (!quoted != !(subtype & VSQUOTE))
+ trace_putc('"', fp);
+ break;
+ case VSMINUS:
+ trace_putc('-', fp);
+ break;
+ case VSPLUS:
+ trace_putc('+', fp);
+ break;
+ case VSQUESTION:
+ trace_putc('?', fp);
+ break;
+ case VSASSIGN:
+ trace_putc('=', fp);
+ break;
+ case VSTRIMLEFTMAX:
+ trace_putc('#', fp);
+ /* FALLTHROUGH */
+ case VSTRIMLEFT:
+ trace_putc('#', fp);
+ break;
+ case VSTRIMRIGHTMAX:
+ trace_putc('%', fp);
+ /* FALLTHROUGH */
+ case VSTRIMRIGHT:
+ trace_putc('%', fp);
+ break;
+ case VSLENGTH:
+ break;
+ default: {
+ char str[32];
+
+ snprintf(str, sizeof str,
+ "<subtype %d>", subtype);
+ trace_puts(str, fp);
+ }
+ break;
+ }
+ break;
+ case CTLENDVAR:
+ /* { */
+ trace_putc('}', fp);
+ if (!quoted != !(subtype & VSQUOTE))
+ trace_putc('"', fp);
+ subtype = 0;
+ break;
+
+ case CTLBACKQ|CTLQUOTE:
+ if (!quoted)
+ trace_putc('"', fp);
+ /* FALLTHRU */
+ case CTLBACKQ:
+ trace_putc('$', fp);
+ trace_putc('(', fp);
+ if (bqlist) {
+ shtree(bqlist->n, -1, 3, 0, fp);
+ bqlist = bqlist->next;
+ } else
+ trace_puts("???", fp);
+ trace_putc(')', fp);
+ if (!quoted && *p == (CTLBACKQ|CTLQUOTE))
+ trace_putc('"', fp);
+ break;
+
+ case CTLQUOTEMARK:
+ if (subtype != 0 || !quoted) {
+ trace_putc('"', fp);
+ quoted++;
+ }
+ break;
+ case CTLQUOTEEND:
+ trace_putc('"', fp);
+ quoted--;
+ break;
+ case CTLARI:
+ if (*p == ' ')
+ p++;
+ trace_puts("$(( ", fp);
+ break;
+ case CTLENDARI:
+ trace_puts(" ))", fp);
+ break;
+
+ default:
+ if (*p == '$')
+ trace_putc('\\', fp);
+ trace_putc(*p, fp);
+ break;
+ }
+ }
+ if (quoted)
+ trace_putc('"', fp);
+}
+
+
+static void
+indent(int amount, TFILE *fp)
+{
+ int i;
+
+ if (amount <= 0)
+ return;
+
+ amount <<= 2; /* indent slots -> chars */
+
+ i = trlinelen(fp);
+ fp->supr = SUP_NL;
+ if (i > amount) {
+ trace_putc('\n', fp);
+ i = 0;
+ }
+ fp->supr = 0;
+ for (; i < amount - 7 ; i++) {
+ trace_putc('\t', fp);
+ i |= 7;
+ }
+ while (i < amount) {
+ trace_putc(' ', fp);
+ i++;
+ }
+ fp->supr = SUP_WSP;
+}
+
+static void
+trace_putc(char c, TFILE *fp)
+{
+ char *p;
+
+ if (c == '\0')
+ return;
+ if (debug == 0 || fp == NULL)
+ return;
+
+ if (fp->llen == 0) {
+ if (fp->blen != 0)
+ abort();
+
+ if ((fp->supr & SUP_NL) && c == '\n')
+ return;
+ if ((fp->supr & (SUP_WSP|SUP_SP)) && c == ' ')
+ return;
+ if ((fp->supr & SUP_WSP) && c == '\t')
+ return;
+
+ if (fp->nxtiov >= TR_IOVECS - 1) /* should be rare */
+ trace_flush(fp, 0);
+
+ p = trace_id(fp);
+ if (p != NULL) {
+ fp->lines[fp->nxtiov].iov_base = p;
+ fp->lines[fp->nxtiov].iov_len = strlen(p);
+ fp->nxtiov++;
+ }
+ } else if (fp->blen && fp->blen >= fp->lines[fp->nxtiov].iov_len) {
+ fp->blen = 0;
+ if (++fp->nxtiov >= TR_IOVECS)
+ trace_flush(fp, 0);
+ }
+
+ if (fp->lines[fp->nxtiov].iov_len == 0) {
+ p = (char *)malloc(2 * TR_STD_WIDTH);
+ if (p == NULL) {
+ trace_flush(fp, 1);
+ debug = 0;
+ return;
+ }
+ *p = '\0';
+ fp->lines[fp->nxtiov].iov_base = p;
+ fp->lines[fp->nxtiov].iov_len = 2 * TR_STD_WIDTH;
+ fp->blen = 0;
+ }
+
+ p = (char *)fp->lines[fp->nxtiov].iov_base + fp->blen++;
+ *p++ = c;
+ *p = 0;
+
+ if (c != ' ' && c != '\t' && c != '\n') {
+ fp->lastc = c;
+ fp->supr = 0;
+ }
+
+ if (c == '\n') {
+ fp->lines[fp->nxtiov++].iov_len = fp->blen;
+ fp->blen = 0;
+ fp->llen = 0;
+ fp->supr |= SUP_NL;
+ return;
+ }
+
+ if (c == '\t')
+ fp->llen |= 7;
+ fp->llen++;
+}
+
+void
+trace_flush(TFILE *fp, int all)
+{
+ int niov, i;
+ ssize_t written;
+
+ niov = fp->nxtiov;
+ if (all && fp->blen > 0) {
+ fp->lines[niov].iov_len = fp->blen;
+ fp->blen = 0;
+ fp->llen = 0;
+ niov++;
+ }
+ if (niov == 0)
+ return;
+ if (fp->blen > 0 && --niov == 0)
+ return;
+ written = writev(fp->tfd, fp->lines, niov);
+ for (i = 0; i < niov; i++) {
+ free(fp->lines[i].iov_base);
+ fp->lines[i].iov_base = NULL;
+ fp->lines[i].iov_len = 0;
+ }
+ if (written == -1) {
+ if (fp->blen > 0) {
+ free(fp->lines[niov].iov_base);
+ fp->lines[niov].iov_base = NULL;
+ fp->lines[niov].iov_len = 0;
+ }
+ debug = 0;
+ fp->blen = 0;
+ fp->llen = 0;
+ return;
+ }
+ if (fp->blen > 0) {
+ fp->lines[0].iov_base = fp->lines[niov].iov_base;
+ fp->lines[0].iov_len = fp->lines[niov].iov_len;
+ fp->lines[niov].iov_base = NULL;
+ fp->lines[niov].iov_len = 0;
+ }
+ fp->nxtiov = 0;
+}
+
+void
+trace_puts(const char *s, TFILE *fp)
+{
+ char c;
+
+ while ((c = *s++) != '\0')
+ trace_putc(c, fp);
+}
+
+inline static char *
+trace_id(TFILE *tf)
+{
+ int i;
+ char indent[16];
+ char *p;
+ int lno;
+ char c;
+
+ if (DFlags & DBG_NEST) {
+ if ((unsigned)ShNest >= sizeof indent - 1) {
+ (void) snprintf(indent, sizeof indent,
+ "### %*d ###", (int)(sizeof indent) - 9, ShNest);
+ p = strchr(indent, '\0');
+ } else {
+ p = indent;
+ for (i = 0; i < 6; i++)
+ *p++ = (i < ShNest) ? '#' : ' ';
+ while (i++ < ShNest && p < &indent[sizeof indent - 1])
+ *p++ = '#';
+ *p = '\0';
+ }
+ } else
+ indent[0] = '\0';
+
+ /*
+ * If we are in the parser, then plinno is the current line
+ * number being processed (parser line no).
+ * If we are elsewhere, then line_number gives the source
+ * line of whatever we are currently doing (close enough.)
+ */
+ if (parsing)
+ lno = plinno;
+ else
+ lno = line_number;
+
+ c = ((i = getpid()) == tf->pid) ? ':' : '=';
+
+ if (DFlags & DBG_PID) {
+ if (DFlags & DBG_LINE)
+ (void) asprintf(&p, "%5d%c%s\t%4d%c@\t", i, c,
+ indent, lno, parsing?'-':'+');
+ else
+ (void) asprintf(&p, "%5d%c%s\t", i, c, indent);
+ return p;
+ } else if (DFlags & DBG_NEST) {
+ if (DFlags & DBG_LINE)
+ (void) asprintf(&p, "%c%s\t%4d%c@\t", c, indent, lno,
+ parsing?'-':'+');
+ else
+ (void) asprintf(&p, "%c%s\t", c, indent);
+ return p;
+ } else if (DFlags & DBG_LINE) {
+ (void) asprintf(&p, "%c%4d%c@\t", c, lno, parsing?'-':'+');
+ return p;
+ }
+ return NULL;
+}
+
+/*
+ * Used only from trargs(), which itself is used only to print
+ * arg lists (argv[]) either that passed into this shell, or
+ * the arg list about to be given to some other command (incl
+ * builtin, and function) as their argv[]. If any of the CTL*
+ * chars seem to appear, they really should be just treated as data,
+ * not special... But this is just debug, so, who cares!
+ */
+static void
+trstring(const char *s)
+{
+ const char *p;
+ char c;
+ TFILE *fp;
+
+ if (debug != 1 || !tracetfile)
+ return;
+ fp = tracetfile;
+ trace_putc('"', fp);
+ for (p = s ; *p ; p++) {
+ switch (*p) {
+ case '\n': c = 'n'; goto backslash;
+ case '\t': c = 't'; goto backslash;
+ case '\r': c = 'r'; goto backslash;
+ case '"': c = '"'; goto backslash;
+ case '\\': c = '\\'; goto backslash;
+ case CTLESC: c = 'e'; goto backslash;
+ case CTLVAR: c = 'v'; goto backslash;
+ case CTLVAR+CTLQUOTE: c = 'V'; goto backslash;
+ case CTLBACKQ: c = 'q'; goto backslash;
+ case CTLBACKQ+CTLQUOTE: c = 'Q'; goto backslash;
+backslash: trace_putc('\\', fp);
+ trace_putc(c, fp);
+ break;
+ default:
+ if (*p >= ' ' && *p <= '~')
+ trace_putc(*p, fp);
+ else {
+ trace_putc('\\', fp);
+ trace_putc(*p >> 6 & 03, fp);
+ trace_putc(*p >> 3 & 07, fp);
+ trace_putc(*p & 07, fp);
+ }
+ break;
+ }
+ }
+ trace_putc('"', fp);
+}
+
+/*
+ * deal with the user "accidentally" picking our fd to use.
+ */
+static void
+trace_fd_swap(int from, int to)
+{
+ if (tracetfile == NULL || from == to)
+ return;
+
+ tracetfile->tfd = to;
+
+ /*
+ * This is just so histedit has a stdio FILE* to use.
+ */
+ if (tracefile)
+ fclose(tracefile);
+ tracefile = fdopen(to, "a");
+ if (tracefile)
+ setlinebuf(tracefile);
+}
+
+
+static struct debug_flag {
+ char label;
+ uint64_t flag;
+} debug_flags[] = {
+ { 'a', DBG_ARITH }, /* arithmetic ( $(( )) ) */
+ { 'c', DBG_CMDS }, /* command searching, ... */
+ { 'e', DBG_EVAL }, /* evaluation of the parse tree */
+ { 'f', DBG_REDIR }, /* file descriptors & redirections */
+ { 'g', DBG_MATCH }, /* pattern matching (glob) */
+ { 'h', DBG_HISTORY }, /* history & cmd line editing */
+ { 'i', DBG_INPUT }, /* shell input routines */
+ { 'j', DBG_JOBS }, /* job control, structures */
+ { 'l', DBG_LEXER }, /* lexical analysis */
+ { 'm', DBG_MEM }, /* memory management */
+ { 'o', DBG_OUTPUT }, /* output routines */
+ { 'p', DBG_PROCS }, /* process management, fork, ... */
+ { 'r', DBG_PARSE }, /* parser, lexer, ... tree building */
+ { 's', DBG_SIG }, /* signals and everything related */
+ { 't', DBG_TRAP }, /* traps & signals */
+ { 'v', DBG_VARS }, /* variables and parameters */
+ { 'w', DBG_WAIT }, /* waits for processes to finish */
+ { 'x', DBG_EXPAND }, /* word expansion ${} $() $(( )) */
+ { 'z', DBG_ERRS }, /* error control, jumps, cleanup */
+
+ { '0', DBG_U0 }, /* ad-hoc temp debug flag #0 */
+ { '1', DBG_U1 }, /* ad-hoc temp debug flag #1 */
+ { '2', DBG_U2 }, /* ad-hoc temp debug flag #2 */
+ { '3', DBG_U3 }, /* ad-hoc temp debug flag #3 */
+
+ { '@', DBG_LINE }, /* prefix trace lines with line# */
+ { '$', DBG_PID }, /* prefix trace lines with sh pid */
+ { '^', DBG_NEST }, /* show shell nesting level */
+
+ /* alpha options only - but not DBG_LEXER */
+ { '_', DBG_PARSE | DBG_EVAL | DBG_EXPAND | DBG_JOBS | DBG_SIG |
+ DBG_PROCS | DBG_REDIR | DBG_CMDS | DBG_ERRS |
+ DBG_WAIT | DBG_TRAP | DBG_VARS | DBG_MEM | DBG_MATCH |
+ DBG_INPUT | DBG_OUTPUT | DBG_ARITH | DBG_HISTORY },
+
+ /* { '*', DBG_ALLVERBOSE }, is handled in the code */
+
+ { '#', DBG_U0 | DBG_U1 | DBG_U2 | DBG_U3 },
+
+ { 0, 0 }
+};
+
+void
+set_debug(const char * flags, int on)
+{
+ char f;
+ struct debug_flag *df;
+ int verbose;
+
+ while ((f = *flags++) != '\0') {
+ verbose = 0;
+ if (is_upper(f)) {
+ verbose = 1;
+ f += 'a' - 'A';
+ }
+ if (f == '*')
+ f = '_', verbose = 1;
+ if (f == '+') {
+ if (*flags == '+')
+ flags++, verbose=1;
+ }
+
+ /*
+ * Note: turning on any debug option also enables DBG_ALWAYS
+ * turning on any verbose option also enables DBG_VERBOSE
+ * Once enabled, those flags cannot be disabled.
+ * (tracing can still be turned off with "set +o debug")
+ */
+ for (df = debug_flags; df->label != '\0'; df++) {
+ if (f == '+' || df->label == f) {
+ if (on) {
+ DFlags |= DBG_ALWAYS | df->flag;
+ if (verbose)
+ DFlags |= DBG_VERBOSE |
+ (df->flag << DBG_VBOSE_SHIFT);
+ } else {
+ DFlags &= ~(df->flag<<DBG_VBOSE_SHIFT);
+ if (!verbose)
+ DFlags &= ~df->flag;
+ }
+ }
+ }
+ }
+}
+
+
+int
+debugcmd(int argc, char **argv)
+{
+ if (argc == 1) {
+ struct debug_flag *df;
+
+ out1fmt("Debug: %sabled. Flags: ", debug ? "en" : "dis");
+ for (df = debug_flags; df->label != '\0'; df++) {
+ if (df->flag & (df->flag - 1))
+ continue;
+ if (is_alpha(df->label) &&
+ (df->flag << DBG_VBOSE_SHIFT) & DFlags)
+ out1c(df->label - ('a' - 'A'));
+ else if (df->flag & DFlags)
+ out1c(df->label);
+ }
+ out1c('\n');
+ return 0;
+ }
+
+ while (*++argv) {
+ if (**argv == '-')
+ set_debug(*argv + 1, 1);
+ else if (**argv == '+')
+ set_debug(*argv + 1, 0);
+ else
+ return 1;
+ }
+ return 0;
+}
+
+#endif /* DEBUG */
diff --git a/bin/sh/show.h b/bin/sh/show.h
new file mode 100644
index 0000000..2c48873
--- /dev/null
+++ b/bin/sh/show.h
@@ -0,0 +1,46 @@
+/* $NetBSD: show.h,v 1.11 2017/06/30 23:00:40 kre Exp $ */
+
+/*-
+ * Copyright (c) 1995
+ * The Regents of the University of California. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * @(#)show.h 1.1 (Berkeley) 5/4/95
+ */
+
+#include <stdarg.h>
+
+#ifdef DEBUG
+union node;
+void showtree(union node *);
+void trace(const char *, ...);
+void tracev(const char *, va_list);
+void trargs(char **);
+void trargstr(union node *);
+void trputc(int);
+void trputs(const char *);
+void opentrace(void);
+#endif
diff --git a/bin/sh/syntax.c b/bin/sh/syntax.c
new file mode 100644
index 0000000..7b460d4
--- /dev/null
+++ b/bin/sh/syntax.c
@@ -0,0 +1,111 @@
+/* $NetBSD: syntax.c,v 1.7 2018/12/03 06:40:26 kre Exp $ */
+
+#include <sys/cdefs.h>
+__RCSID("$NetBSD: syntax.c,v 1.7 2018/12/03 06:40:26 kre Exp $");
+
+#include <limits.h>
+#include "shell.h"
+#include "syntax.h"
+#include "parser.h"
+
+#if CWORD != 0
+#error initialisation assumes 'CWORD' is zero
+#endif
+
+#define ndx(ch) (ch + 2 - CHAR_MIN)
+#define set(ch, val) [ndx(ch)] = val,
+#define set_range(s, e, val) [ndx(s) ... ndx(e)] = val,
+
+/* syntax table used when not in quotes */
+const char basesyntax[258] = { CFAKE, CEOF,
+ set_range(CTL_FIRST, CTL_LAST, CCTL)
+ set('\n', CNL)
+ set('\\', CBACK)
+ set('\'', CSQUOTE)
+ set('"', CDQUOTE)
+ set('`', CBQUOTE)
+ set('$', CVAR)
+ set('}', CENDVAR)
+ set('<', CSPCL)
+ set('>', CSPCL)
+ set('(', CSPCL)
+ set(')', CSPCL)
+ set(';', CSPCL)
+ set('&', CSPCL)
+ set('|', CSPCL)
+ set(' ', CSPCL)
+ set('\t', CSPCL)
+};
+
+/* syntax table used when in double quotes */
+const char dqsyntax[258] = { CFAKE, CEOF,
+ set_range(CTL_FIRST, CTL_LAST, CCTL)
+ set('\n', CNL)
+ set('\\', CBACK)
+ set('"', CDQUOTE)
+ set('`', CBQUOTE)
+ set('$', CVAR)
+ set('}', CENDVAR)
+ /* ':/' for tilde expansion, '-]' for [a\-x] pattern ranges */
+ set('!', CCTL)
+ set('*', CCTL)
+ set('?', CCTL)
+ set('[', CCTL)
+ set('=', CCTL)
+ set('~', CCTL)
+ set(':', CCTL)
+ set('/', CCTL)
+ set('-', CCTL)
+ set(']', CCTL)
+};
+
+/* syntax table used when in single quotes */
+const char sqsyntax[258] = { CFAKE, CEOF,
+ set_range(CTL_FIRST, CTL_LAST, CCTL)
+ set('\n', CNL)
+ set('\'', CSQUOTE)
+ set('\\', CSBACK)
+ /* ':/' for tilde expansion, '-]' for [a\-x] pattern ranges */
+ set('!', CCTL)
+ set('*', CCTL)
+ set('?', CCTL)
+ set('[', CCTL)
+ set('=', CCTL)
+ set('~', CCTL)
+ set(':', CCTL)
+ set('/', CCTL)
+ set('-', CCTL)
+ set(']', CCTL)
+};
+
+/* syntax table used when in arithmetic */
+const char arisyntax[258] = { CFAKE, CEOF,
+ set_range(CTL_FIRST, CTL_LAST, CCTL)
+ set('\n', CNL)
+ set('\\', CBACK)
+ set('`', CBQUOTE)
+ set('\'', CSQUOTE)
+ set('"', CDQUOTE)
+ set('$', CVAR)
+ set('}', CENDVAR)
+ set('(', CLP)
+ set(')', CRP)
+};
+
+/* character classification table */
+const char is_type[258] = { 0, 0,
+ set_range('0', '9', ISDIGIT)
+ set_range('a', 'z', ISLOWER)
+ set_range('A', 'Z', ISUPPER)
+ set('_', ISUNDER)
+ set('#', ISSPECL)
+ set('?', ISSPECL)
+ set('$', ISSPECL)
+ set('!', ISSPECL)
+ set('-', ISSPECL)
+ set('*', ISSPECL)
+ set('@', ISSPECL)
+ set(' ', ISSPACE)
+ set('\t', ISSPACE)
+ set('\n', ISSPACE)
+};
diff --git a/bin/sh/syntax.h b/bin/sh/syntax.h
new file mode 100644
index 0000000..ad064b8
--- /dev/null
+++ b/bin/sh/syntax.h
@@ -0,0 +1,98 @@
+/* $NetBSD: syntax.h,v 1.11 2018/12/03 06:40:26 kre Exp $ */
+
+/*-
+ * Copyright (c) 1991, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Kenneth Almquist.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+#include <ctype.h>
+
+/* Syntax classes */
+#define CWORD 0 /* character is nothing special */
+#define CNL 1 /* newline character */
+#define CBACK 2 /* a backslash character */
+#define CSQUOTE 3 /* single quote */
+#define CDQUOTE 4 /* double quote */
+#define CBQUOTE 5 /* backwards single quote */
+#define CVAR 6 /* a dollar sign */
+#define CENDVAR 7 /* a '}' character */
+#define CLP 8 /* a left paren in arithmetic */
+#define CRP 9 /* a right paren in arithmetic */
+#define CEOF 10 /* end of file */
+#define CSPCL 11 /* these terminate a word */
+#define CCTL 12 /* like CWORD, except it must be escaped */
+#define CSBACK 13 /* a backslash in a single quote syntax */
+#define CFAKE 14 /* a delimiter that does not exist */
+ /*
+ * note CSBACK == (CCTL|1)
+ * the code does not rely upon that, but keeping it allows a
+ * smart enough compiler to optimise some tests
+ */
+
+/* Syntax classes for is_ functions */
+#define ISDIGIT 01 /* a digit */
+#define ISUPPER 02 /* an upper case letter */
+#define ISLOWER 04 /* a lower case letter */
+#define ISUNDER 010 /* an underscore */
+#define ISSPECL 020 /* the name of a special parameter */
+#define ISSPACE 040 /* a white space character */
+
+#define PEOF (CHAR_MIN - 1)
+#define PFAKE (CHAR_MIN - 2)
+#define SYNBASE (-PFAKE)
+
+
+#define BASESYNTAX (basesyntax + SYNBASE)
+#define DQSYNTAX (dqsyntax + SYNBASE)
+#define SQSYNTAX (sqsyntax + SYNBASE)
+#define ARISYNTAX (arisyntax + SYNBASE)
+
+/* These defines assume that the digits are contiguous (which is guaranteed) */
+#define is_digit(c) ((unsigned)((c) - '0') <= 9)
+#define sh_ctype(c) (is_type+SYNBASE)[(int)(c)]
+#define is_upper(c) (sh_ctype(c) & ISUPPER)
+#define is_lower(c) (sh_ctype(c) & ISLOWER)
+#define is_alpha(c) (sh_ctype(c) & (ISUPPER|ISLOWER))
+#define is_name(c) (sh_ctype(c) & (ISUPPER|ISLOWER|ISUNDER))
+#define is_in_name(c) (sh_ctype(c) & (ISUPPER|ISLOWER|ISUNDER|ISDIGIT))
+#define is_special(c) (sh_ctype(c) & (ISSPECL|ISDIGIT))
+#define is_space(c) (sh_ctype(c) & ISSPACE)
+#define digit_val(c) ((c) - '0')
+
+/* true if the arg char needs CTLESC to protect it */
+#define NEEDESC(c) (SQSYNTAX[(int)(c)] == CCTL || \
+ SQSYNTAX[(int)(c)] == CSBACK)
+
+extern const char basesyntax[];
+extern const char dqsyntax[];
+extern const char sqsyntax[];
+extern const char arisyntax[];
+extern const char is_type[];
diff --git a/bin/sh/trap.c b/bin/sh/trap.c
new file mode 100644
index 0000000..cb641fd
--- /dev/null
+++ b/bin/sh/trap.c
@@ -0,0 +1,837 @@
+/* $NetBSD: trap.c,v 1.51 2019/01/18 06:28:09 kre Exp $ */
+
+/*-
+ * Copyright (c) 1991, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Kenneth Almquist.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+#ifndef lint
+#if 0
+static char sccsid[] = "@(#)trap.c 8.5 (Berkeley) 6/5/95";
+#else
+__RCSID("$NetBSD: trap.c,v 1.51 2019/01/18 06:28:09 kre Exp $");
+#endif
+#endif /* not lint */
+
+#include <signal.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <errno.h>
+#include <termios.h>
+
+#undef CEOF /* from <termios.h> but concflicts with sh use */
+
+#include <sys/ioctl.h>
+#include <sys/resource.h>
+
+#include "shell.h"
+#include "main.h"
+#include "nodes.h" /* for other headers */
+#include "eval.h"
+#include "jobs.h"
+#include "show.h"
+#include "options.h"
+#include "builtins.h"
+#include "syntax.h"
+#include "output.h"
+#include "memalloc.h"
+#include "error.h"
+#include "trap.h"
+#include "mystring.h"
+#include "var.h"
+
+
+/*
+ * Sigmode records the current value of the signal handlers for the various
+ * modes. A value of zero means that the current handler is not known.
+ * S_HARD_IGN indicates that the signal was ignored on entry to the shell,
+ */
+
+#define S_DFL 1 /* default signal handling (SIG_DFL) */
+#define S_CATCH 2 /* signal is caught */
+#define S_IGN 3 /* signal is ignored (SIG_IGN) */
+#define S_HARD_IGN 4 /* signal is ignored permenantly */
+#define S_RESET 5 /* temporary - to reset a hard ignored sig */
+
+
+MKINIT char sigmode[NSIG]; /* current value of signal */
+static volatile sig_atomic_t gotsig[NSIG];/* indicates specified signal received */
+volatile sig_atomic_t pendingsigs; /* indicates some signal received */
+
+int traps_invalid; /* in a subshell, but trap[] not yet cleared */
+static char * volatile trap[NSIG]; /* trap handler commands */
+static int in_dotrap;
+static int last_trapsig;
+
+static int exiting; /* exitshell() has been done */
+static int exiting_status; /* the status to use for exit() */
+
+static int getsigaction(int, sig_t *);
+STATIC const char *trap_signame(int);
+void printsignals(struct output *, int);
+
+/*
+ * return the signal number described by `p' (as a number or a name)
+ * or -1 if it isn't one
+ */
+
+static int
+signame_to_signum(const char *p)
+{
+ int i;
+
+ if (is_number(p))
+ return number(p);
+
+ if (strcasecmp(p, "exit") == 0 )
+ return 0;
+
+ i = signalnumber(p);
+ if (i == 0)
+ i = -1;
+ return i;
+}
+
+/*
+ * return the name of a signal used by the "trap" command
+ */
+STATIC const char *
+trap_signame(int signo)
+{
+ static char nbuf[12];
+ const char *p;
+
+ if (signo == 0)
+ return "EXIT";
+ p = signalname(signo);
+ if (p != NULL)
+ return p;
+ (void)snprintf(nbuf, sizeof nbuf, "%d", signo);
+ return nbuf;
+}
+
+#ifdef SMALL
+/*
+ * Print a list of valid signal names
+ */
+void
+printsignals(struct output *out, int len)
+{
+ int n;
+
+ if (len != 0)
+ outc(' ', out);
+ for (n = 1; n < NSIG; n++) {
+ outfmt(out, "%s", trap_signame(n));
+ if ((n == NSIG/2) || n == (NSIG - 1))
+ outstr("\n", out);
+ else
+ outc(' ', out);
+ }
+}
+#else /* !SMALL */
+/*
+ * Print the names of all the signals (neatly) to fp
+ * "len" gives the number of chars already printed to
+ * the current output line (in kill.c, always 0)
+ */
+void
+printsignals(struct output *out, int len)
+{
+ int sig;
+ int nl, pad;
+ const char *name;
+ int termwidth = 80;
+
+ if ((name = bltinlookup("COLUMNS", 1)) != NULL)
+ termwidth = (int)strtol(name, NULL, 10);
+ else if (isatty(1)) {
+ struct winsize win;
+
+ if (ioctl(1, TIOCGWINSZ, &win) == 0 && win.ws_col > 0)
+ termwidth = win.ws_col;
+ }
+
+ if (posix)
+ pad = 1;
+ else
+ pad = (len | 7) + 1 - len;
+
+ for (sig = 0; (sig = signalnext(sig)) != 0; ) {
+ name = signalname(sig);
+ if (name == NULL)
+ continue;
+
+ nl = strlen(name);
+
+ if (len > 0 && nl + len + pad >= termwidth) {
+ outc('\n', out);
+ len = 0;
+ pad = 0;
+ } else if (pad > 0 && len != 0)
+ outfmt(out, "%*s", pad, "");
+ else
+ pad = 0;
+
+ len += nl + pad;
+ if (!posix)
+ pad = (nl | 7) + 1 - nl;
+ else
+ pad = 1;
+
+ outstr(name, out);
+ }
+ if (len != 0)
+ outc('\n', out);
+}
+#endif /* SMALL */
+
+/*
+ * The trap builtin.
+ */
+
+int
+trapcmd(int argc, char **argv)
+{
+ char *action;
+ char **ap;
+ int signo;
+ int errs = 0;
+ int printonly = 0;
+
+ ap = argv + 1;
+
+ CTRACE(DBG_TRAP, ("trapcmd: "));
+ if (argc == 2 && strcmp(*ap, "-l") == 0) {
+ CTRACE(DBG_TRAP, ("-l\n"));
+ out1str("EXIT");
+ printsignals(out1, 4);
+ return 0;
+ }
+ if (argc == 2 && strcmp(*ap, "-") == 0) {
+ CTRACE(DBG_TRAP, ("-\n"));
+ for (signo = 0; signo < NSIG; signo++) {
+ if (trap[signo] == NULL)
+ continue;
+ INTOFF;
+ ckfree(trap[signo]);
+ trap[signo] = NULL;
+ if (signo != 0)
+ setsignal(signo, 0);
+ INTON;
+ }
+ traps_invalid = 0;
+ return 0;
+ }
+ if (argc >= 2 && strcmp(*ap, "-p") == 0) {
+ CTRACE(DBG_TRAP, ("-p "));
+ printonly = 1;
+ ap++;
+ argc--;
+ }
+
+ if (argc > 1 && strcmp(*ap, "--") == 0) {
+ argc--;
+ ap++;
+ }
+
+ if (argc <= 1) {
+ int count;
+
+ CTRACE(DBG_TRAP, ("*all*\n"));
+ if (printonly) {
+ for (count = 0, signo = 0 ; signo < NSIG ; signo++)
+ if (trap[signo] == NULL) {
+ if (count == 0)
+ out1str("trap -- -");
+ out1fmt(" %s", trap_signame(signo));
+ /* oh! unlucky 13 */
+ if (++count >= 13) {
+ out1str("\n");
+ count = 0;
+ }
+ }
+ if (count)
+ out1str("\n");
+ }
+
+ for (count = 0, signo = 0 ; signo < NSIG ; signo++)
+ if (trap[signo] != NULL && trap[signo][0] == '\0') {
+ if (count == 0)
+ out1str("trap -- ''");
+ out1fmt(" %s", trap_signame(signo));
+ /*
+ * the prefix is 10 bytes, with 4 byte
+ * signal names (common) we have room in
+ * the 70 bytes left on a normal line for
+ * 70/(4+1) signals, that's 14, but to
+ * allow for the occasional longer sig name
+ * we output one less...
+ */
+ if (++count >= 13) {
+ out1str("\n");
+ count = 0;
+ }
+ }
+ if (count)
+ out1str("\n");
+
+ for (signo = 0 ; signo < NSIG ; signo++)
+ if (trap[signo] != NULL && trap[signo][0] != '\0') {
+ out1str("trap -- ");
+ print_quoted(trap[signo]);
+ out1fmt(" %s\n", trap_signame(signo));
+ }
+
+ return 0;
+ }
+ CTRACE(DBG_TRAP, ("\n"));
+
+ action = NULL;
+
+ if (!printonly && traps_invalid)
+ free_traps();
+
+ if (!printonly && !is_number(*ap)) {
+ if ((*ap)[0] == '-' && (*ap)[1] == '\0')
+ ap++; /* reset to default */
+ else
+ action = *ap++; /* can be '' for "ignore" */
+ argc--;
+ }
+
+ if (argc < 2) { /* there must be at least 1 condition */
+ out2str("Usage: trap [-l]\n"
+ " trap -p [condition ...]\n"
+ " trap action condition ...\n"
+ " trap N condition ...\n");
+ return 2;
+ }
+
+
+ while (*ap) {
+ signo = signame_to_signum(*ap);
+
+ if (signo < 0 || signo >= NSIG) {
+ /* This is not a fatal error, so sayeth posix */
+ outfmt(out2, "trap: '%s' bad condition\n", *ap);
+ errs = 1;
+ ap++;
+ continue;
+ }
+ ap++;
+
+ if (printonly) {
+ out1str("trap -- ");
+ if (trap[signo] == NULL)
+ out1str("-");
+ else
+ print_quoted(trap[signo]);
+ out1fmt(" %s\n", trap_signame(signo));
+ continue;
+ }
+
+ INTOFF;
+ if (action)
+ action = savestr(action);
+
+ VTRACE(DBG_TRAP, ("trap for %d from %s%s%s to %s%s%s\n", signo,
+ trap[signo] ? "'" : "", trap[signo] ? trap[signo] : "-",
+ trap[signo] ? "'" : "", action ? "'" : "",
+ action ? action : "-", action ? "'" : ""));
+
+ if (trap[signo])
+ ckfree(trap[signo]);
+
+ trap[signo] = action;
+
+ if (signo != 0)
+ setsignal(signo, 0);
+ INTON;
+ }
+ return errs;
+}
+
+
+
+/*
+ * Clear traps on a fork or vfork.
+ * Takes one arg vfork, to tell it to not be destructive of
+ * the parents variables.
+ */
+void
+clear_traps(int vforked)
+{
+ char * volatile *tp;
+
+ VTRACE(DBG_TRAP, ("clear_traps(%d)\n", vforked));
+ if (!vforked)
+ traps_invalid = 1;
+
+ for (tp = &trap[1] ; tp < &trap[NSIG] ; tp++) {
+ if (*tp && **tp) { /* trap not NULL or SIG_IGN */
+ INTOFF;
+ setsignal(tp - trap, vforked == 1);
+ INTON;
+ }
+ }
+ if (vforked == 2)
+ free_traps();
+}
+
+void
+free_traps(void)
+{
+ char * volatile *tp;
+
+ VTRACE(DBG_TRAP, ("free_traps%s\n", traps_invalid ? "(invalid)" : ""));
+ INTOFF;
+ for (tp = trap ; tp < &trap[NSIG] ; tp++)
+ if (*tp && **tp) {
+ ckfree(*tp);
+ *tp = NULL;
+ }
+ traps_invalid = 0;
+ INTON;
+}
+
+/*
+ * See if there are any defined traps
+ */
+int
+have_traps(void)
+{
+ char * volatile *tp;
+
+ if (traps_invalid)
+ return 0;
+
+ for (tp = trap ; tp < &trap[NSIG] ; tp++)
+ if (*tp && **tp) /* trap not NULL or SIG_IGN */
+ return 1;
+ return 0;
+}
+
+/*
+ * Set the signal handler for the specified signal. The routine figures
+ * out what it should be set to.
+ */
+void
+setsignal(int signo, int vforked)
+{
+ int action;
+ sig_t sigact = SIG_DFL, sig;
+ char *t, tsig;
+
+ if (traps_invalid || (t = trap[signo]) == NULL)
+ action = S_DFL;
+ else if (*t != '\0')
+ action = S_CATCH;
+ else
+ action = S_IGN;
+
+ VTRACE(DBG_TRAP, ("setsignal(%d%s) -> %d", signo,
+ vforked ? ", VF" : "", action));
+ if (rootshell && !vforked && action == S_DFL) {
+ switch (signo) {
+ case SIGINT:
+ if (iflag || minusc || sflag == 0)
+ action = S_CATCH;
+ break;
+ case SIGQUIT:
+#ifdef DEBUG
+ if (debug)
+ break;
+#endif
+ /* FALLTHROUGH */
+ case SIGTERM:
+ if (rootshell && iflag)
+ action = S_IGN;
+ break;
+#if JOBS
+ case SIGTSTP:
+ case SIGTTOU:
+ if (rootshell && mflag)
+ action = S_IGN;
+ break;
+#endif
+ }
+ }
+
+ /*
+ * Never let users futz with SIGCHLD
+ * instead we will give them pseudo SIGCHLD's
+ * when background jobs complete.
+ */
+ if (signo == SIGCHLD)
+ action = S_DFL;
+
+ VTRACE(DBG_TRAP, (" -> %d", action));
+
+ t = &sigmode[signo];
+ tsig = *t;
+ if (tsig == 0) {
+ /*
+ * current setting unknown
+ */
+ if (!getsigaction(signo, &sigact)) {
+ /*
+ * Pretend it worked; maybe we should give a warning
+ * here, but other shells don't. We don't alter
+ * sigmode, so that we retry every time.
+ */
+ VTRACE(DBG_TRAP, (" getsigaction (%d)\n", errno));
+ return;
+ }
+ VTRACE(DBG_TRAP, (" [%s]%s%s", sigact==SIG_IGN ? "IGN" :
+ sigact==SIG_DFL ? "DFL" : "caught",
+ iflag ? "i" : "", mflag ? "m" : ""));
+
+ if (sigact == SIG_IGN) {
+ /*
+ * POSIX 3.14.13 states that non-interactive shells
+ * should ignore trap commands for signals that were
+ * ignored upon entry, and leaves the behavior
+ * unspecified for interactive shells. On interactive
+ * shells, or if job control is on, and we have a job
+ * control related signal, we allow the trap to work.
+ *
+ * This change allows us to be POSIX compliant, and
+ * at the same time override the default behavior if
+ * we need to by setting the interactive flag.
+ */
+ if ((mflag && (signo == SIGTSTP ||
+ signo == SIGTTIN || signo == SIGTTOU)) || iflag) {
+ tsig = S_IGN;
+ } else
+ tsig = S_HARD_IGN;
+ } else {
+ tsig = S_RESET; /* force to be set */
+ }
+ }
+ VTRACE(DBG_TRAP, (" tsig=%d\n", tsig));
+
+ if (tsig == S_HARD_IGN || tsig == action)
+ return;
+
+ switch (action) {
+ case S_DFL: sigact = SIG_DFL; break;
+ case S_CATCH: sigact = onsig; break;
+ case S_IGN: sigact = SIG_IGN; break;
+ }
+
+ sig = signal(signo, sigact);
+
+ if (sig != SIG_ERR) {
+ sigset_t ss;
+
+ if (!vforked)
+ *t = action;
+
+ if (action == S_CATCH)
+ (void)siginterrupt(signo, 1);
+ /*
+ * If our parent accidentally blocked signals for
+ * us make sure we unblock them
+ */
+ (void)sigemptyset(&ss);
+ (void)sigaddset(&ss, signo);
+ (void)sigprocmask(SIG_UNBLOCK, &ss, NULL);
+ }
+ return;
+}
+
+/*
+ * Return the current setting for sig w/o changing it.
+ */
+static int
+getsigaction(int signo, sig_t *sigact)
+{
+ struct sigaction sa;
+
+ if (sigaction(signo, (struct sigaction *)0, &sa) == -1)
+ return 0;
+ *sigact = (sig_t) sa.sa_handler;
+ return 1;
+}
+
+/*
+ * Ignore a signal.
+ */
+
+void
+ignoresig(int signo, int vforked)
+{
+ if (sigmode[signo] == 0)
+ setsignal(signo, vforked);
+
+ VTRACE(DBG_TRAP, ("ignoresig(%d%s)\n", signo, vforked ? ", VF" : ""));
+ if (sigmode[signo] != S_IGN && sigmode[signo] != S_HARD_IGN) {
+ signal(signo, SIG_IGN);
+ if (!vforked)
+ sigmode[signo] = S_IGN;
+ }
+}
+
+char *
+child_trap(void)
+{
+ char * p;
+
+ p = trap[SIGCHLD];
+
+ if (traps_invalid || (p != NULL && *p == '\0'))
+ p = NULL;
+
+ return p;
+}
+
+
+#ifdef mkinit
+INCLUDE <signal.h>
+INCLUDE "trap.h"
+INCLUDE "shell.h"
+INCLUDE "show.h"
+
+SHELLPROC {
+ char *sm;
+
+ INTOFF;
+ clear_traps(2);
+ for (sm = sigmode ; sm < sigmode + NSIG ; sm++) {
+ if (*sm == S_IGN) {
+ *sm = S_HARD_IGN;
+ VTRACE(DBG_TRAP, ("SHELLPROC: %d -> hard_ign\n",
+ (sm - sigmode)));
+ }
+ }
+ INTON;
+}
+#endif
+
+
+
+/*
+ * Signal handler.
+ */
+
+void
+onsig(int signo)
+{
+ CTRACE(DBG_SIG, ("Signal %d, had: pending %d, gotsig[%d]=%d\n",
+ signo, pendingsigs, signo, gotsig[signo]));
+
+ /* This should not be needed.
+ signal(signo, onsig);
+ */
+
+ if (signo == SIGINT && (traps_invalid || trap[SIGINT] == NULL)) {
+ onint();
+ return;
+ }
+
+ /*
+ * if the signal will do nothing, no point reporting it
+ */
+ if (!traps_invalid && trap[signo] != NULL && trap[signo][0] != '\0' &&
+ signo != SIGCHLD) {
+ gotsig[signo] = 1;
+ pendingsigs++;
+ }
+}
+
+
+
+/*
+ * Called to execute a trap. Perhaps we should avoid entering new trap
+ * handlers while we are executing a trap handler.
+ */
+
+void
+dotrap(void)
+{
+ int i;
+ char *tr;
+ int savestatus;
+ struct skipsave saveskip;
+
+ in_dotrap++;
+
+ CTRACE(DBG_TRAP, ("dotrap[%d]: %d pending, traps %sinvalid\n",
+ in_dotrap, pendingsigs, traps_invalid ? "" : "not "));
+ for (;;) {
+ pendingsigs = 0;
+ for (i = 1 ; ; i++) {
+ if (i >= NSIG)
+ return;
+ if (gotsig[i])
+ break;
+ }
+ gotsig[i] = 0;
+
+ if (traps_invalid)
+ continue;
+
+ tr = trap[i];
+
+ CTRACE(DBG_TRAP|DBG_SIG, ("dotrap %d: %s%s%s\n", i,
+ tr ? "\"" : "", tr ? tr : "NULL", tr ? "\"" : ""));
+
+ if (tr != NULL) {
+ last_trapsig = i;
+ save_skipstate(&saveskip);
+ savestatus = exitstatus;
+
+ tr = savestr(tr); /* trap code may free trap[i] */
+ evalstring(tr, 0);
+ ckfree(tr);
+
+ if (current_skipstate() == SKIPNONE ||
+ saveskip.state != SKIPNONE) {
+ restore_skipstate(&saveskip);
+ exitstatus = savestatus;
+ }
+ }
+ }
+
+ in_dotrap--;
+}
+
+int
+lastsig(void)
+{
+ int i;
+
+ for (i = NSIG; --i > 0; )
+ if (gotsig[i])
+ return i;
+ return SIGINT; /* XXX */
+}
+
+/*
+ * Controls whether the shell is interactive or not.
+ */
+
+
+void
+setinteractive(int on)
+{
+ static int is_interactive;
+
+ if (on == is_interactive)
+ return;
+ setsignal(SIGINT, 0);
+ setsignal(SIGQUIT, 0);
+ setsignal(SIGTERM, 0);
+ is_interactive = on;
+}
+
+
+
+/*
+ * Called to exit the shell.
+ */
+void
+exitshell(int status)
+{
+ CTRACE(DBG_ERRS|DBG_PROCS|DBG_CMDS|DBG_TRAP,
+ ("pid %d: exitshell(%d)\n", getpid(), status));
+
+ exiting = 1;
+ exiting_status = status;
+ exitshell_savedstatus();
+}
+
+void
+exitshell_savedstatus(void)
+{
+ struct jmploc loc;
+ char *p;
+ volatile int sig = 0;
+ int s;
+ sigset_t sigs;
+
+ CTRACE(DBG_ERRS|DBG_PROCS|DBG_CMDS|DBG_TRAP,
+ ("pid %d: exitshell_savedstatus()%s $?=%d xs=%d dt=%d ts=%d\n",
+ getpid(), exiting ? " exiting" : "", exitstatus,
+ exiting_status, in_dotrap, last_trapsig));
+
+ if (!exiting) {
+ if (in_dotrap && last_trapsig) {
+ sig = last_trapsig;
+ exiting_status = sig + 128;
+ } else
+ exiting_status = exitstatus;
+ }
+ exitstatus = exiting_status;
+
+ if (!setjmp(loc.loc)) {
+ handler = &loc;
+
+ if (!traps_invalid && (p = trap[0]) != NULL && *p != '\0') {
+ reset_eval();
+ trap[0] = NULL;
+ VTRACE(DBG_TRAP, ("exit trap: \"%s\"\n", p));
+ evalstring(p, 0);
+ }
+ }
+
+ INTOFF; /* we're done, no more interrupts. */
+
+ if (!setjmp(loc.loc)) {
+ handler = &loc; /* probably unnecessary */
+ flushall();
+#if JOBS
+ setjobctl(0);
+#endif
+ }
+
+ if ((s = sig) != 0 && s != SIGSTOP && s != SIGTSTP && s != SIGTTIN &&
+ s != SIGTTOU) {
+ struct rlimit nocore;
+
+ /*
+ * if the signal is of the core dump variety, don't...
+ */
+ nocore.rlim_cur = nocore.rlim_max = 0;
+ (void) setrlimit(RLIMIT_CORE, &nocore);
+
+ signal(s, SIG_DFL);
+ sigemptyset(&sigs);
+ sigaddset(&sigs, s);
+ sigprocmask(SIG_UNBLOCK, &sigs, NULL);
+
+ kill(getpid(), s);
+ }
+ _exit(exiting_status);
+ /* NOTREACHED */
+}
diff --git a/bin/sh/trap.h b/bin/sh/trap.h
new file mode 100644
index 0000000..7ea3ef1
--- /dev/null
+++ b/bin/sh/trap.h
@@ -0,0 +1,52 @@
+/* $NetBSD: trap.h,v 1.25 2018/12/03 10:53:29 martin Exp $ */
+
+/*-
+ * Copyright (c) 1991, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Kenneth Almquist.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * @(#)trap.h 8.3 (Berkeley) 6/5/95
+ */
+
+extern volatile sig_atomic_t pendingsigs;
+
+extern int traps_invalid;
+
+void clear_traps(int);
+void free_traps(void);
+int have_traps(void);
+void setsignal(int, int);
+void ignoresig(int, int);
+void onsig(int);
+void dotrap(void);
+char *child_trap(void);
+void setinteractive(int);
+void exitshell(int) __dead;
+void exitshell_savedstatus(void) __dead;
+int lastsig(void);
diff --git a/bin/sh/var.c b/bin/sh/var.c
new file mode 100644
index 0000000..378598c
--- /dev/null
+++ b/bin/sh/var.c
@@ -0,0 +1,1587 @@
+/* $NetBSD: var.c,v 1.75 2019/01/21 13:27:29 kre Exp $ */
+
+/*-
+ * Copyright (c) 1991, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Kenneth Almquist.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+#ifndef lint
+#if 0
+static char sccsid[] = "@(#)var.c 8.3 (Berkeley) 5/4/95";
+#else
+__RCSID("$NetBSD: var.c,v 1.75 2019/01/21 13:27:29 kre Exp $");
+#endif
+#endif /* not lint */
+
+#include <stdio.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <string.h>
+#include <paths.h>
+#include <limits.h>
+#include <time.h>
+#include <pwd.h>
+#include <fcntl.h>
+#include <inttypes.h>
+
+/*
+ * Shell variables.
+ */
+
+#include "shell.h"
+#include "output.h"
+#include "expand.h"
+#include "nodes.h" /* for other headers */
+#include "eval.h" /* defines cmdenviron */
+#include "exec.h"
+#include "syntax.h"
+#include "options.h"
+#include "builtins.h"
+#include "mail.h"
+#include "var.h"
+#include "memalloc.h"
+#include "error.h"
+#include "mystring.h"
+#include "parser.h"
+#include "show.h"
+#include "machdep.h"
+#ifndef SMALL
+#include "myhistedit.h"
+#endif
+
+#ifdef SMALL
+#define VTABSIZE 39
+#else
+#define VTABSIZE 517
+#endif
+
+
+struct varinit {
+ struct var *var;
+ int flags;
+ const char *text;
+ union var_func_union v_u;
+};
+#define func v_u.set_func
+#define rfunc v_u.ref_func
+
+char *get_lineno(struct var *);
+
+#ifndef SMALL
+char *get_tod(struct var *);
+char *get_hostname(struct var *);
+char *get_seconds(struct var *);
+char *get_euser(struct var *);
+char *get_random(struct var *);
+#endif
+
+struct localvar *localvars;
+
+#ifndef SMALL
+struct var vhistsize;
+struct var vterm;
+struct var editrc;
+struct var ps_lit;
+#endif
+struct var vifs;
+struct var vmail;
+struct var vmpath;
+struct var vpath;
+struct var vps1;
+struct var vps2;
+struct var vps4;
+struct var vvers;
+struct var voptind;
+struct var line_num;
+#ifndef SMALL
+struct var tod;
+struct var host_name;
+struct var seconds;
+struct var euname;
+struct var random_num;
+
+intmax_t sh_start_time;
+#endif
+
+struct var line_num;
+int line_number;
+int funclinebase = 0;
+int funclineabs = 0;
+
+char ifs_default[] = " \t\n";
+
+const struct varinit varinit[] = {
+#ifndef SMALL
+ { &vhistsize, VSTRFIXED|VTEXTFIXED|VUNSET, "HISTSIZE=",
+ { .set_func= sethistsize } },
+#endif
+ { &vifs, VSTRFIXED|VTEXTFIXED, "IFS= \t\n",
+ { NULL } },
+ { &vmail, VSTRFIXED|VTEXTFIXED|VUNSET, "MAIL=",
+ { NULL } },
+ { &vmpath, VSTRFIXED|VTEXTFIXED|VUNSET, "MAILPATH=",
+ { NULL } },
+ { &vvers, VSTRFIXED|VTEXTFIXED|VNOEXPORT, "NETBSD_SHELL=",
+ { NULL } },
+ { &vpath, VSTRFIXED|VTEXTFIXED, "PATH=" _PATH_DEFPATH,
+ { .set_func= changepath } },
+ /*
+ * vps1 depends on uid
+ */
+ { &vps2, VSTRFIXED|VTEXTFIXED, "PS2=> ",
+ { NULL } },
+ { &vps4, VSTRFIXED|VTEXTFIXED, "PS4=+ ",
+ { NULL } },
+#ifndef SMALL
+ { &vterm, VSTRFIXED|VTEXTFIXED|VUNSET, "TERM=",
+ { .set_func= setterm } },
+ { &editrc, VSTRFIXED|VTEXTFIXED|VUNSET, "EDITRC=",
+ { .set_func= set_editrc } },
+ { &ps_lit, VSTRFIXED|VTEXTFIXED|VUNSET, "PSlit=",
+ { .set_func= set_prompt_lit } },
+#endif
+ { &voptind, VSTRFIXED|VTEXTFIXED|VNOFUNC, "OPTIND=1",
+ { .set_func= getoptsreset } },
+ { &line_num, VSTRFIXED|VTEXTFIXED|VFUNCREF|VSPECIAL, "LINENO=1",
+ { .ref_func= get_lineno } },
+#ifndef SMALL
+ { &tod, VSTRFIXED|VTEXTFIXED|VFUNCREF, "ToD=",
+ { .ref_func= get_tod } },
+ { &host_name, VSTRFIXED|VTEXTFIXED|VFUNCREF, "HOSTNAME=",
+ { .ref_func= get_hostname } },
+ { &seconds, VSTRFIXED|VTEXTFIXED|VFUNCREF, "SECONDS=",
+ { .ref_func= get_seconds } },
+ { &euname, VSTRFIXED|VTEXTFIXED|VFUNCREF, "EUSER=",
+ { .ref_func= get_euser } },
+ { &random_num, VSTRFIXED|VTEXTFIXED|VFUNCREF|VSPECIAL, "RANDOM=",
+ { .ref_func= get_random } },
+#endif
+ { NULL, 0, NULL,
+ { NULL } }
+};
+
+struct var *vartab[VTABSIZE];
+
+STATIC int strequal(const char *, const char *);
+STATIC struct var *find_var(const char *, struct var ***, int *);
+STATIC void showvar(struct var *, const char *, const char *, int);
+static void export_usage(const char *) __dead;
+
+/*
+ * Initialize the varable symbol tables and import the environment
+ */
+
+#ifdef mkinit
+INCLUDE <stdio.h>
+INCLUDE <unistd.h>
+INCLUDE <time.h>
+INCLUDE "var.h"
+INCLUDE "version.h"
+MKINIT char **environ;
+INIT {
+ char **envp;
+ char buf[64];
+
+#ifndef SMALL
+ sh_start_time = (intmax_t)time((time_t *)0);
+#endif
+ /*
+ * Set up our default variables and their values.
+ */
+ initvar();
+
+ /*
+ * Import variables from the environment, which will
+ * override anything initialised just previously.
+ */
+ for (envp = environ ; *envp ; envp++) {
+ if (strchr(*envp, '=')) {
+ setvareq(*envp, VEXPORT|VTEXTFIXED);
+ }
+ }
+
+ /*
+ * Set variables which override anything read from environment.
+ *
+ * PPID is readonly
+ * Always default IFS
+ * POSIX: "Whenever the shell is invoked, OPTIND shall
+ * be initialized to 1."
+ * PSc indicates the root/non-root status of this shell.
+ * START_TIME belongs only to this shell.
+ * NETBSD_SHELL is a constant (readonly), and is never exported
+ * LINENO is simply magic...
+ */
+ snprintf(buf, sizeof(buf), "%d", (int)getppid());
+ setvar("PPID", buf, VREADONLY);
+ setvar("IFS", ifs_default, VTEXTFIXED);
+ setvar("OPTIND", "1", VTEXTFIXED);
+ setvar("PSc", (geteuid() == 0 ? "#" : "$"), VTEXTFIXED);
+
+#ifndef SMALL
+ snprintf(buf, sizeof(buf), "%jd", sh_start_time);
+ setvar("START_TIME", buf, VTEXTFIXED);
+#endif
+
+ setvar("NETBSD_SHELL", NETBSD_SHELL
+#ifdef BUILD_DATE
+ " BUILD:" BUILD_DATE
+#endif
+#ifdef DEBUG
+ " DEBUG"
+#endif
+#if !defined(JOBS) || JOBS == 0
+ " -JOBS"
+#endif
+#ifndef DO_SHAREDVFORK
+ " -VFORK"
+#endif
+#ifdef SMALL
+ " SMALL"
+#endif
+#ifdef TINY
+ " TINY"
+#endif
+#ifdef OLD_TTY_DRIVER
+ " OLD_TTY"
+#endif
+#ifdef SYSV
+ " SYSV"
+#endif
+#ifndef BSD
+ " -BSD"
+#endif
+#ifdef BOGUS_NOT_COMMAND
+ " BOGUS_NOT"
+#endif
+ , VTEXTFIXED|VREADONLY|VNOEXPORT);
+
+ setvar("LINENO", "1", VTEXTFIXED);
+}
+#endif
+
+
+/*
+ * This routine initializes the builtin variables. It is called when the
+ * shell is initialized and again when a shell procedure is spawned.
+ */
+
+void
+initvar(void)
+{
+ const struct varinit *ip;
+ struct var *vp;
+ struct var **vpp;
+
+ for (ip = varinit ; (vp = ip->var) != NULL ; ip++) {
+ if (find_var(ip->text, &vpp, &vp->name_len) != NULL)
+ continue;
+ vp->next = *vpp;
+ *vpp = vp;
+ vp->text = strdup(ip->text);
+ vp->flags = (ip->flags & ~VTEXTFIXED) | VSTRFIXED;
+ vp->v_u = ip->v_u;
+ }
+ /*
+ * PS1 depends on uid
+ */
+ if (find_var("PS1", &vpp, &vps1.name_len) == NULL) {
+ vps1.next = *vpp;
+ *vpp = &vps1;
+ vps1.flags = VSTRFIXED;
+ vps1.text = NULL;
+ choose_ps1();
+ }
+}
+
+void
+choose_ps1(void)
+{
+ uid_t u = geteuid();
+
+ if ((vps1.flags & (VTEXTFIXED|VSTACK)) == 0)
+ free(vps1.text);
+ vps1.text = strdup(u != 0 ? "PS1=$ " : "PS1=# ");
+ vps1.flags &= ~(VTEXTFIXED|VSTACK);
+
+ /*
+ * Update PSc whenever we feel the need to update PS1
+ */
+ setvarsafe("PSc", (u == 0 ? "#" : "$"), 0);
+}
+
+/*
+ * Validate a string as a valid variable name
+ * nb: not parameter - special params and such are "invalid" here.
+ * Name terminated by either \0 or the term param (usually '=' or '\0').
+ *
+ * If not NULL, the length of the (intended) name is returned via len
+ */
+
+int
+validname(const char *name, int term, int *len)
+{
+ const char *p = name;
+ int ok = 1;
+
+ if (p == NULL || *p == '\0' || *p == term) {
+ if (len != NULL)
+ *len = 0;
+ return 0;
+ }
+
+ if (!is_name(*p))
+ ok = 0;
+ p++;
+ for (;;) {
+ if (*p == '\0' || *p == term)
+ break;
+ if (!is_in_name(*p))
+ ok = 0;
+ p++;
+ }
+ if (len != NULL)
+ *len = p - name;
+
+ return ok;
+}
+
+/*
+ * Safe version of setvar, returns 1 on success 0 on failure.
+ */
+
+int
+setvarsafe(const char *name, const char *val, int flags)
+{
+ struct jmploc jmploc;
+ struct jmploc * const savehandler = handler;
+ int volatile err = 0;
+
+ if (setjmp(jmploc.loc))
+ err = 1;
+ else {
+ handler = &jmploc;
+ setvar(name, val, flags);
+ }
+ handler = savehandler;
+ return err;
+}
+
+/*
+ * Set the value of a variable. The flags argument is ored with the
+ * flags of the variable. If val is NULL, the variable is unset.
+ *
+ * This always copies name and val when setting a variable, so
+ * the source strings can be from anywhere, and are no longer needed
+ * after this function returns. The VTEXTFIXED and VSTACK flags should
+ * not be used (but just in case they were, clear them.)
+ */
+
+void
+setvar(const char *name, const char *val, int flags)
+{
+ const char *p;
+ const char *q;
+ char *d;
+ int len;
+ int namelen;
+ char *nameeq;
+
+ p = name;
+
+ if (!validname(p, '=', &namelen))
+ error("%.*s: bad variable name", namelen, name);
+ len = namelen + 2; /* 2 is space for '=' and '\0' */
+ if (val == NULL) {
+ flags |= VUNSET;
+ } else {
+ len += strlen(val);
+ }
+ d = nameeq = ckmalloc(len);
+ q = name;
+ while (--namelen >= 0)
+ *d++ = *q++;
+ *d++ = '=';
+ *d = '\0';
+ if (val)
+ scopy(val, d);
+ setvareq(nameeq, flags & ~(VTEXTFIXED | VSTACK));
+}
+
+
+
+/*
+ * Same as setvar except that the variable and value are passed in
+ * the first argument as name=value. Since the first argument will
+ * be actually stored in the table, it should not be a string that
+ * will go away. The flags (VTEXTFIXED or VSTACK) can be used to
+ * indicate the source of the string (if neither is set, the string will
+ * eventually be free()d when a replacement value is assigned.)
+ */
+
+void
+setvareq(char *s, int flags)
+{
+ struct var *vp, **vpp;
+ int nlen;
+
+ VTRACE(DBG_VARS, ("setvareq([%s],%#x) aflag=%d ", s, flags, aflag));
+ if (aflag && !(flags & VNOEXPORT))
+ flags |= VEXPORT;
+ vp = find_var(s, &vpp, &nlen);
+ if (vp != NULL) {
+ VTRACE(DBG_VARS, ("was [%s] fl:%#x\n", vp->text,
+ vp->flags));
+ if (vp->flags & VREADONLY) {
+ if ((flags & (VTEXTFIXED|VSTACK)) == 0)
+ ckfree(s);
+ if (flags & VNOERROR)
+ return;
+ error("%.*s: is read only", vp->name_len, vp->text);
+ }
+ if (flags & VNOSET) {
+ if ((flags & (VTEXTFIXED|VSTACK)) == 0)
+ ckfree(s);
+ return;
+ }
+
+ INTOFF;
+
+ if (vp->func && !(vp->flags & VFUNCREF) && !(flags & VNOFUNC))
+ (*vp->func)(s + vp->name_len + 1);
+
+ if ((vp->flags & (VTEXTFIXED|VSTACK)) == 0)
+ ckfree(vp->text);
+
+ /*
+ * if we set a magic var, the magic dissipates,
+ * unless it is very special indeed.
+ */
+ if (vp->rfunc && (vp->flags & (VFUNCREF|VSPECIAL)) == VFUNCREF)
+ vp->rfunc = NULL;
+
+ vp->flags &= ~(VTEXTFIXED|VSTACK|VUNSET);
+ if (flags & VNOEXPORT)
+ vp->flags &= ~VEXPORT;
+ if (flags & VDOEXPORT)
+ vp->flags &= ~VNOEXPORT;
+ if (vp->flags & VNOEXPORT)
+ flags &= ~VEXPORT;
+ vp->flags |= flags & ~(VNOFUNC | VDOEXPORT);
+ vp->text = s;
+
+ /*
+ * We could roll this to a function, to handle it as
+ * a regular variable function callback, but why bother?
+ */
+ if (vp == &vmpath || (vp == &vmail && ! mpathset()))
+ chkmail(1);
+
+ INTON;
+ return;
+ }
+ /* not found */
+ if (flags & VNOSET) {
+ VTRACE(DBG_VARS, ("new noset\n"));
+ if ((flags & (VTEXTFIXED|VSTACK)) == 0)
+ ckfree(s);
+ return;
+ }
+ vp = ckmalloc(sizeof (*vp));
+ vp->flags = flags & ~(VNOFUNC|VFUNCREF|VDOEXPORT);
+ vp->text = s;
+ vp->name_len = nlen;
+ vp->func = NULL;
+ vp->next = *vpp;
+ *vpp = vp;
+
+ VTRACE(DBG_VARS, ("new [%s] (%d) %#x\n", s, nlen, vp->flags));
+}
+
+
+
+/*
+ * Process a linked list of variable assignments.
+ */
+
+void
+listsetvar(struct strlist *list, int flags)
+{
+ struct strlist *lp;
+
+ INTOFF;
+ for (lp = list ; lp ; lp = lp->next) {
+ setvareq(savestr(lp->text), flags);
+ }
+ INTON;
+}
+
+void
+listmklocal(struct strlist *list, int flags)
+{
+ struct strlist *lp;
+
+ for (lp = list ; lp ; lp = lp->next)
+ mklocal(lp->text, flags);
+}
+
+
+/*
+ * Find the value of a variable. Returns NULL if not set.
+ */
+
+char *
+lookupvar(const char *name)
+{
+ struct var *v;
+
+ v = find_var(name, NULL, NULL);
+ if (v == NULL || v->flags & VUNSET)
+ return NULL;
+ if (v->rfunc && (v->flags & VFUNCREF) != 0)
+ return (*v->rfunc)(v) + v->name_len + 1;
+ return v->text + v->name_len + 1;
+}
+
+
+
+/*
+ * Search the environment of a builtin command. If the second argument
+ * is nonzero, return the value of a variable even if it hasn't been
+ * exported.
+ */
+
+char *
+bltinlookup(const char *name, int doall)
+{
+ struct strlist *sp;
+ struct var *v;
+
+ for (sp = cmdenviron ; sp ; sp = sp->next) {
+ if (strequal(sp->text, name))
+ return strchr(sp->text, '=') + 1;
+ }
+
+ v = find_var(name, NULL, NULL);
+
+ if (v == NULL || v->flags & VUNSET || (!doall && !(v->flags & VEXPORT)))
+ return NULL;
+ if (v->rfunc && (v->flags & VFUNCREF) != 0)
+ return (*v->rfunc)(v) + v->name_len + 1;
+ return v->text + v->name_len + 1;
+}
+
+
+
+/*
+ * Generate a list of exported variables. This routine is used to construct
+ * the third argument to execve when executing a program.
+ */
+
+char **
+environment(void)
+{
+ int nenv;
+ struct var **vpp;
+ struct var *vp;
+ char **env;
+ char **ep;
+
+ nenv = 0;
+ for (vpp = vartab ; vpp < vartab + VTABSIZE ; vpp++) {
+ for (vp = *vpp ; vp ; vp = vp->next)
+ if ((vp->flags & (VEXPORT|VUNSET)) == VEXPORT)
+ nenv++;
+ }
+ CTRACE(DBG_VARS, ("environment: %d vars to export\n", nenv));
+ ep = env = stalloc((nenv + 1) * sizeof *env);
+ for (vpp = vartab ; vpp < vartab + VTABSIZE ; vpp++) {
+ for (vp = *vpp ; vp ; vp = vp->next)
+ if ((vp->flags & (VEXPORT|VUNSET)) == VEXPORT) {
+ if (vp->rfunc && (vp->flags & VFUNCREF))
+ *ep++ = (*vp->rfunc)(vp);
+ else
+ *ep++ = vp->text;
+ VTRACE(DBG_VARS, ("environment: %s\n", ep[-1]));
+ }
+ }
+ *ep = NULL;
+ return env;
+}
+
+
+/*
+ * Called when a shell procedure is invoked to clear out nonexported
+ * variables. It is also necessary to reallocate variables of with
+ * VSTACK set since these are currently allocated on the stack.
+ */
+
+#ifdef mkinit
+void shprocvar(void);
+
+SHELLPROC {
+ shprocvar();
+}
+#endif
+
+void
+shprocvar(void)
+{
+ struct var **vpp;
+ struct var *vp, **prev;
+
+ for (vpp = vartab ; vpp < vartab + VTABSIZE ; vpp++) {
+ for (prev = vpp ; (vp = *prev) != NULL ; ) {
+ if ((vp->flags & VEXPORT) == 0) {
+ *prev = vp->next;
+ if ((vp->flags & VTEXTFIXED) == 0)
+ ckfree(vp->text);
+ if ((vp->flags & VSTRFIXED) == 0)
+ ckfree(vp);
+ } else {
+ if (vp->flags & VSTACK) {
+ vp->text = savestr(vp->text);
+ vp->flags &=~ VSTACK;
+ }
+ prev = &vp->next;
+ }
+ }
+ }
+ initvar();
+}
+
+
+
+/*
+ * Command to list all variables which are set. Currently this command
+ * is invoked from the set command when the set command is called without
+ * any variables.
+ */
+
+void
+print_quoted(const char *p)
+{
+ const char *q;
+
+ if (p[0] == '\0') {
+ out1fmt("''");
+ return;
+ }
+ if (strcspn(p, "|&;<>()$`\\\"' \t\n*?[]#~=%") == strlen(p)) {
+ out1fmt("%s", p);
+ return;
+ }
+ while (*p) {
+ if (*p == '\'') {
+ out1fmt("\\'");
+ p++;
+ continue;
+ }
+ q = strchr(p, '\'');
+ if (!q) {
+ out1fmt("'%s'", p );
+ return;
+ }
+ out1fmt("'%.*s'", (int)(q - p), p );
+ p = q;
+ }
+}
+
+static int
+sort_var(const void *v_v1, const void *v_v2)
+{
+ const struct var * const *v1 = v_v1;
+ const struct var * const *v2 = v_v2;
+ char *t1 = (*v1)->text, *t2 = (*v2)->text;
+
+ if (*t1 == *t2) {
+ char *p, *s;
+
+ STARTSTACKSTR(p);
+
+ /*
+ * note: if lengths are equal, strings must be different
+ * so we don't care which string we pick for the \0 in
+ * that case.
+ */
+ if ((strchr(t1, '=') - t1) <= (strchr(t2, '=') - t2)) {
+ s = t1;
+ t1 = p;
+ } else {
+ s = t2;
+ t2 = p;
+ }
+
+ while (*s && *s != '=') {
+ STPUTC(*s, p);
+ s++;
+ }
+ STPUTC('\0', p);
+ }
+
+ return strcoll(t1, t2);
+}
+
+/*
+ * POSIX requires that 'set' (but not export or readonly) output the
+ * variables in lexicographic order - by the locale's collating order (sigh).
+ * Maybe we could keep them in an ordered balanced binary tree
+ * instead of hashed lists.
+ * For now just roll 'em through qsort for printing...
+ */
+
+STATIC void
+showvar(struct var *vp, const char *cmd, const char *xtra, int show_value)
+{
+ const char *p;
+
+ if (cmd)
+ out1fmt("%s ", cmd);
+ if (xtra)
+ out1fmt("%s ", xtra);
+ p = vp->text;
+ if (vp->rfunc && (vp->flags & VFUNCREF) != 0) {
+ p = (*vp->rfunc)(vp);
+ if (p == NULL)
+ p = vp->text;
+ }
+ for ( ; *p != '=' ; p++)
+ out1c(*p);
+ if (!(vp->flags & VUNSET) && show_value) {
+ out1fmt("=");
+ print_quoted(++p);
+ }
+ out1c('\n');
+}
+
+int
+showvars(const char *cmd, int flag, int show_value, const char *xtra)
+{
+ struct var **vpp;
+ struct var *vp;
+
+ static struct var **list; /* static in case we are interrupted */
+ static int list_len;
+ int count = 0;
+
+ if (!list) {
+ list_len = 32;
+ list = ckmalloc(list_len * sizeof *list);
+ }
+
+ for (vpp = vartab ; vpp < vartab + VTABSIZE ; vpp++) {
+ for (vp = *vpp ; vp ; vp = vp->next) {
+ if (flag && !(vp->flags & flag))
+ continue;
+ if (vp->flags & VUNSET && !(show_value & 2))
+ continue;
+ if (count >= list_len) {
+ list = ckrealloc(list,
+ (list_len << 1) * sizeof *list);
+ list_len <<= 1;
+ }
+ list[count++] = vp;
+ }
+ }
+
+ qsort(list, count, sizeof *list, sort_var);
+
+ for (vpp = list; count--; vpp++)
+ showvar(*vpp, cmd, xtra, show_value);
+
+ /* no free(list), will be used again next time ... */
+
+ return 0;
+}
+
+
+
+/*
+ * The export and readonly commands.
+ */
+
+static void __dead
+export_usage(const char *cmd)
+{
+#ifdef SMALL
+ if (*cmd == 'r')
+ error("Usage: %s [ -p | var[=val]... ]", cmd);
+ else
+ error("Usage: %s [ -p | [-n] var[=val]... ]", cmd);
+#else
+ if (*cmd == 'r')
+ error("Usage: %s [-p [var...] | -q var... | var[=val]... ]", cmd);
+ else
+ error(
+ "Usage: %s [ -px [var...] | -q[x] var... | [-n|x] var[=val]... ]",
+ cmd);
+#endif
+}
+
+int
+exportcmd(int argc, char **argv)
+{
+ struct var *vp;
+ char *name;
+ const char *p = argv[0];
+ int flag = p[0] == 'r'? VREADONLY : VEXPORT;
+ int pflg = 0;
+ int nflg = 0;
+#ifndef SMALL
+ int xflg = 0;
+ int qflg = 0;
+#endif
+ int res;
+ int c;
+ int f;
+
+#ifdef SMALL
+#define EXPORT_OPTS "np"
+#else
+#define EXPORT_OPTS "npqx"
+#endif
+
+ while ((c = nextopt(EXPORT_OPTS)) != '\0') {
+
+#undef EXPORT_OPTS
+
+ switch (c) {
+ case 'n':
+ if (pflg || flag == VREADONLY
+#ifndef SMALL
+ || qflg || xflg
+#endif
+ )
+ export_usage(p);
+ nflg = 1;
+ break;
+ case 'p':
+ if (nflg
+#ifndef SMALL
+ || qflg
+#endif
+ )
+ export_usage(p);
+ pflg = 3;
+ break;
+#ifndef SMALL
+ case 'q':
+ if (nflg || pflg)
+ export_usage(p);
+ qflg = 1;
+ break;
+ case 'x':
+ if (nflg || flag == VREADONLY)
+ export_usage(p);
+ flag = VNOEXPORT;
+ xflg = 1;
+ break;
+#endif
+ }
+ }
+
+ if ((nflg
+#ifndef SMALL
+ || qflg
+#endif
+ ) && *argptr == NULL)
+ export_usage(p);
+
+#ifndef SMALL
+ if (pflg && *argptr != NULL) {
+ while ((name = *argptr++) != NULL) {
+ int len;
+
+ vp = find_var(name, NULL, &len);
+ if (name[len] == '=')
+ export_usage(p);
+ if (!goodname(name))
+ error("%s: bad variable name", name);
+
+ if (vp && vp->flags & flag)
+ showvar(vp, p, xflg ? "-x" : NULL, 1);
+ }
+ return 0;
+ }
+#endif
+
+ if (pflg || *argptr == NULL)
+ return showvars( pflg ? p : 0, flag, pflg,
+#ifndef SMALL
+ pflg && xflg ? "-x" :
+#endif
+ NULL );
+
+ res = 0;
+#ifndef SMALL
+ if (qflg) {
+ while ((name = *argptr++) != NULL) {
+ int len;
+
+ vp = find_var(name, NULL, &len);
+ if (name[len] == '=')
+ export_usage(p);
+ if (!goodname(name))
+ error("%s: bad variable name", name);
+
+ if (vp == NULL || !(vp->flags & flag))
+ res = 1;
+ }
+ return res;
+ }
+#endif
+
+ while ((name = *argptr++) != NULL) {
+ int len;
+
+ f = flag;
+
+ vp = find_var(name, NULL, &len);
+ p = name + len;
+ if (*p++ != '=')
+ p = NULL;
+
+ if (vp != NULL) {
+ if (nflg)
+ vp->flags &= ~flag;
+ else if (flag&VEXPORT && vp->flags&VNOEXPORT) {
+ /* note we go ahead and do any assignment */
+ sh_warnx("%.*s: not available for export",
+ len, name);
+ res = 1;
+ } else {
+ if (flag == VNOEXPORT)
+ vp->flags &= ~VEXPORT;
+
+ /* if not NULL will be done in setvar below */
+ if (p == NULL)
+ vp->flags |= flag;
+ }
+ if (p == NULL)
+ continue;
+ } else if (nflg && p == NULL && !goodname(name))
+ error("%s: bad variable name", name);
+
+ if (!nflg || p != NULL)
+ setvar(name, p, f);
+ }
+ return res;
+}
+
+
+/*
+ * The "local" command.
+ */
+
+int
+localcmd(int argc, char **argv)
+{
+ char *name;
+ int c;
+ int flags = 0; /*XXX perhaps VUNSET from a -o option value */
+
+ if (! in_function())
+ error("Not in a function");
+
+ /* upper case options, as bash stole all the good ones ... */
+ while ((c = nextopt("INx")) != '\0')
+ switch (c) {
+ case 'I': flags &= ~VUNSET; break;
+ case 'N': flags |= VUNSET; break;
+ case 'x': flags |= VEXPORT; break;
+ }
+
+ while ((name = *argptr++) != NULL) {
+ mklocal(name, flags);
+ }
+ return 0;
+}
+
+
+/*
+ * Make a variable a local variable. When a variable is made local, its
+ * value and flags are saved in a localvar structure. The saved values
+ * will be restored when the shell function returns. We handle the name
+ * "-" as a special case.
+ */
+
+void
+mklocal(const char *name, int flags)
+{
+ struct localvar *lvp;
+ struct var **vpp;
+ struct var *vp;
+
+ INTOFF;
+ lvp = ckmalloc(sizeof (struct localvar));
+ if (name[0] == '-' && name[1] == '\0') {
+ char *p;
+ p = ckmalloc(sizeof_optlist);
+ lvp->text = memcpy(p, optlist, sizeof_optlist);
+ lvp->rfunc = NULL;
+ vp = NULL;
+ xtrace_clone(0);
+ } else {
+ vp = find_var(name, &vpp, NULL);
+ if (vp == NULL) {
+ flags &= ~VNOEXPORT;
+ if (strchr(name, '='))
+ setvareq(savestr(name),
+ VSTRFIXED | (flags & ~VUNSET));
+ else
+ setvar(name, NULL, VSTRFIXED|flags);
+ vp = *vpp; /* the new variable */
+ lvp->text = NULL;
+ lvp->flags = VUNSET;
+ lvp->rfunc = NULL;
+ } else {
+ lvp->text = vp->text;
+ lvp->flags = vp->flags;
+ lvp->v_u = vp->v_u;
+ vp->flags |= VSTRFIXED|VTEXTFIXED;
+ if (flags & (VDOEXPORT | VUNSET))
+ vp->flags &= ~VNOEXPORT;
+ if (vp->flags & VNOEXPORT &&
+ (flags & (VEXPORT|VDOEXPORT|VUNSET)) == VEXPORT)
+ flags &= ~VEXPORT;
+ if (flags & (VNOEXPORT | VUNSET))
+ vp->flags &= ~VEXPORT;
+ flags &= ~VNOEXPORT;
+ if (name[vp->name_len] == '=')
+ setvareq(savestr(name), flags & ~VUNSET);
+ else if (flags & VUNSET)
+ unsetvar(name, 0);
+ else
+ vp->flags |= flags & (VUNSET|VEXPORT);
+
+ if (vp == &line_num) {
+ if (name[vp->name_len] == '=')
+ funclinebase = funclineabs -1;
+ else
+ funclinebase = 0;
+ }
+ }
+ }
+ lvp->vp = vp;
+ lvp->next = localvars;
+ localvars = lvp;
+ INTON;
+}
+
+
+/*
+ * Called after a function returns.
+ */
+
+void
+poplocalvars(void)
+{
+ struct localvar *lvp;
+ struct var *vp;
+
+ while ((lvp = localvars) != NULL) {
+ localvars = lvp->next;
+ vp = lvp->vp;
+ VTRACE(DBG_VARS, ("poplocalvar %s\n", vp ? vp->text : "-"));
+ if (vp == NULL) { /* $- saved */
+ memcpy(optlist, lvp->text, sizeof_optlist);
+ ckfree(lvp->text);
+ xtrace_pop();
+ optschanged();
+ } else if ((lvp->flags & (VUNSET|VSTRFIXED)) == VUNSET) {
+ (void)unsetvar(vp->text, 0);
+ } else {
+ if (lvp->func && (lvp->flags & (VNOFUNC|VFUNCREF)) == 0)
+ (*lvp->func)(lvp->text + vp->name_len + 1);
+ if ((vp->flags & VTEXTFIXED) == 0)
+ ckfree(vp->text);
+ vp->flags = lvp->flags;
+ vp->text = lvp->text;
+ vp->v_u = lvp->v_u;
+ }
+ ckfree(lvp);
+ }
+}
+
+
+int
+setvarcmd(int argc, char **argv)
+{
+ if (argc <= 2)
+ return unsetcmd(argc, argv);
+ else if (argc == 3)
+ setvar(argv[1], argv[2], 0);
+ else
+ error("List assignment not implemented");
+ return 0;
+}
+
+
+/*
+ * The unset builtin command. We unset the function before we unset the
+ * variable to allow a function to be unset when there is a readonly variable
+ * with the same name.
+ */
+
+int
+unsetcmd(int argc, char **argv)
+{
+ char **ap;
+ int i;
+ int flg_func = 0;
+ int flg_var = 0;
+ int flg_x = 0;
+ int ret = 0;
+
+ while ((i = nextopt("efvx")) != '\0') {
+ switch (i) {
+ case 'f':
+ flg_func = 1;
+ break;
+ case 'e':
+ case 'x':
+ flg_x = (2 >> (i == 'e'));
+ /* FALLTHROUGH */
+ case 'v':
+ flg_var = 1;
+ break;
+ }
+ }
+
+ if (flg_func == 0 && flg_var == 0)
+ flg_var = 1;
+
+ for (ap = argptr; *ap ; ap++) {
+ if (flg_func)
+ ret |= unsetfunc(*ap);
+ if (flg_var)
+ ret |= unsetvar(*ap, flg_x);
+ }
+ return ret;
+}
+
+
+/*
+ * Unset the specified variable.
+ */
+
+int
+unsetvar(const char *s, int unexport)
+{
+ struct var **vpp;
+ struct var *vp;
+
+ vp = find_var(s, &vpp, NULL);
+ if (vp == NULL)
+ return 0;
+
+ if (vp->flags & VREADONLY && !(unexport & 1))
+ return 1;
+
+ INTOFF;
+ if (unexport & 1) {
+ vp->flags &= ~VEXPORT;
+ } else {
+ if (vp->text[vp->name_len + 1] != '\0')
+ setvar(s, nullstr, 0);
+ if (!(unexport & 2))
+ vp->flags &= ~VEXPORT;
+ vp->flags |= VUNSET;
+ if ((vp->flags&(VEXPORT|VSTRFIXED|VREADONLY|VNOEXPORT)) == 0) {
+ if ((vp->flags & VTEXTFIXED) == 0)
+ ckfree(vp->text);
+ *vpp = vp->next;
+ ckfree(vp);
+ }
+ }
+ INTON;
+ return 0;
+}
+
+
+/*
+ * Returns true if the two strings specify the same varable. The first
+ * variable name is terminated by '='; the second may be terminated by
+ * either '=' or '\0'.
+ */
+
+STATIC int
+strequal(const char *p, const char *q)
+{
+ while (*p == *q++) {
+ if (*p++ == '=')
+ return 1;
+ }
+ if (*p == '=' && *(q - 1) == '\0')
+ return 1;
+ return 0;
+}
+
+/*
+ * Search for a variable.
+ * 'name' may be terminated by '=' or a NUL.
+ * vppp is set to the pointer to vp, or the list head if vp isn't found
+ * lenp is set to the number of characters in 'name'
+ */
+
+STATIC struct var *
+find_var(const char *name, struct var ***vppp, int *lenp)
+{
+ unsigned int hashval;
+ int len;
+ struct var *vp, **vpp;
+ const char *p = name;
+
+ hashval = 0;
+ while (*p && *p != '=')
+ hashval = 2 * hashval + (unsigned char)*p++;
+
+ len = p - name;
+ if (lenp)
+ *lenp = len;
+
+ vpp = &vartab[hashval % VTABSIZE];
+ if (vppp)
+ *vppp = vpp;
+
+ for (vp = *vpp ; vp ; vpp = &vp->next, vp = *vpp) {
+ if (vp->name_len != len)
+ continue;
+ if (memcmp(vp->text, name, len) != 0)
+ continue;
+ if (vppp)
+ *vppp = vpp;
+ return vp;
+ }
+ return NULL;
+}
+
+/*
+ * The following are the functions that create the values for
+ * shell variables that are dynamically produced when needed.
+ *
+ * The output strings cannot be malloc'd as there is nothing to
+ * free them - callers assume these are ordinary variables where
+ * the value returned is vp->text
+ *
+ * Each function needs its own storage space, as the results are
+ * used to create processes' environment, and (if exported) all
+ * the values will (might) be needed simultaneously.
+ *
+ * It is not a problem if a var is updated while nominally in use
+ * somewhere, all these are intended to be dynamic, the value they
+ * return is not guaranteed, an updated vaue is just as good.
+ *
+ * So, malloc a single buffer for the result of each function,
+ * grow, and even shrink, it as needed, but once we have one that
+ * is a suitable size for the actual usage, simply hold it forever.
+ *
+ * For a SMALL shell we implement only LINENO, none of the others,
+ * and give it just a fixed length static buffer for its result.
+ */
+
+#ifndef SMALL
+
+struct space_reserved { /* record of space allocated for results */
+ char *b;
+ int len;
+};
+
+/* rough (over-)estimate of the number of bytes needed to hold a number */
+static int
+digits_in(intmax_t number)
+{
+ int res = 0;
+
+ if (number & ~((1LL << 62) - 1))
+ res = 64; /* enough for 2^200 and a bit more */
+ else if (number & ~((1LL << 32) - 1))
+ res = 20; /* enough for 2^64 */
+ else if (number & ~((1 << 23) - 1))
+ res = 10; /* enough for 2^32 */
+ else
+ res = 8; /* enough for 2^23 or smaller */
+
+ return res;
+}
+
+static int
+make_space(struct space_reserved *m, int bytes)
+{
+ void *p;
+
+ if (m->len >= bytes && m->len <= (bytes<<2))
+ return 1;
+
+ bytes = SHELL_ALIGN(bytes);
+ /* not ckrealloc() - we want failure, not error() here */
+ p = realloc(m->b, bytes);
+ if (p == NULL) /* what we had should still be there */
+ return 0;
+
+ m->b = p;
+ m->len = bytes;
+ m->b[bytes - 1] = '\0';
+
+ return 1;
+}
+#endif
+
+char *
+get_lineno(struct var *vp)
+{
+#ifdef SMALL
+#define length (8 + 10) /* 10 digits is enough for a 32 bit line num */
+ static char result[length];
+#else
+ static struct space_reserved buf;
+#define result buf.b
+#define length buf.len
+#endif
+ int ln = line_number;
+
+ if (vp->flags & VUNSET)
+ return NULL;
+
+ ln -= funclinebase;
+
+#ifndef SMALL
+ if (!make_space(&buf, vp->name_len + 2 + digits_in(ln)))
+ return vp->text;
+#endif
+
+ snprintf(result, length, "%.*s=%d", vp->name_len, vp->text, ln);
+ return result;
+}
+#undef result
+#undef length
+
+#ifndef SMALL
+
+char *
+get_hostname(struct var *vp)
+{
+ static struct space_reserved buf;
+
+ if (vp->flags & VUNSET)
+ return NULL;
+
+ if (!make_space(&buf, vp->name_len + 2 + 256))
+ return vp->text;
+
+ memcpy(buf.b, vp->text, vp->name_len + 1); /* include '=' */
+ (void)gethostname(buf.b + vp->name_len + 1,
+ buf.len - vp->name_len - 3);
+ return buf.b;
+}
+
+char *
+get_tod(struct var *vp)
+{
+ static struct space_reserved buf; /* space for answers */
+ static struct space_reserved tzs; /* remember TZ last used */
+ static timezone_t last_zone; /* timezone data for tzs zone */
+ const char *fmt;
+ char *tz;
+ time_t now;
+ struct tm tm_now, *tmp;
+ timezone_t zone = NULL;
+ static char t_err[] = "time error";
+ int len;
+
+ if (vp->flags & VUNSET)
+ return NULL;
+
+ fmt = lookupvar("ToD_FORMAT");
+ if (fmt == NULL)
+ fmt="%T";
+ tz = lookupvar("TZ");
+ (void)time(&now);
+
+ if (tz != NULL) {
+ if (tzs.b == NULL || strcmp(tzs.b, tz) != 0) {
+ if (make_space(&tzs, strlen(tz) + 1)) {
+ INTOFF;
+ strcpy(tzs.b, tz);
+ if (last_zone)
+ tzfree(last_zone);
+ last_zone = zone = tzalloc(tz);
+ INTON;
+ } else
+ zone = tzalloc(tz);
+ } else
+ zone = last_zone;
+
+ tmp = localtime_rz(zone, &now, &tm_now);
+ } else
+ tmp = localtime_r(&now, &tm_now);
+
+ len = (strlen(fmt) * 4) + vp->name_len + 2;
+ while (make_space(&buf, len)) {
+ memcpy(buf.b, vp->text, vp->name_len+1);
+ if (tmp == NULL) {
+ if (buf.len >= vp->name_len+2+(int)(sizeof t_err - 1)) {
+ strcpy(buf.b + vp->name_len + 1, t_err);
+ if (zone && zone != last_zone)
+ tzfree(zone);
+ return buf.b;
+ }
+ len = vp->name_len + 4 + sizeof t_err - 1;
+ continue;
+ }
+ if (strftime_z(zone, buf.b + vp->name_len + 1,
+ buf.len - vp->name_len - 2, fmt, tmp)) {
+ if (zone && zone != last_zone)
+ tzfree(zone);
+ return buf.b;
+ }
+ if (len >= 4096) /* Let's be reasonable */
+ break;
+ len <<= 1;
+ }
+ if (zone && zone != last_zone)
+ tzfree(zone);
+ return vp->text;
+}
+
+char *
+get_seconds(struct var *vp)
+{
+ static struct space_reserved buf;
+ intmax_t secs;
+
+ if (vp->flags & VUNSET)
+ return NULL;
+
+ secs = (intmax_t)time((time_t *)0) - sh_start_time;
+ if (!make_space(&buf, vp->name_len + 2 + digits_in(secs)))
+ return vp->text;
+
+ snprintf(buf.b, buf.len, "%.*s=%jd", vp->name_len, vp->text, secs);
+ return buf.b;
+}
+
+char *
+get_euser(struct var *vp)
+{
+ static struct space_reserved buf;
+ static uid_t lastuid = 0;
+ uid_t euid;
+ struct passwd *pw;
+
+ if (vp->flags & VUNSET)
+ return NULL;
+
+ euid = geteuid();
+ if (buf.b != NULL && lastuid == euid)
+ return buf.b;
+
+ pw = getpwuid(euid);
+ if (pw == NULL)
+ return vp->text;
+
+ if (make_space(&buf, vp->name_len + 2 + strlen(pw->pw_name))) {
+ lastuid = euid;
+ snprintf(buf.b, buf.len, "%.*s=%s", vp->name_len, vp->text,
+ pw->pw_name);
+ return buf.b;
+ }
+
+ return vp->text;
+}
+
+char *
+get_random(struct var *vp)
+{
+ static struct space_reserved buf;
+ static intmax_t random_val = 0;
+
+#ifdef USE_LRAND48
+#define random lrand48
+#define srandom srand48
+#endif
+
+ if (vp->flags & VUNSET)
+ return NULL;
+
+ if (vp->text != buf.b) {
+ /*
+ * Either initialisation, or a new seed has been set
+ */
+ if (vp->text[vp->name_len + 1] == '\0') {
+ int fd;
+
+ /*
+ * initialisation (without pre-seeding),
+ * or explictly requesting a truly random seed.
+ */
+ fd = open("/dev/urandom", 0);
+ if (fd == -1) {
+ out2str("RANDOM initialisation failed\n");
+ random_val = (getpid()<<3) ^ time((time_t *)0);
+ } else {
+ int n;
+
+ do {
+ n = read(fd,&random_val,sizeof random_val);
+ } while (n != sizeof random_val);
+ close(fd);
+ }
+ } else
+ /* good enough for today */
+ random_val = strtoimax(vp->text+vp->name_len+1,NULL,0);
+
+ srandom((long)random_val);
+ }
+
+#if 0
+ random_val = (random_val + 1) & 0x7FFF; /* 15 bit "random" numbers */
+#else
+ random_val = (random() >> 5) & 0x7FFF;
+#endif
+
+ if (!make_space(&buf, vp->name_len + 2 + digits_in(random_val)))
+ return vp->text;
+
+ snprintf(buf.b, buf.len, "%.*s=%jd", vp->name_len, vp->text,
+ random_val);
+
+ if (buf.b != vp->text && (vp->flags & (VTEXTFIXED|VSTACK)) == 0)
+ free(vp->text);
+ vp->flags |= VTEXTFIXED;
+ vp->text = buf.b;
+
+ return vp->text;
+#undef random
+#undef srandom
+}
+
+#endif /* SMALL */
diff --git a/bin/sh/var.h b/bin/sh/var.h
new file mode 100644
index 0000000..43bef4b
--- /dev/null
+++ b/bin/sh/var.h
@@ -0,0 +1,151 @@
+/* $NetBSD: var.h,v 1.38 2018/12/04 14:03:30 kre Exp $ */
+
+/*-
+ * Copyright (c) 1991, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Kenneth Almquist.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * @(#)var.h 8.2 (Berkeley) 5/4/95
+ */
+
+#ifndef VUNSET /* double include protection */
+/*
+ * Shell variables.
+ */
+
+/* flags */
+#define VUNSET 0x0001 /* the variable is not set */
+#define VEXPORT 0x0002 /* variable is exported */
+#define VREADONLY 0x0004 /* variable cannot be modified */
+#define VNOEXPORT 0x0008 /* variable may not be exported */
+
+#define VSTRFIXED 0x0010 /* variable struct is statically allocated */
+#define VTEXTFIXED 0x0020 /* text is statically allocated */
+#define VSTACK 0x0040 /* text is allocated on the stack */
+#define VNOFUNC 0x0100 /* don't call the callback function */
+#define VFUNCREF 0x0200 /* the function is called on ref, not set */
+
+#define VSPECIAL 0x1000 /* magic properties not lost when set */
+#define VDOEXPORT 0x2000 /* obey VEXPORT even if VNOEXPORT */
+#define VNOSET 0x4000 /* do not set variable - just readonly test */
+#define VNOERROR 0x8000 /* be quiet if set fails (no error msg) */
+
+struct var;
+
+union var_func_union { /* function to be called when: */
+ void (*set_func)(const char *); /* variable gets set/unset */
+ char*(*ref_func)(struct var *); /* variable is referenced */
+};
+
+struct var {
+ struct var *next; /* next entry in hash list */
+ int flags; /* flags are defined above */
+ char *text; /* name=value */
+ int name_len; /* length of name */
+ union var_func_union v_u; /* function to apply (sometimes) */
+};
+
+
+struct localvar {
+ struct localvar *next; /* next local variable in list */
+ struct var *vp; /* the variable that was made local */
+ int flags; /* saved flags */
+ char *text; /* saved text */
+ union var_func_union v_u; /* saved function */
+};
+
+
+extern struct localvar *localvars;
+
+extern struct var vifs;
+extern char ifs_default[];
+extern struct var vmail;
+extern struct var vmpath;
+extern struct var vpath;
+extern struct var vps1;
+extern struct var vps2;
+extern struct var vps4;
+extern struct var line_num;
+#ifndef SMALL
+extern struct var editrc;
+extern struct var vterm;
+extern struct var vtermcap;
+extern struct var vhistsize;
+extern struct var ps_lit;
+extern struct var euname;
+extern struct var random_num;
+extern intmax_t sh_start_time;
+#endif
+
+extern int line_number;
+extern int funclinebase;
+extern int funclineabs;
+
+/*
+ * The following macros access the values of the above variables.
+ * They have to skip over the name. They return the null string
+ * for unset variables.
+ */
+
+#define ifsset() ((vifs.flags & VUNSET) == 0)
+#define ifsval() (ifsset() ? (vifs.text + 4) : ifs_default)
+#define mailval() (vmail.text + 5)
+#define mpathval() (vmpath.text + 9)
+#define pathval() (vpath.text + 5)
+#define ps1val() (vps1.text + 4)
+#define ps2val() (vps2.text + 4)
+#define ps4val() (vps4.text + 4)
+#define optindval() (voptind.text + 7)
+#ifndef SMALL
+#define histsizeval() (vhistsize.text + 9)
+#define termval() (vterm.text + 5)
+#endif
+
+#define mpathset() ((vmpath.flags & VUNSET) == 0)
+
+void initvar(void);
+void setvar(const char *, const char *, int);
+void setvareq(char *, int);
+struct strlist;
+void listsetvar(struct strlist *, int);
+char *lookupvar(const char *);
+char *bltinlookup(const char *, int);
+char **environment(void);
+void shprocvar(void);
+int showvars(const char *, int, int, const char *);
+void mklocal(const char *, int);
+void listmklocal(struct strlist *, int);
+void poplocalvars(void);
+int unsetvar(const char *, int);
+void choose_ps1(void);
+int setvarsafe(const char *, const char *, int);
+void print_quoted(const char *);
+int validname(const char *, int, int *);
+
+#endif
diff --git a/bin/sh/version.h b/bin/sh/version.h
new file mode 100644
index 0000000..59069e4
--- /dev/null
+++ b/bin/sh/version.h
@@ -0,0 +1,36 @@
+/* $NetBSD: version.h,v 1.3 2018/12/12 12:16:42 kre Exp $ */
+
+/*-
+ * Copyright (c) 2014 The NetBSD Foundation, Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
+ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/*
+ * This value should be modified rarely - only when significant changes
+ * to the shell (which does not mean a bug fixed, or some new feature added)
+ * have been made. The most likely reason to change this value would be
+ * when a new (shell only) release is to be exported. This should not be
+ * updated just because a new NetBSD release is to include this code.
+ */
+#define NETBSD_SHELL "20181212"
diff --git a/bin/sleep/sleep.1 b/bin/sleep/sleep.1
new file mode 100644
index 0000000..1ee73b3
--- /dev/null
+++ b/bin/sleep/sleep.1
@@ -0,0 +1,177 @@
+.\" $NetBSD: sleep.1,v 1.27 2019/01/27 17:42:53 wiz Exp $
+.\"
+.\" Copyright (c) 1990, 1993, 1994
+.\" The Regents of the University of California. All rights reserved.
+.\"
+.\" This code is derived from software contributed to Berkeley by
+.\" the Institute of Electrical and Electronics Engineers, Inc.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\" notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must reproduce the above copyright
+.\" notice, this list of conditions and the following disclaimer in the
+.\" documentation and/or other materials provided with the distribution.
+.\" 3. Neither the name of the University nor the names of its contributors
+.\" may be used to endorse or promote products derived from this software
+.\" without specific prior written permission.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+.\" ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+.\" SUCH DAMAGE.
+.\"
+.\" @(#)sleep.1 8.3 (Berkeley) 4/18/94
+.\"
+.Dd January 26, 2019
+.Dt SLEEP 1
+.Os
+.Sh NAME
+.Nm sleep
+.Nd suspend execution for an interval of time
+.Sh SYNOPSIS
+.Nm
+.Ar seconds
+.Sh DESCRIPTION
+The
+.Nm
+utility suspends execution for a minimum of
+.Ar seconds
+seconds, then exits.
+It is usually used to schedule the execution of other commands (see
+.Sx EXAMPLES
+below).
+.Pp
+Note: The
+.Nx
+.Nm
+command will accept and honor a non-integer number of specified seconds.
+Note however, that if the request is for much more than 2.5 hours,
+any fractional seconds will be ignored.
+Permitting non-integral delays is a non-portable extension,
+and its use will decrease the probability that
+a shell script will execute properly on another system.
+.Pp
+When the
+.Dv SIGINFO
+signal is received, an estimate of the number of seconds remaining to
+sleep is printed on the standard output.
+.Sh EXIT STATUS
+The
+.Nm
+utility exits with one of the following values:
+.Bl -tag -width flag
+.It Li \&0
+On successful completion, or if the signal
+.Dv SIGALRM
+was received.
+.It Li \&>\&0
+An error occurred.
+.El
+.Sh EXAMPLES
+To schedule the execution of a command for 1800 seconds later:
+.Pp
+.Dl (sleep 1800; sh command_file >errors 2>&1)&
+.Pp
+This incantation would wait half an hour before
+running the script
+.Dq command_file .
+(See the
+.Xr at 1
+utility.)
+.Pp
+To repeatedly run a command (using
+.Xr csh 1 ) :
+.Pp
+.Bd -literal -offset indent -compact
+while (1)
+ if (! -r zzz.rawdata) then
+ sleep 300
+ else
+ foreach i (*.rawdata)
+ sleep 70
+ awk -f collapse_data $i >> results
+ end
+ break
+ endif
+end
+.Ed
+.Pp
+The scenario for a script such as this might be: a program currently
+running is taking longer than expected to process a series of
+files, and it would be nice to have
+another program start processing the files created by the first
+program as soon as it is finished (when zzz.rawdata is created).
+The script checks every five minutes for the file zzz.rawdata.
+When the file is found, processing the generated files (*.rawdata)
+is done courteously by sleeping for 70 seconds in between each
+awk job.
+.Pp
+To wait until a particular time, the following,
+with some error checking added, might be used (using
+.Xr sh 1
+on
+.Nx ) :
+.Bd -literal -offset indent
+END=$(( $( date -d "$1" +%s ) - START_TIME ))
+while [ "${SECONDS}" -lt "${END}" ]
+do
+ sleep "$((END - SECONDS))"
+done
+.Ed
+.Pp
+where the argument
+.Sq \&$1
+specifies the desired date and time in any format the
+.Fl d
+option to the
+.Xr date 1
+command accepts.
+.Sh SEE ALSO
+.Xr at 1 ,
+.Xr csh 1 ,
+.Xr date 1 ,
+.Xr sh 1 ,
+.Xr nanosleep 2 ,
+.Xr sleep 3
+.Sh STANDARDS
+The
+.Nm
+command is expected to be
+.St -p1003.2
+compatible.
+.Sh HISTORY
+A
+.Nm
+utility appeared in
+.At v4 .
+Processing fractional seconds, and processing the
+.Ic seconds
+argument respecting the current locale, was added in
+.Nx 1.3 .
+The ability to sleep for extended periods appeared in
+.Nx 9 .
+.Sh BUGS
+This
+.Nm
+command cannot handle requests for durations
+much longer than about 250 billion years.
+Any such attempt will result in an error,
+and immediate termination.
+It is suggested that when there is a need
+for sleeps exceeding this period, the
+.Nm
+command be executed in a loop, with each
+individual
+.Nm
+invocation limited to 200 billion years
+approximately.
diff --git a/bin/sleep/sleep.c b/bin/sleep/sleep.c
new file mode 100644
index 0000000..dfaf302
--- /dev/null
+++ b/bin/sleep/sleep.c
@@ -0,0 +1,229 @@
+/* $NetBSD: sleep.c,v 1.29 2019/01/27 02:00:45 christos Exp $ */
+
+/*
+ * Copyright (c) 1988, 1993, 1994
+ * The Regents of the University of California. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+#ifndef lint
+__COPYRIGHT("@(#) Copyright (c) 1988, 1993, 1994\
+ The Regents of the University of California. All rights reserved.");
+#endif /* not lint */
+
+#ifndef lint
+#if 0
+static char sccsid[] = "@(#)sleep.c 8.3 (Berkeley) 4/2/94";
+#else
+__RCSID("$NetBSD: sleep.c,v 1.29 2019/01/27 02:00:45 christos Exp $");
+#endif
+#endif /* not lint */
+
+#include <ctype.h>
+#include <err.h>
+#include <locale.h>
+#include <math.h>
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <time.h>
+#include <unistd.h>
+
+__dead static void alarmhandle(int);
+__dead static void usage(void);
+
+static void report(const time_t, const time_t, const char *const);
+
+static volatile sig_atomic_t report_requested;
+static void
+report_request(int signo __unused)
+{
+
+ report_requested = 1;
+}
+
+int
+main(int argc, char *argv[])
+{
+ char *arg, *temp;
+ const char *msg;
+ double fval, ival, val;
+ struct timespec ntime;
+ time_t original;
+ int ch, fracflag;
+ unsigned delay;
+
+ setprogname(argv[0]);
+ (void)setlocale(LC_ALL, "");
+
+ (void)signal(SIGALRM, alarmhandle);
+
+ while ((ch = getopt(argc, argv, "")) != -1)
+ switch(ch) {
+ default:
+ usage();
+ }
+ argc -= optind;
+ argv += optind;
+
+ if (argc != 1)
+ usage();
+
+ /*
+ * Okay, why not just use atof for everything? Why bother
+ * checking if there is a fraction in use? Because the old
+ * sleep handled the full range of integers, that's why, and a
+ * double can't handle a large long. This is fairly useless
+ * given how large a number a double can hold on most
+ * machines, but now we won't ever have trouble. If you want
+ * 1000000000.9 seconds of sleep, well, that's your
+ * problem. Why use an isdigit() check instead of checking for
+ * a period? Because doing it this way means locales will be
+ * handled transparently by the atof code.
+ *
+ * Since fracflag is set for any non-digit, we also fall
+ * into the floating point conversion path if the input
+ * is hex (the 'x' in 0xA is not a digit). Then if
+ * strtod() handles hex (on NetBSD it does) so will we.
+ * That path is also taken for scientific notation (1.2e+3)
+ * and when the input is simply nonsense.
+ */
+ fracflag = 0;
+ arg = *argv;
+ for (temp = arg; *temp != '\0'; temp++)
+ if (!isdigit((unsigned char)*temp)) {
+ ch = *temp;
+ fracflag++;
+ }
+
+ if (fracflag) {
+ /*
+ * If we cannot convert the value using the user's locale
+ * then try again using the C locale, so strtod() can always
+ * parse values like 2.5, even if the user's locale uses
+ * a different decimal radix character (like ',')
+ *
+ * (but only if that is the potential problem)
+ */
+ val = strtod(arg, &temp);
+ if (*temp != '\0')
+ val = strtod_l(arg, &temp, LC_C_LOCALE);
+ if (val < 0 || temp == arg || *temp != '\0')
+ usage();
+
+ ival = floor(val);
+ fval = (1000000000 * (val-ival));
+ ntime.tv_sec = ival;
+ if ((double)ntime.tv_sec != ival)
+ errx(1, "requested delay (%s) out of range", arg);
+ ntime.tv_nsec = fval;
+
+ if (ntime.tv_sec == 0 && ntime.tv_nsec == 0)
+ return EXIT_SUCCESS; /* was 0.0 or underflowed */
+ } else {
+ ntime.tv_sec = strtol(arg, &temp, 10);
+ if (ntime.tv_sec < 0 || temp == arg || *temp != '\0')
+ usage();
+
+ if (ntime.tv_sec == 0)
+ return EXIT_SUCCESS;
+ ntime.tv_nsec = 0;
+ }
+
+ original = ntime.tv_sec;
+ if (ntime.tv_nsec != 0)
+ msg = " and a bit";
+ else
+ msg = "";
+
+ signal(SIGINFO, report_request);
+
+ if (ntime.tv_sec <= 10000) { /* arbitrary */
+ while (nanosleep(&ntime, &ntime) != 0) {
+ if (report_requested) {
+ report(ntime.tv_sec, original, msg);
+ report_requested = 0;
+ } else
+ err(EXIT_FAILURE, "nanosleep failed");
+ }
+ } else while (ntime.tv_sec > 0) {
+ delay = (unsigned int)ntime.tv_sec;
+
+ if ((time_t)delay != ntime.tv_sec || delay > 30 * 86400)
+ delay = 30 * 86400;
+
+ ntime.tv_sec -= delay;
+ delay = sleep(delay);
+ ntime.tv_sec += delay;
+
+ if (delay != 0 && report_requested) {
+ report(ntime.tv_sec, original, "");
+ report_requested = 0;
+ } else
+ break;
+ }
+
+ return EXIT_SUCCESS;
+ /* NOTREACHED */
+}
+
+ /* Reporting does not bother with nanoseconds. */
+static void
+report(const time_t remain, const time_t original, const char * const msg)
+{
+ if (remain == 0)
+ warnx("In the final moments of the original"
+ " %jd%s second%s", (intmax_t)original, msg,
+ original == 1 && *msg == '\0' ? "" : "s");
+ else if (remain < 2000)
+ warnx("Between %jd and %jd seconds left"
+ " out of the original %g%s",
+ (intmax_t)remain, (intmax_t)remain + 1, (double)original,
+ msg);
+ else if ((original - remain) < 100000 && (original-remain) < original/8)
+ warnx("Have waited only %jd seconds of the original %g",
+ (intmax_t)(original - remain), (double)original);
+ else
+ warnx("Approximately %g seconds left out of the original %g",
+ (double)remain, (double)original);
+}
+
+static void
+usage(void)
+{
+ (void)fprintf(stderr, "usage: %s seconds\n", getprogname());
+ exit(EXIT_FAILURE);
+ /* NOTREACHED */
+}
+
+/* ARGSUSED */
+static void
+alarmhandle(int i)
+{
+ _exit(EXIT_SUCCESS);
+ /* NOTREACHED */
+}
diff --git a/bin/stty/cchar.c b/bin/stty/cchar.c
new file mode 100644
index 0000000..ad88c76
--- /dev/null
+++ b/bin/stty/cchar.c
@@ -0,0 +1,148 @@
+/* $NetBSD: cchar.c,v 1.16 2006/10/16 00:37:55 christos Exp $ */
+
+/*-
+ * Copyright (c) 1991, 1993, 1994
+ * The Regents of the University of California. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+#ifndef lint
+#if 0
+static char sccsid[] = "@(#)cchar.c 8.5 (Berkeley) 4/2/94";
+#else
+__RCSID("$NetBSD: cchar.c,v 1.16 2006/10/16 00:37:55 christos Exp $");
+#endif
+#endif /* not lint */
+
+#include <sys/types.h>
+
+#include <err.h>
+#include <limits.h>
+#include <stddef.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "stty.h"
+#include "extern.h"
+
+/*
+ * Special control characters.
+ *
+ * Cchars1 are the standard names, cchars2 are the old aliases.
+ * The first are displayed, but both are recognized on the
+ * command line.
+ */
+const struct cchar cchars1[] = {
+ { "discard", VDISCARD, CDISCARD },
+ { "dsusp", VDSUSP, CDSUSP },
+ { "eof", VEOF, CEOF },
+ { "eol", VEOL, CEOL },
+ { "eol2", VEOL2, CEOL },
+ { "erase", VERASE, CERASE },
+ { "intr", VINTR, CINTR },
+ { "kill", VKILL, CKILL },
+ { "lnext", VLNEXT, CLNEXT },
+ { "min", VMIN, CMIN },
+ { "quit", VQUIT, CQUIT },
+ { "reprint", VREPRINT, CREPRINT },
+ { "start", VSTART, CSTART },
+ { "status", VSTATUS, CSTATUS },
+ { "stop", VSTOP, CSTOP },
+ { "susp", VSUSP, CSUSP },
+ { "time", VTIME, CTIME },
+ { "werase", VWERASE, CWERASE },
+ { .name = NULL },
+};
+
+const struct cchar cchars2[] = {
+ { "brk", VEOL, CEOL },
+ { "flush", VDISCARD, CDISCARD },
+ { "rprnt", VREPRINT, CREPRINT },
+ { .name = NULL },
+};
+
+static int c_cchar(const void *, const void *);
+
+static int
+c_cchar(const void *a, const void *b)
+{
+ return (strcmp(((const struct cchar *)a)->name,
+ ((const struct cchar *)b)->name));
+}
+
+int
+csearch(char ***argvp, struct info *ip)
+{
+ struct cchar *cp, tmp;
+ long val;
+ char *arg, *ep, *name;
+
+ name = **argvp;
+
+ tmp.name = name;
+ if (!(cp = (struct cchar *)bsearch(&tmp, cchars1,
+ sizeof(cchars1)/sizeof(cchars1[0]) - 1, sizeof(cchars1[0]),
+ c_cchar)) &&
+ !(cp = (struct cchar *)bsearch(&tmp, cchars2,
+ sizeof(cchars2)/sizeof(cchars2[0]) - 1, sizeof(cchars2[0]),
+ c_cchar)))
+ return (0);
+
+ arg = *++*argvp;
+ if (!arg) {
+ warnx("option requires an argument -- %s", name);
+ usage();
+ }
+
+#define CHK(s) (*arg == s[0] && !strcmp(arg, s))
+ if (CHK("undef") || CHK("<undef>"))
+ ip->t.c_cc[cp->sub] = _POSIX_VDISABLE;
+ else if (cp->sub == VMIN || cp->sub == VTIME) {
+ val = strtol(arg, &ep, 10);
+ if (val == _POSIX_VDISABLE) {
+ warnx("value of %ld would disable the option -- %s",
+ val, name);
+ usage();
+ }
+ if (val > UCHAR_MAX) {
+ warnx("maximum option value is %d -- %s",
+ UCHAR_MAX, name);
+ usage();
+ }
+ if (*ep != '\0') {
+ warnx("option requires a numeric argument -- %s", name);
+ usage();
+ }
+ ip->t.c_cc[cp->sub] = (cc_t)val;
+ } else if (arg[0] == '^')
+ ip->t.c_cc[cp->sub] = (arg[1] == '?') ? 0177 :
+ (arg[1] == '-') ? _POSIX_VDISABLE : arg[1] & 037;
+ else
+ ip->t.c_cc[cp->sub] = arg[0];
+ ip->set = 1;
+ return (1);
+}
diff --git a/bin/stty/extern.h b/bin/stty/extern.h
new file mode 100644
index 0000000..92598eb
--- /dev/null
+++ b/bin/stty/extern.h
@@ -0,0 +1,51 @@
+/* $NetBSD: extern.h,v 1.13 2013/09/12 19:47:23 christos Exp $ */
+
+/*-
+ * Copyright (c) 1991, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * @(#)extern.h 8.1 (Berkeley) 5/31/93
+ */
+
+#ifndef _EXTERN_H_
+#define _EXTERN_H_
+
+int c_cchars(const void *, const void *);
+int c_modes(const void *, const void *);
+int csearch(char ***, struct info *);
+void checkredirect(void);
+void gprint(struct termios *);
+void gread(struct termios *, char *);
+int ksearch(char ***, struct info *);
+int msearch(char ***, struct info *);
+void optlist(void);
+void print(struct termios *, struct winsize *, int, const char *, enum FMT);
+__dead void usage(void);
+
+extern const struct cchar cchars1[], cchars2[];
+
+#endif /* !_EXTERN_H_ */
diff --git a/bin/stty/gfmt.c b/bin/stty/gfmt.c
new file mode 100644
index 0000000..55ec0c2
--- /dev/null
+++ b/bin/stty/gfmt.c
@@ -0,0 +1,132 @@
+/* $NetBSD: gfmt.c,v 1.17 2011/08/29 14:51:19 joerg Exp $ */
+
+/*-
+ * Copyright (c) 1991, 1993, 1994
+ * The Regents of the University of California. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+#ifndef lint
+#if 0
+static char sccsid[] = "@(#)gfmt.c 8.6 (Berkeley) 4/2/94";
+#else
+__RCSID("$NetBSD: gfmt.c,v 1.17 2011/08/29 14:51:19 joerg Exp $");
+#endif
+#endif /* not lint */
+
+#include <sys/param.h>
+
+#include <err.h>
+#include <stdio.h>
+#include <string.h>
+
+#include "stty.h"
+#include "extern.h"
+
+__dead static void gerr(char *);
+
+static void
+gerr(char *s)
+{
+ if (s)
+ errx(1, "illegal gfmt1 option -- %s", s);
+ else
+ errx(1, "illegal gfmt1 option");
+}
+
+void
+gprint(struct termios *tp)
+{
+ const struct cchar *cp;
+
+ (void)printf("gfmt1:cflag=%x:iflag=%x:lflag=%x:oflag=%x:",
+ tp->c_cflag, tp->c_iflag, tp->c_lflag, tp->c_oflag);
+ for (cp = cchars1; cp->name; ++cp)
+ (void)printf("%s=%x:", cp->name, tp->c_cc[cp->sub]);
+ (void)printf("ispeed=%d:ospeed=%d\n", cfgetispeed(tp), cfgetospeed(tp));
+}
+
+void
+gread(struct termios *tp, char *s)
+{
+ const struct cchar *cp;
+ char *ep, *p;
+ long tmp;
+
+ if ((s = strchr(s, ':')) == NULL)
+ gerr(NULL);
+ for (++s; s != NULL;) {
+ p = strsep(&s, ":\0");
+ if (!p || !*p)
+ break;
+ if (!(ep = strchr(p, '=')))
+ gerr(p);
+ *ep++ = '\0';
+ (void)sscanf(ep, "%lx", &tmp);
+
+#define CHK(s) (*p == s[0] && !strcmp(p, s))
+ if (CHK("cflag")) {
+ tp->c_cflag = tmp;
+ continue;
+ }
+ if (CHK("iflag")) {
+ tp->c_iflag = tmp;
+ continue;
+ }
+#ifdef BSD4_4
+ if (CHK("ispeed")) {
+ (void)sscanf(ep, "%ld", &tmp);
+ tp->c_ispeed = tmp;
+ continue;
+ }
+#endif
+ if (CHK("lflag")) {
+ tp->c_lflag = tmp;
+ continue;
+ }
+ if (CHK("oflag")) {
+ tp->c_oflag = tmp;
+ continue;
+ }
+#ifdef BSD4_4
+ if (CHK("ospeed")) {
+ (void)sscanf(ep, "%ld", &tmp);
+ tp->c_ospeed = tmp;
+ continue;
+ }
+#endif
+ for (cp = cchars1; cp->name != NULL; ++cp)
+ if (CHK(cp->name)) {
+ if (cp->sub == VMIN || cp->sub == VTIME)
+ (void)sscanf(ep, "%ld", &tmp);
+ tp->c_cc[cp->sub] = tmp;
+ break;
+ }
+ if (cp->name == NULL)
+ gerr(p);
+ }
+}
diff --git a/bin/stty/key.c b/bin/stty/key.c
new file mode 100644
index 0000000..0094d3b
--- /dev/null
+++ b/bin/stty/key.c
@@ -0,0 +1,332 @@
+/* $NetBSD: key.c,v 1.22 2017/01/10 20:44:05 christos Exp $ */
+
+/*-
+ * Copyright (c) 1991, 1993, 1994
+ * The Regents of the University of California. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+#ifndef lint
+#if 0
+static char sccsid[] = "@(#)key.c 8.4 (Berkeley) 2/20/95";
+#else
+__RCSID("$NetBSD: key.c,v 1.22 2017/01/10 20:44:05 christos Exp $");
+#endif
+#endif /* not lint */
+
+#include <sys/types.h>
+
+#include <err.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <time.h>
+#include <paths.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "stty.h"
+#include "extern.h"
+
+__BEGIN_DECLS
+void f_all(struct info *);
+void f_cbreak(struct info *);
+void f_columns(struct info *);
+void f_dec(struct info *);
+void f_everything(struct info *);
+void f_extproc(struct info *);
+void f_insane(struct info *);
+void f_ispeed(struct info *);
+void f_nl(struct info *);
+void f_ospeed(struct info *);
+void f_raw(struct info *);
+void f_rows(struct info *);
+void f_sane(struct info *);
+void f_size(struct info *);
+void f_speed(struct info *);
+void f_ostart(struct info *);
+void f_ostop(struct info *);
+void f_tty(struct info *);
+__END_DECLS
+
+static const struct key {
+ const char *name; /* name */
+ void (*f)(struct info *); /* function */
+#define F_NEEDARG 0x01 /* needs an argument */
+#define F_OFFOK 0x02 /* can turn off */
+ int flags;
+} keys[] = {
+ { "all", f_all, 0 },
+ { "cbreak", f_cbreak, F_OFFOK },
+ { "cols", f_columns, F_NEEDARG },
+ { "columns", f_columns, F_NEEDARG },
+ { "cooked", f_sane, 0 },
+ { "dec", f_dec, 0 },
+ { "everything", f_everything, 0 },
+ { "extproc", f_extproc, F_OFFOK },
+ { "insane", f_insane, 0 },
+ { "ispeed", f_ispeed, F_NEEDARG },
+ { "new", f_tty, 0 },
+ { "nl", f_nl, F_OFFOK },
+ { "old", f_tty, 0 },
+ { "ospeed", f_ospeed, F_NEEDARG },
+ { "ostart", f_ostart, 0 },
+ { "ostop", f_ostop, 0 },
+ { "raw", f_raw, F_OFFOK },
+ { "rows", f_rows, F_NEEDARG },
+ { "sane", f_sane, 0 },
+ { "size", f_size, 0 },
+ { "speed", f_speed, 0 },
+ { "tty", f_tty, 0 },
+};
+
+static int c_key(const void *, const void *);
+
+static int
+c_key(const void *a, const void *b)
+{
+
+ return (strcmp(((const struct key *)a)->name,
+ ((const struct key *)b)->name));
+}
+
+int
+ksearch(char ***argvp, struct info *ip)
+{
+ char *name;
+ struct key *kp, tmp;
+
+ name = **argvp;
+ if (*name == '-') {
+ ip->off = 1;
+ ++name;
+ } else
+ ip->off = 0;
+
+ tmp.name = name;
+ if (!(kp = (struct key *)bsearch(&tmp, keys,
+ sizeof(keys)/sizeof(struct key), sizeof(struct key), c_key)))
+ return (0);
+ if (!(kp->flags & F_OFFOK) && ip->off) {
+ warnx("illegal option -- %s", name);
+ usage();
+ }
+ if (kp->flags & F_NEEDARG && !(ip->arg = *++*argvp)) {
+ warnx("option requires an argument -- %s", name);
+ usage();
+ }
+ kp->f(ip);
+ return (1);
+}
+
+void
+f_all(struct info *ip)
+{
+ print(&ip->t, &ip->win, ip->queue, ip->ldisc, STTY_BSD);
+}
+
+void
+f_cbreak(struct info *ip)
+{
+ if (ip->off)
+ f_sane(ip);
+ else {
+ ip->t.c_iflag |= BRKINT|IXON|IMAXBEL;
+ ip->t.c_oflag |= OPOST;
+ ip->t.c_lflag |= ISIG|IEXTEN;
+ ip->t.c_lflag &= ~ICANON;
+ ip->set = 1;
+ }
+}
+
+void
+f_columns(struct info *ip)
+{
+ ip->win.ws_col = atoi(ip->arg);
+ ip->wset = 1;
+}
+
+void
+f_dec(struct info *ip)
+{
+ ip->t.c_cc[VERASE] = (u_char)0177;
+ ip->t.c_cc[VKILL] = CTRL('u');
+ ip->t.c_cc[VINTR] = CTRL('c');
+ ip->t.c_lflag &= ~ECHOPRT;
+ ip->t.c_lflag |= ECHOE|ECHOKE|ECHOCTL;
+ ip->t.c_iflag &= ~IXANY;
+ ip->set = 1;
+}
+
+void
+f_everything(struct info *ip)
+{
+ print(&ip->t, &ip->win, ip->queue, ip->ldisc, STTY_BSD);
+}
+
+void
+f_extproc(struct info *ip)
+{
+#ifdef TIOCEXT
+ if (ip->off) {
+ int tmp = 0;
+ (void)ioctl(ip->fd, TIOCEXT, &tmp);
+ } else {
+ int tmp = 1;
+ (void)ioctl(ip->fd, TIOCEXT, &tmp);
+ }
+ ip->set = 1;
+#endif
+}
+
+void
+f_insane(struct info *ip)
+{
+ int f, r;
+
+ r = f = open(_PATH_URANDOM, O_RDONLY, 0);
+ if (f >= 0) {
+ r = read(f, &(ip->t), sizeof(struct termios));
+ close(f);
+ }
+ if (r < 0) {
+ /* XXX not cryptographically secure! */
+
+ srandom(time(NULL));
+ ip->t.c_iflag = random();
+ ip->t.c_oflag = random();
+ ip->t.c_cflag = random();
+ ip->t.c_lflag = random();
+ for (f = 0; f < NCCS; f++) {
+ ip->t.c_cc[f] = random() & 0xFF;
+ }
+ ip->t.c_ispeed = random();
+ ip->t.c_ospeed = random();
+ }
+
+ ip->set = 1;
+}
+
+void
+f_ispeed(struct info *ip)
+{
+ cfsetispeed(&ip->t, atoi(ip->arg));
+ ip->set = 1;
+}
+
+void
+f_nl(struct info *ip)
+{
+ if (ip->off) {
+ ip->t.c_iflag |= ICRNL;
+ ip->t.c_oflag |= ONLCR;
+ } else {
+ ip->t.c_iflag &= ~ICRNL;
+ ip->t.c_oflag &= ~ONLCR;
+ }
+ ip->set = 1;
+}
+
+void
+f_ospeed(struct info *ip)
+{
+ cfsetospeed(&ip->t, atoi(ip->arg));
+ ip->set = 1;
+}
+
+void
+f_raw(struct info *ip)
+{
+ if (ip->off)
+ f_sane(ip);
+ else {
+ cfmakeraw(&ip->t);
+ ip->t.c_cflag &= ~(CSIZE|PARENB);
+ ip->t.c_cflag |= CS8;
+ ip->set = 1;
+ }
+}
+
+void
+f_rows(struct info *ip)
+{
+ ip->win.ws_row = atoi(ip->arg);
+ ip->wset = 1;
+}
+
+void
+f_sane(struct info *ip)
+{
+ ip->t.c_cflag = TTYDEF_CFLAG | (ip->t.c_cflag & (CLOCAL|CRTSCTS|CDTRCTS));
+ ip->t.c_iflag = TTYDEF_IFLAG;
+ ip->t.c_iflag |= ICRNL;
+ /* preserve user-preference flags in lflag */
+#define LKEEP (ECHOKE|ECHOE|ECHOK|ECHOPRT|ECHOCTL|ALTWERASE|TOSTOP|NOFLSH)
+ ip->t.c_lflag = TTYDEF_LFLAG | (ip->t.c_lflag & LKEEP);
+ ip->t.c_oflag = TTYDEF_OFLAG;
+ ip->set = 1;
+}
+
+void
+f_size(struct info *ip)
+{
+ (void)printf("%d %d\n", ip->win.ws_row, ip->win.ws_col);
+}
+
+void
+f_speed(struct info *ip)
+{
+ (void)printf("%d\n", cfgetospeed(&ip->t));
+}
+
+/* ARGSUSED */
+void
+f_tty(struct info *ip)
+{
+#ifdef TTYDISC
+ int tmp;
+
+ tmp = TTYDISC;
+ if (ioctl(0, TIOCSETD, &tmp) < 0)
+ err(1, "TIOCSETD");
+#endif
+}
+
+void
+f_ostart(struct info *ip)
+{
+ if (ioctl (0, TIOCSTART) < 0)
+ err(1, "TIOCSTART");
+}
+
+void
+f_ostop(struct info *ip)
+{
+ if (ioctl (0, TIOCSTOP) < 0)
+ err(1, "TIOCSTOP");
+}
diff --git a/bin/stty/modes.c b/bin/stty/modes.c
new file mode 100644
index 0000000..a7877af
--- /dev/null
+++ b/bin/stty/modes.c
@@ -0,0 +1,224 @@
+/* $NetBSD: modes.c,v 1.18 2015/05/01 17:01:08 christos Exp $ */
+
+/*-
+ * Copyright (c) 1991, 1993, 1994
+ * The Regents of the University of California. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+#ifndef lint
+#if 0
+static char sccsid[] = "@(#)modes.c 8.3 (Berkeley) 4/2/94";
+#else
+__RCSID("$NetBSD: modes.c,v 1.18 2015/05/01 17:01:08 christos Exp $");
+#endif
+#endif /* not lint */
+
+#include <sys/types.h>
+
+#include <stddef.h>
+#include <string.h>
+#include <stdbool.h>
+
+#include "stty.h"
+#include "extern.h"
+
+struct modes {
+ const char *name;
+ tcflag_t flag;
+};
+
+struct specialmodes {
+ const char *name;
+ tcflag_t set;
+ tcflag_t unset;
+};
+
+/*
+ * The code in optlist() depends on minus options following regular
+ * options, i.e. "foo" must immediately precede "-foo".
+ */
+const struct modes cmodes[] = {
+ { "cstopb", CSTOPB },
+ { "cread", CREAD },
+ { "parenb", PARENB },
+ { "parodd", PARODD },
+ { "hupcl", HUPCL },
+ { "hup", HUPCL },
+ { "clocal", CLOCAL },
+ { "crtscts", CRTSCTS },
+ { "mdmbuf", MDMBUF },
+ { "cdtrcts", CDTRCTS },
+ { .name = NULL },
+};
+
+const struct specialmodes cspecialmodes[] = {
+ { "cs5", CS5, CSIZE },
+ { "cs6", CS6, CSIZE },
+ { "cs7", CS7, CSIZE },
+ { "cs8", CS8, CSIZE },
+ { "parity", PARENB | CS7, PARODD | CSIZE },
+ { "-parity", CS8, PARODD | PARENB | CSIZE },
+ { "evenp", PARENB | CS7, PARODD | CSIZE },
+ { "-evenp", CS8, PARODD | PARENB | CSIZE },
+ { "oddp", PARENB | CS7 | PARODD, CSIZE },
+ { "-oddp", CS8, PARODD | PARENB | CSIZE },
+ { "pass8", CS8, PARODD | PARENB | CSIZE },
+ { "-pass8", PARENB | CS7, PARODD | CSIZE },
+ { .name = NULL },
+};
+
+const struct modes imodes[] = {
+ { "ignbrk", IGNBRK },
+ { "brkint", BRKINT },
+ { "ignpar", IGNPAR },
+ { "parmrk", PARMRK },
+ { "inpck", INPCK },
+ { "istrip", ISTRIP },
+ { "inlcr", INLCR },
+ { "igncr", IGNCR },
+ { "icrnl", ICRNL },
+ { "ixon", IXON },
+ { "flow", IXON },
+ { "ixoff", IXOFF },
+ { "tandem", IXOFF },
+ { "ixany", IXANY },
+ { "imaxbel", IMAXBEL },
+ { .name = NULL },
+};
+
+const struct specialmodes ispecialmodes[] = {
+ { "decctlq", 0, IXANY },
+ { "-decctlq", IXANY, 0 },
+ { .name = NULL },
+};
+
+const struct modes lmodes[] = {
+ { "echo", ECHO },
+ { "echoe", ECHOE },
+ { "crterase", ECHOE },
+ { "crtbs", ECHOE }, /* crtbs not supported, close enough */
+ { "echok", ECHOK },
+ { "echoke", ECHOKE },
+ { "crtkill", ECHOKE },
+ { "altwerase", ALTWERASE },
+ { "iexten", IEXTEN },
+ { "echonl", ECHONL },
+ { "echoctl", ECHOCTL },
+ { "ctlecho", ECHOCTL },
+ { "echoprt", ECHOPRT },
+ { "prterase", ECHOPRT },
+ { "isig", ISIG },
+ { "icanon", ICANON },
+ { "noflsh", NOFLSH },
+ { "tostop", TOSTOP },
+ { "flusho", FLUSHO },
+ { "pendin", PENDIN },
+ { "nokerninfo", NOKERNINFO },
+ { .name = NULL },
+};
+
+const struct specialmodes lspecialmodes[] = {
+ { "crt", ECHOE|ECHOKE|ECHOCTL, ECHOK|ECHOPRT },
+ { "-crt", ECHOK, ECHOE|ECHOKE|ECHOCTL },
+ { "newcrt", ECHOE|ECHOKE|ECHOCTL, ECHOK|ECHOPRT },
+ { "-newcrt", ECHOK, ECHOE|ECHOKE|ECHOCTL },
+ { "kerninfo", 0, NOKERNINFO },
+ { "-kerninfo", NOKERNINFO, 0 },
+ { .name = NULL },
+};
+
+const struct modes omodes[] = {
+ { "opost", OPOST },
+ { "onlcr", ONLCR },
+ { "ocrnl", OCRNL },
+ { "oxtabs", OXTABS },
+ { "onocr", ONOCR },
+ { "onlret", ONLRET },
+ { .name = NULL },
+};
+
+const struct specialmodes ospecialmodes[] = {
+ { "litout", 0, OPOST },
+ { "-litout", OPOST, 0 },
+ { "tabs", 0, OXTABS }, /* "preserve" tabs */
+ { "-tabs", OXTABS, 0 },
+ { .name = NULL },
+};
+
+#define CHK(s) (!strcmp(name, s))
+
+static int
+modeset(const char *name, const struct modes *mp,
+ const struct specialmodes *smp, tcflag_t *f)
+{
+ bool neg;
+
+ for (; smp->name; ++smp)
+ if (CHK(smp->name)) {
+ *f &= ~smp->unset;
+ *f |= smp->set;
+ return 1;
+ }
+
+ if ((neg = (*name == '-')))
+ name++;
+
+ for (; mp->name; ++mp)
+ if (CHK(mp->name)) {
+ if (neg)
+ *f &= ~mp->flag;
+ else
+ *f |= mp->flag;
+ return 1;
+ }
+
+ return 0;
+}
+
+int
+msearch(char ***argvp, struct info *ip)
+{
+ const char *name = **argvp;
+
+ if (modeset(name, cmodes, cspecialmodes, &ip->t.c_cflag))
+ goto out;
+
+ if (modeset(name, imodes, ispecialmodes, &ip->t.c_iflag))
+ goto out;
+
+ if (modeset(name, lmodes, lspecialmodes, &ip->t.c_lflag))
+ goto out;
+
+ if (modeset(name, omodes, ospecialmodes, &ip->t.c_oflag))
+ goto out;
+
+ return 0;
+out:
+ ip->set = 1;
+ return 1;
+}
diff --git a/bin/stty/print.c b/bin/stty/print.c
new file mode 100644
index 0000000..6f2de1a
--- /dev/null
+++ b/bin/stty/print.c
@@ -0,0 +1,257 @@
+/* $NetBSD: print.c,v 1.23 2013/09/12 19:47:23 christos Exp $ */
+
+/*-
+ * Copyright (c) 1991, 1993, 1994
+ * The Regents of the University of California. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+#ifndef lint
+#if 0
+static char sccsid[] = "@(#)print.c 8.6 (Berkeley) 4/16/94";
+#else
+__RCSID("$NetBSD: print.c,v 1.23 2013/09/12 19:47:23 christos Exp $");
+#endif
+#endif /* not lint */
+
+#include <sys/types.h>
+
+#include <stddef.h>
+#include <stdio.h>
+#include <string.h>
+
+#include "stty.h"
+#include "extern.h"
+
+static void binit(const char *);
+static void bput(const char *);
+static const char *ccval(const struct cchar *, int);
+
+void
+print(struct termios *tp, struct winsize *wp, int queue, const char *ldisc,
+ enum FMT fmt)
+{
+ const struct cchar *p;
+ long tmp;
+ u_char *cc;
+ int cnt, ispeed, ospeed;
+ char buf1[100], buf2[100];
+
+ cnt = 0;
+
+ /* Line speed. */
+ ispeed = cfgetispeed(tp);
+ ospeed = cfgetospeed(tp);
+ if (ispeed != ospeed)
+ cnt +=
+ printf("ispeed %d baud; ospeed %d baud;", ispeed, ospeed);
+ else
+ cnt += printf("speed %d baud;", ispeed);
+ if (fmt >= STTY_BSD) {
+ cnt += printf(" %d rows; %d columns;", wp->ws_row, wp->ws_col);
+ if (queue)
+ cnt += printf(" queue = %d;", queue);
+ if (ldisc)
+ cnt += printf(" line = %s;", ldisc);
+ }
+
+ if (cnt)
+ (void)printf("\n");
+
+#define on(f) ((tmp&f) != 0)
+#define put(n, f, d) \
+ if (fmt >= STTY_BSD || on(f) != d) \
+ bput(n + on(f));
+
+ /* "local" flags */
+ tmp = tp->c_lflag;
+ binit("lflags");
+ put("-icanon", ICANON, 1);
+ put("-isig", ISIG, 1);
+ put("-iexten", IEXTEN, 1);
+ put("-echo", ECHO, 1);
+ put("-echoe", ECHOE, 0);
+ put("-echok", ECHOK, 0);
+ put("-echoke", ECHOKE, 0);
+ put("-echonl", ECHONL, 0);
+ put("-echoctl", ECHOCTL, 0);
+ put("-echoprt", ECHOPRT, 0);
+ put("-altwerase", ALTWERASE, 0);
+ put("-noflsh", NOFLSH, 0);
+ put("-tostop", TOSTOP, 0);
+ put("-flusho", FLUSHO, 0);
+ put("-pendin", PENDIN, 0);
+ put("-nokerninfo", NOKERNINFO, 0);
+ put("-extproc", EXTPROC, 0);
+
+ /* input flags */
+ tmp = tp->c_iflag;
+ binit("iflags");
+ put("-istrip", ISTRIP, 0);
+ put("-icrnl", ICRNL, 1);
+ put("-inlcr", INLCR, 0);
+ put("-igncr", IGNCR, 0);
+ put("-ixon", IXON, 1);
+ put("-ixoff", IXOFF, 0);
+ put("-ixany", IXANY, 1);
+ put("-imaxbel", IMAXBEL, 1);
+ put("-ignbrk", IGNBRK, 0);
+ put("-brkint", BRKINT, 1);
+ put("-inpck", INPCK, 0);
+ put("-ignpar", IGNPAR, 0);
+ put("-parmrk", PARMRK, 0);
+
+ /* output flags */
+ tmp = tp->c_oflag;
+ binit("oflags");
+ put("-opost", OPOST, 1);
+ put("-onlcr", ONLCR, 1);
+ put("-ocrnl", OCRNL, 0);
+ put("-oxtabs", OXTABS, 1);
+ put("-onocr", OXTABS, 0);
+ put("-onlret", OXTABS, 0);
+
+ /* control flags (hardware state) */
+ tmp = tp->c_cflag;
+ binit("cflags");
+ put("-cread", CREAD, 1);
+ switch(tmp&CSIZE) {
+ case CS5:
+ bput("cs5");
+ break;
+ case CS6:
+ bput("cs6");
+ break;
+ case CS7:
+ bput("cs7");
+ break;
+ case CS8:
+ bput("cs8");
+ break;
+ }
+ bput("-parenb" + on(PARENB));
+ put("-parodd", PARODD, 0);
+ put("-hupcl", HUPCL, 1);
+ put("-clocal", CLOCAL, 0);
+ put("-cstopb", CSTOPB, 0);
+ put("-crtscts", CRTSCTS, 0);
+ put("-mdmbuf", MDMBUF, 0);
+ put("-cdtrcts", CDTRCTS, 0);
+
+ /* special control characters */
+ cc = tp->c_cc;
+ if (fmt == STTY_POSIX) {
+ binit("cchars");
+ for (p = cchars1; p->name; ++p) {
+ (void)snprintf(buf1, sizeof(buf1), "%s = %s;",
+ p->name, ccval(p, cc[p->sub]));
+ bput(buf1);
+ }
+ binit(NULL);
+ } else {
+ binit(NULL);
+ for (p = cchars1, cnt = 0; p->name; ++p) {
+ if (fmt != STTY_BSD && cc[p->sub] == p->def)
+ continue;
+#define WD "%-8s"
+ (void)snprintf(buf1 + cnt * 8, 9, WD, p->name);
+ (void)snprintf(buf2 + cnt * 8, 9, WD, ccval(p, cc[p->sub]));
+ if (++cnt == LINELENGTH / 8) {
+ cnt = 0;
+ (void)printf("%s\n", buf1);
+ (void)printf("%s\n", buf2);
+ }
+ }
+ if (cnt) {
+ (void)printf("%s\n", buf1);
+ (void)printf("%s\n", buf2);
+ }
+ }
+}
+
+static int col;
+static const char *label;
+
+static void
+binit(const char *lb)
+{
+
+ if (col) {
+ (void)printf("\n");
+ col = 0;
+ }
+ label = lb;
+}
+
+static void
+bput(const char *s)
+{
+
+ if (col == 0) {
+ col = printf("%s: %s", label, s);
+ return;
+ }
+ if ((col + strlen(s)) > LINELENGTH) {
+ (void)printf("\n\t");
+ col = printf("%s", s) + 8;
+ return;
+ }
+ col += printf(" %s", s);
+}
+
+static const char *
+ccval(const struct cchar *p, int c)
+{
+ static char buf[5];
+ char *bp;
+
+ if (c == _POSIX_VDISABLE)
+ return ("<undef>");
+
+ if (p->sub == VMIN || p->sub == VTIME) {
+ (void)snprintf(buf, sizeof(buf), "%d", c);
+ return (buf);
+ }
+ bp = buf;
+ if (c & 0200) {
+ *bp++ = 'M';
+ *bp++ = '-';
+ c &= 0177;
+ }
+ if (c == 0177) {
+ *bp++ = '^';
+ *bp++ = '?';
+ }
+ else if (c < 040) {
+ *bp++ = '^';
+ *bp++ = c + '@';
+ }
+ else
+ *bp++ = c;
+ *bp = '\0';
+ return (buf);
+}
diff --git a/bin/stty/stty.1 b/bin/stty/stty.1
new file mode 100644
index 0000000..8901f61
--- /dev/null
+++ b/bin/stty/stty.1
@@ -0,0 +1,635 @@
+.\" $NetBSD: stty.1,v 1.45 2017/10/30 15:38:52 wiz Exp $
+.\"
+.\" Copyright (c) 1990, 1993, 1994
+.\" The Regents of the University of California. All rights reserved.
+.\"
+.\" This code is derived from software contributed to Berkeley by
+.\" the Institute of Electrical and Electronics Engineers, Inc.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\" notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must reproduce the above copyright
+.\" notice, this list of conditions and the following disclaimer in the
+.\" documentation and/or other materials provided with the distribution.
+.\" 3. Neither the name of the University nor the names of its contributors
+.\" may be used to endorse or promote products derived from this software
+.\" without specific prior written permission.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+.\" ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+.\" SUCH DAMAGE.
+.\"
+.\" @(#)stty.1 8.5 (Berkeley) 6/1/94
+.\"
+.Dd August 15, 2016
+.Dt STTY 1
+.Os
+.Sh NAME
+.Nm stty
+.Nd set options for a terminal device interface
+.Sh SYNOPSIS
+.Nm
+.Op Fl a | Fl e | Fl g
+.Op Fl f Ar file
+.Op operand ...
+.Sh DESCRIPTION
+The
+.Nm
+utility sets or reports on terminal
+characteristics for the device that is its standard input.
+If no options or operands are specified, it reports the settings of a subset
+of characteristics as well as additional ones if they differ from their
+default values.
+Otherwise it modifies
+the terminal state according to the specified arguments.
+Some combinations of arguments are mutually
+exclusive on some terminal types.
+.Pp
+The following options are available:
+.Bl -tag -width XfXfileXX
+.It Fl a
+Display all the current settings for the terminal to standard output
+as per
+.St -p1003.2 .
+.It Fl e
+Display all the current settings for the terminal to standard output
+in the traditional
+.Bx
+.Dq all
+and
+.Dq everything
+formats.
+.It Fl f Ar file
+Open and use the terminal named by
+.Ar file
+rather than using standard input.
+The file is opened using the
+.Dv O_NONBLOCK
+flag of
+.Fn open ,
+making it possible to
+set or display settings on a terminal that might otherwise
+block on the open.
+.It Fl g
+Display all the current settings for the terminal to standard output
+in a form that may be used as an argument to a subsequent invocation of
+.Nm
+to restore the current terminal state as per
+.St -p1003.2 .
+.El
+.Pp
+The following arguments are available to set the terminal
+characteristics:
+.Ss Control Modes
+Control mode flags affect hardware characteristics associated with the
+terminal.
+This corresponds to the
+.Fa c_cflag
+of the
+.Xr termios 4
+structure.
+.Bl -tag -width Fl
+.It Cm parenb Pq Fl parenb
+Enable (disable) parity generation
+and detection.
+.It Cm parodd Pq Fl parodd
+Select odd (even) parity.
+.It Cm cs5 cs6 cs7 cs8
+Select character size, if possible.
+.It Ar number
+Set terminal baud rate to
+.Ar number ,
+if possible.
+If the
+baud rate is set to zero, modem
+control is no longer
+asserted.
+.It Cm ispeed Ar number
+Set terminal input baud rate to
+.Ar number ,
+if possible.
+If the
+input baud rate is set to zero, the
+input baud rate is set to the
+value of the output baud
+rate.
+.It Cm ospeed Ar number
+Set terminal output baud rate to
+.Ar number ,
+if possible.
+If
+the output baud rate is set to
+zero, modem control is
+no longer asserted.
+.It Cm speed Ar number
+This sets both
+.Cm ispeed
+and
+.Cm ospeed
+to
+.Ar number .
+.It Cm hupcl Pq Fl hupcl
+Stop asserting modem control
+(do not stop asserting modem control) on last close.
+.It Cm hup Pq Fl hup
+Same as hupcl
+.Pq Fl hupcl .
+.It Cm cstopb Pq Fl cstopb
+Use two (one) stop bits per character.
+.It Cm cread Pq Fl cread
+Enable (disable) the receiver.
+.It Cm clocal Pq Fl clocal
+Assume a line without (with) modem
+control.
+.It Cm crtscts Pq Fl crtscts
+Enable RTS/CTS flow control.
+.It Cm cdtrcts Pq Fl cdtrcts
+Enable DTR/CTS flow control (if supported).
+.El
+.Ss Input Modes
+This corresponds to the
+.Fa c_iflag
+of the
+.Xr termios 4
+structure.
+.Bl -tag -width Fl
+.It Cm ignbrk Pq Fl ignbrk
+Ignore (do not ignore) break on
+input.
+.It Cm brkint Pq Fl brkint
+Signal (do not signal)
+.Dv INTR
+on
+break.
+.It Cm ignpar Pq Fl ignpar
+Ignore (do not ignore) parity
+errors.
+.It Cm parmrk Pq Fl parmrk
+Mark (do not mark) parity errors.
+.It Cm inpck Pq Fl inpck
+Enable (disable) input parity
+checking.
+.It Cm istrip Pq Fl istrip
+Strip (do not strip) input characters
+to seven bits.
+.It Cm inlcr Pq Fl inlcr
+Map (do not map)
+.Dv NL
+to
+.Dv CR
+on input.
+.It Cm igncr Pq Fl igncr
+Ignore (do not ignore)
+.Dv CR
+on input.
+.It Cm icrnl Pq Fl icrnl
+Map (do not map)
+.Dv CR
+to
+.Dv NL
+on input.
+.It Cm ixon Pq Fl ixon
+Enable (disable)
+.Dv START/STOP
+output
+control.
+Output from the system is
+stopped when the system receives
+.Dv STOP
+and started when the system
+receives
+.Dv START ,
+or if
+.Cm ixany
+is set, any character restarts output.
+.It Cm ixoff Pq Fl ixoff
+Request that the system send (not
+send)
+.Dv START/STOP
+characters when
+the input queue is nearly
+empty/full.
+.It Cm ixany Pq Fl ixany
+Allow any character (allow only
+.Dv START )
+to restart output.
+.It Cm imaxbel Pq Fl imaxbel
+The system imposes a limit of
+.Dv MAX_INPUT
+(currently 255) characters in the input queue.
+If
+.Cm imaxbel
+is set and the input queue limit has been reached,
+subsequent input causes the system to send an ASCII BEL
+character to the output queue (the terminal beeps at you).
+Otherwise,
+if
+.Cm imaxbel
+is unset and the input queue is full, the next input character causes
+the entire input and output queues to be discarded.
+.El
+.Ss Output Modes
+This corresponds to the
+.Fa c_oflag
+of the
+.Xr termios 4
+structure.
+.Bl -tag -width Fl
+.It Cm opost Pq Fl opost
+Post-process output (do not
+post-process output; ignore all other
+output modes).
+.It Cm onlcr Pq Fl onlcr
+Map (do not map)
+.Dv NL
+to
+.Dv CR-NL
+on output.
+.It Cm ocrnl Pq Fl ocrnl
+Map (do not map)
+.Dv CR
+to
+.Dv NL
+on output.
+.It Cm oxtabs Pq Fl oxtabs
+Expand (do not expand) tabs to spaces on output.
+.It Cm onocr Pq Fl onocr
+Do not (do) output CRs at column zero.
+.It Cm onlret Pq Fl onlret
+On the terminal NL performs (does not perform) the CR function.
+.El
+.Ss Local Modes
+Local mode flags (lflags) affect various and sundry characteristics of terminal
+processing.
+Historically the term "local" pertained to new job control features
+implemented by Jim Kulp on a PDP-11/70 at IIASA.
+Later the driver ran on the first VAX at Evans Hall, UC Berkeley,
+where the job control details were greatly modified but the structure
+definitions and names remained essentially unchanged.
+The second interpretation of the
+.Sq l
+in lflag
+is
+.Dq line discipline flag ,
+which corresponds to the
+.Fa c_lflag
+of the
+.Xr termios 4
+structure.
+.Bl -tag -width Fl
+.It Cm isig Pq Fl isig
+Enable (disable) the checking of
+characters against the special control
+characters
+.Dv INTR , QUIT ,
+and
+.Dv SUSP .
+.It Cm icanon Pq Fl icanon
+Enable (disable) canonical input
+.Dv ( ERASE
+and
+.Dv KILL
+processing).
+.It Cm iexten Pq Fl iexten
+Enable (disable) any implementation
+defined special control characters
+not currently controlled by icanon,
+isig, or ixon.
+.It Cm echo Pq Fl echo
+Echo back (do not echo back) every
+character typed.
+.It Cm echoe Pq Fl echoe
+The
+.Dv ERASE
+character shall (shall
+not) visually erase the last character
+in the current line from the
+display, if possible.
+.It Cm echok Pq Fl echok
+Echo (do not echo)
+.Dv NL
+after
+.Dv KILL
+character.
+.It Cm echoke Pq Fl echoke
+The
+.Dv KILL
+character shall (shall
+not) visually erase
+the current line from the
+display, if possible.
+.It Cm echonl Pq Fl echonl
+Echo (do not echo)
+.Dv NL ,
+even if echo
+is disabled.
+.It Cm echoctl Pq Fl echoctl
+If
+.Cm echoctl
+is set, echo control characters as ^X.
+Otherwise control characters echo as themselves.
+.It Cm echoprt Pq Fl echoprt
+For printing terminals.
+If set, echo erased characters backwards within
+.Dq \e
+and
+.Dq / .
+Otherwise, disable this feature.
+.It Cm noflsh Pq Fl noflsh
+Disable (enable) flush after
+.Dv INTR , QUIT , SUSP .
+.It Cm tostop Pq Fl tostop
+Send (do not send)
+.Dv SIGTTOU
+for background output.
+This causes background jobs to stop if they attempt terminal output.
+.It Cm altwerase Pq Fl altwerase
+Use (do not use) an alternative word erase algorithm when processing
+.Dv WERASE
+characters.
+This alternative algorithm considers sequences of
+alphanumeric/underscores as words.
+It also skips the first preceding character in its classification
+(as a convenience since the one preceding character could have been
+erased with simply an
+.Dv ERASE
+character).
+.It Cm mdmbuf Pq Fl mdmbuf
+If set, flow control output based on condition of Carrier Detect.
+Otherwise writes return an error if Carrier Detect is low (and Carrier
+is not being ignored with the
+.Dv CLOCAL
+flag).
+.It Cm flusho Pq Fl flusho
+Indicates output is (is not) being discarded.
+.It Cm pendin Pq Fl pendin
+Indicates input is (is not) pending after a switch from non-canonical
+to canonical mode and will be re-input when a read becomes pending
+or more input arrives.
+.El
+.Ss Control Characters
+.Bl -tag -width Fl
+.It Ar control-character Ar string
+Set
+.Ar control-character
+to string
+.Ar string .
+If the string is a single character,
+then the control character is set to
+that character.
+If the string is the
+two character sequence "^-" or the
+string "undef", then the control character
+is disabled (i.e., set to
+.Bro Dv _POSIX_VDISABLE Brc ) .
+.Pp
+Recognized control characters:
+.Bd -ragged -offset indent
+.Bl -column character Subscript Description
+.It control- Ta "" Ta ""
+.It character Subscript Description
+.It _________ _________ _______________
+.It eof Ta VEOF Ta EOF No character
+.It eol Ta VEOL Ta EOL No character
+.It eol2 Ta VEOL2 Ta EOL2 No character
+.It erase Ta VERASE Ta ERASE No character
+.It werase Ta VWERASE Ta WERASE No character
+.It kill Ta VKILL Ta KILL No character
+.It reprint Ta VREPRINT Ta REPRINT No character
+.It intr Ta VINTR Ta INTR No character
+.It quit Ta VQUIT Ta QUIT No character
+.It susp Ta VSUSP Ta SUSP No character
+.It dsusp Ta VDSUSP Ta DSUSP No character
+.It start Ta VSTART Ta START No character
+.It stop Ta VSTOP Ta STOP No character
+.It lnext Ta VLNEXT Ta LNEXT No character
+.It status Ta VSTATUS Ta STATUS No character
+.It discard Ta VDISCARD Ta DISCARD No character
+.El
+.Ed
+.It Cm min Ar number
+.It Cm time Ar number
+Set the value of min or time to
+.Ar number .
+.Dv MIN
+and
+.Dv TIME
+are used in
+Non-Canonical mode input processing
+(-icanon).
+.El
+.Ss Combination Modes
+.Bl -tag -width Fl
+.It Ar saved settings
+Set the current terminal characteristics to the saved settings
+produced by the
+.Fl g
+option.
+.It Cm evenp No or Cm parity
+Enable parenb and cs7; disable parodd.
+.It Cm oddp
+Enable parenb, cs7, and parodd.
+.It Fl parity , evenp , oddp
+Disable parenb, and set cs8.
+.It Cm \&nl Pq Fl \&nl
+Enable (disable) icrnl.
+In addition
+-nl unsets inlcr and igncr.
+.It Cm ek
+Reset
+.Dv ERASE
+and
+.Dv KILL
+characters back to system defaults.
+.It Cm sane
+Resets all modes to reasonable values for interactive terminal use.
+.It Cm insane
+Sets all modes to random values, which are very likely
+.Pq but not guaranteed
+to be unreasonable for interactive terminal use.
+.It Cm tty
+Set the line discipline to the standard terminal line discipline
+.Dv TTYDISC .
+.It Cm crt Pq Fl crt
+Set (disable) all modes suitable for a CRT display device.
+.It Cm kerninfo Pq Fl kerninfo
+Enable (disable) the system generated status line associated with
+processing a
+.Dv STATUS
+character (usually set to ^T).
+The status line consists of the
+system load average, the current command name, its process ID, the
+event the process is waiting on (or the status of the process), the user
+and system times, percent CPU, and current memory usage.
+.It Cm cols Ar number
+The terminal size is recorded as having
+.Ar number
+columns.
+.It Cm columns Ar number
+An alias for
+.Cm cols .
+.It Cm rows Ar number
+The terminal size is recorded as having
+.Ar number
+rows.
+.It Cm dec
+Set modes suitable for users of Digital Equipment Corporation systems
+.Dv ( ERASE ,
+.Dv KILL ,
+and
+.Dv INTR
+characters are set to ^?, ^U, and ^C;
+.Dv ixany
+is disabled, and
+.Dv crt
+is enabled).
+.It Cm extproc Pq Fl extproc
+If set, this flag indicates that some amount of terminal processing is being
+performed by either the terminal hardware or by the remote side connected
+to a pty.
+.It Cm raw Pq Fl raw
+If set, change the modes of the terminal so that no input or output processing
+is performed.
+If unset, change the modes of the terminal to some reasonable
+state that performs input and output processing.
+Note that since the terminal driver no longer has a single
+.Dv RAW
+bit, it is not possible to intuit what flags were set prior to setting
+.Cm raw .
+This means that unsetting
+.Cm raw
+may not put back all the setting that were previously in effect.
+To set the terminal into a raw state and then accurately restore it, the following
+shell code is recommended:
+.Bd -literal -offset indent
+save_state=$(stty -g)
+stty raw
+\&...
+stty "$save_state"
+.Ed
+.It Cm size
+The size of the terminal is printed as two numbers on a single line,
+first rows, then columns.
+.El
+.Ss Compatibility Modes
+These modes remain for compatibility with the previous version of
+the
+.Nm
+utility.
+.Bl -tag -width Fl
+.It Cm all
+Reports all the terminal modes as with
+.Cm stty Fl a
+except that the control characters are printed in a columnar format.
+.It Cm everything
+Same as
+.Cm all .
+.It Cm cooked
+Same as
+.Cm sane .
+.It Cm cbreak
+If set, enables
+.Cm brkint , ixon , imaxbel , opost ,
+.Cm isig , iexten ,
+and
+.Fl icanon .
+If unset, same as
+.Cm sane .
+.It Cm new
+Same as
+.Cm tty .
+.It Cm old
+Same as
+.Cm tty .
+.It Cm newcrt Pq Fl newcrt
+Same as
+.Cm crt .
+.It Cm pass8
+The converse of
+.Cm parity .
+.It Cm tandem Pq Fl tandem
+Same as
+.Cm ixoff .
+.It Cm decctlq Pq Fl decctlq
+The converse of
+.Cm ixany .
+.It Cm crterase Pq Fl crterase
+Same as
+.Cm echoe .
+.It Cm crtbs Pq Fl crtbs
+Same as
+.Cm echoe .
+.It Cm crtkill Pq Fl crtkill
+Same as
+.Cm echoke .
+.It Cm ctlecho Pq Fl ctlecho
+Same as
+.Cm echoctl .
+.It Cm prterase Pq Fl prterase
+Same as
+.Cm echoprt .
+.It Cm litout Pq Fl litout
+The converse of
+.Cm opost .
+.It Cm tabs Pq Fl tabs
+The converse of
+.Cm oxtabs .
+.It Cm brk Ar value
+Same as the control character
+.Cm eol .
+.It Cm flush Ar value
+Same as the control character
+.Cm discard .
+.It Cm rprnt Ar value
+Same as the control character
+.Cm reprint .
+.El
+.Ss Control operations
+These operations are not modes, but rather commands to be performed by
+the tty layer.
+.Bl -tag -width Fl
+.It Cm ostart
+Performs a "start output" operation, as normally done by an
+incoming START character when
+.Cm ixon
+is set.
+.It Cm ostop
+Performs a "stop output" operation, as normally done by an
+incoming STOP character when
+.Cm ixon
+is set.
+.El
+.Sh EXIT STATUS
+.Ex -std
+.Sh SEE ALSO
+.Xr termios 4 ,
+.Xr tty 4
+.Sh STANDARDS
+The
+.Nm
+utility is expected to be
+.St -p1003.2
+compatible.
+The
+.Fl e
+and
+.Fl f
+flags are
+extensions to the standard, as are the operands mentioned in the control
+operations section.
+.Sh HISTORY
+An
+.Nm
+utility appeared in
+.At v2 .
diff --git a/bin/stty/stty.c b/bin/stty/stty.c
new file mode 100644
index 0000000..a1cbd49
--- /dev/null
+++ b/bin/stty/stty.c
@@ -0,0 +1,168 @@
+/* $NetBSD: stty.c,v 1.23 2013/09/12 19:47:23 christos Exp $ */
+
+/*-
+ * Copyright (c) 1989, 1991, 1993, 1994
+ * The Regents of the University of California. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+#ifndef lint
+__COPYRIGHT("@(#) Copyright (c) 1989, 1991, 1993, 1994\
+ The Regents of the University of California. All rights reserved.");
+#endif /* not lint */
+
+#ifndef lint
+#if 0
+static char sccsid[] = "@(#)stty.c 8.3 (Berkeley) 4/2/94";
+#else
+__RCSID("$NetBSD: stty.c,v 1.23 2013/09/12 19:47:23 christos Exp $");
+#endif
+#endif /* not lint */
+
+#include <sys/types.h>
+
+#include <ctype.h>
+#include <err.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <locale.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "stty.h"
+#include "extern.h"
+
+int
+main(int argc, char *argv[])
+{
+ struct info i;
+ enum FMT fmt;
+ int ch;
+
+ setprogname(argv[0]);
+ (void)setlocale(LC_ALL, "");
+
+ fmt = STTY_NOTSET;
+ i.fd = STDIN_FILENO;
+
+ opterr = 0;
+ while (optind < argc &&
+ strspn(argv[optind], "-aefg") == strlen(argv[optind]) &&
+ (ch = getopt(argc, argv, "aef:g")) != -1)
+ switch(ch) {
+ case 'a': /* undocumented: POSIX compatibility */
+ fmt = STTY_POSIX;
+ break;
+ case 'e':
+ fmt = STTY_BSD;
+ break;
+ case 'f':
+ if ((i.fd = open(optarg, O_RDONLY | O_NONBLOCK)) < 0)
+ err(1, "%s", optarg);
+ break;
+ case 'g':
+ fmt = STTY_GFLAG;
+ break;
+ case '?':
+ default:
+ goto args;
+ }
+
+args: argc -= optind;
+ argv += optind;
+
+ if (ioctl(i.fd, TIOCGLINED, i.ldisc) < 0)
+ err(1, "TIOCGLINED");
+ if (tcgetattr(i.fd, &i.t) < 0)
+ err(1, "tcgetattr");
+ if (ioctl(i.fd, TIOCGWINSZ, &i.win) < 0)
+ warn("TIOCGWINSZ");
+ if (ioctl(i.fd, TIOCGQSIZE, &i.queue) < 0)
+ warn("TIOCGQSIZE");
+
+ switch(fmt) {
+ case STTY_NOTSET:
+ if (*argv)
+ break;
+ /* FALLTHROUGH */
+ case STTY_BSD:
+ case STTY_POSIX:
+ print(&i.t, &i.win, i.queue, i.ldisc, fmt);
+ break;
+ case STTY_GFLAG:
+ gprint(&i.t);
+ break;
+ }
+
+ for (i.set = i.wset = 0; *argv; ++argv) {
+ if (ksearch(&argv, &i))
+ continue;
+
+ if (csearch(&argv, &i))
+ continue;
+
+ if (msearch(&argv, &i))
+ continue;
+
+ if (isdigit((unsigned char)**argv)) {
+ int speed;
+
+ speed = atoi(*argv);
+ cfsetospeed(&i.t, speed);
+ cfsetispeed(&i.t, speed);
+ i.set = 1;
+ continue;
+ }
+
+ if (!strncmp(*argv, "gfmt1", sizeof("gfmt1") - 1)) {
+ gread(&i.t, *argv + sizeof("gfmt1") - 1);
+ i.set = 1;
+ continue;
+ }
+
+ warnx("illegal option -- %s", *argv);
+ usage();
+ }
+
+ if (i.set && tcsetattr(i.fd, 0, &i.t) < 0)
+ err(1, "tcsetattr");
+ if (i.wset && ioctl(i.fd, TIOCSWINSZ, &i.win) < 0)
+ warn("TIOCSWINSZ");
+ exit(0);
+ /* NOTREACHED */
+}
+
+void
+usage(void)
+{
+
+ (void)fprintf(stderr, "usage: %s [-a|-e|-g] [-f file] [operand ...]\n", getprogname());
+ exit(1);
+ /* NOTREACHED */
+}
diff --git a/bin/stty/stty.h b/bin/stty/stty.h
new file mode 100644
index 0000000..8b93cba
--- /dev/null
+++ b/bin/stty/stty.h
@@ -0,0 +1,62 @@
+/* $NetBSD: stty.h,v 1.11 2013/09/12 19:47:23 christos Exp $ */
+
+/*-
+ * Copyright (c) 1991, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * @(#)stty.h 8.1 (Berkeley) 5/31/93
+ */
+
+#ifndef _STTY_H_
+#define _STTY_H_
+
+#include <sys/ioctl.h>
+#include <termios.h>
+
+struct info {
+ int fd; /* file descriptor */
+ linedn_t ldisc; /* line discipline */
+ int queue; /* queue size */
+ int off; /* turn off */
+ int set; /* need set */
+ int wset; /* need window set */
+ char *arg; /* argument */
+ struct termios t; /* terminal info */
+ struct winsize win; /* window info */
+};
+
+struct cchar {
+ const char *name;
+ int sub;
+ u_char def;
+};
+
+enum FMT { STTY_NOTSET, STTY_GFLAG, STTY_BSD, STTY_POSIX };
+
+#define LINELENGTH 72
+
+#endif /* !_STTY_H_ */
diff --git a/bin/test/TEST.csh b/bin/test/TEST.csh
new file mode 100644
index 0000000..672e1a0
--- /dev/null
+++ b/bin/test/TEST.csh
@@ -0,0 +1,138 @@
+# $NetBSD: TEST.csh,v 1.2 1995/03/21 07:03:59 cgd Exp $
+# @(#)TEST.csh 5.2 (Berkeley) 4/30/93
+
+#alias t '/usr/src/bin/test/obj/test \!*; echo $status'
+alias t '/bin/test \!*; echo $status'
+
+echo 't -b /dev/ttyp2'
+t -b /dev/ttyp2
+echo 't -b /dev/jb1a'
+t -b /dev/jb1a
+
+echo 't -c test.c'
+t -c test.c
+echo 't -c /dev/tty'
+t -c /dev/tty
+
+echo 't -d test.c'
+t -d test.c
+echo 't -d /etc'
+t -d /etc
+
+echo 't -e noexist'
+t -e noexist
+echo 't -e test.c'
+t -e test.c
+
+echo 't -f noexist'
+t -f noexist
+echo 't -f /dev/tty'
+t -f /dev/tty
+echo 't -f test.c'
+t -f test.c
+
+echo 't -g test.c'
+t -g test.c
+echo 't -g /bin/ps'
+t -g /bin/ps
+
+echo 't -n ""'
+t -n ""
+echo 't -n "hello"'
+t -n "hello"
+
+echo 't -p test.c'
+t -p test.c
+
+echo 't -r noexist'
+t -r noexist
+echo 't -r /etc/master.passwd'
+t -r /etc/master.passwd
+echo 't -r test.c'
+t -r test.c
+
+echo 't -s noexist'
+t -s noexist
+echo 't -s /dev/null'
+t -s /dev/null
+echo 't -s test.c'
+t -s test.c
+
+echo 't -t 20'
+t -t 20
+echo 't -t 0'
+t -t 0
+
+echo 't -u test.c'
+t -u test.c
+echo 't -u /bin/rcp'
+t -u /bin/rcp
+
+echo 't -w noexist'
+t -w noexist
+echo 't -w /etc/master.passwd'
+t -w /etc/master.passwd
+echo 't -w /dev/null'
+t -w /dev/null
+
+echo 't -x noexist'
+t -x noexist
+echo 't -x /bin/ps'
+t -x /bin/ps
+echo 't -x /etc/motd'
+t -x /etc/motd
+
+echo 't -z ""'
+t -z ""
+echo 't -z "foo"'
+t -z "foo"
+
+echo 't "foo"'
+t "foo"
+echo 't ""'
+t ""
+
+echo 't "hello" = "hello"'
+t "hello" = "hello"
+echo 't "hello" = "goodbye"'
+t "hello" = "goodbye"
+
+echo 't "hello" != "hello"'
+t "hello" != "hello"
+echo 't "hello" != "goodbye"'
+t "hello" != "goodbye"
+
+echo 't 200 -eq 200'
+t 200 -eq 200
+echo 't 34 -eq 222'
+t 34 -eq 222
+
+echo 't 200 -ne 200'
+t 200 -ne 200
+echo 't 34 -ne 222'
+t 34 -ne 222
+
+echo 't 200 -gt 200'
+t 200 -gt 200
+echo 't 340 -gt 222'
+t 340 -gt 222
+
+echo 't 200 -ge 200'
+t 200 -ge 200
+echo 't 34 -ge 222'
+t 34 -ge 222
+
+echo 't 200 -lt 200'
+t 200 -lt 200
+echo 't 34 -lt 222'
+t 34 -lt 222
+
+echo 't 200 -le 200'
+t 200 -le 200
+echo 't 340 -le 222'
+t 340 -le 222
+
+echo 't 700 -le 1000 -a -n "1" -a "20" = "20"'
+t 700 -le 1000 -a -n "1" -a "20" = "20"
+echo 't ! \( 700 -le 1000 -a -n "1" -a "20" = "20" \)'
+t ! \( 700 -le 1000 -a -n "1" -a "20" = "20" \)
diff --git a/bin/test/test.1 b/bin/test/test.1
new file mode 100644
index 0000000..9d62515
--- /dev/null
+++ b/bin/test/test.1
@@ -0,0 +1,383 @@
+.\" $NetBSD: test.1,v 1.33 2017/10/18 18:11:54 wiz Exp $
+.\"
+.\" Copyright (c) 1991, 1993
+.\" The Regents of the University of California. All rights reserved.
+.\"
+.\" This code is derived from software contributed to Berkeley by
+.\" the Institute of Electrical and Electronics Engineers, Inc.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\" notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must reproduce the above copyright
+.\" notice, this list of conditions and the following disclaimer in the
+.\" documentation and/or other materials provided with the distribution.
+.\" 3. Neither the name of the University nor the names of its contributors
+.\" may be used to endorse or promote products derived from this software
+.\" without specific prior written permission.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+.\" ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+.\" SUCH DAMAGE.
+.\"
+.\" @(#)test.1 8.1 (Berkeley) 5/31/93
+.\"
+.Dd October 17, 2017
+.Dt TEST 1
+.Os
+.Sh NAME
+.Nm test ,
+.Nm \&[
+.Nd condition evaluation utility
+.Sh SYNOPSIS
+.Nm
+.Ar expression
+.Nm \&[
+.Ar expression Cm \&]
+.Sh DESCRIPTION
+The
+.Nm
+utility evaluates
+.Ar expression
+and, if it evaluates
+to true, returns a zero (true) exit status; otherwise
+it returns 1 (false).
+If
+.Ar expression
+is not given,
+.Nm
+also
+returns 1 (false).
+.Pp
+All operators and flags are separate arguments to the
+.Nm
+utility.
+.Pp
+The following primaries are used to construct
+.Ar expression :
+.Bl -tag -width Ar
+.It Fl b Ar file
+True if
+.Ar file
+exists and is a block special
+file.
+.It Fl c Ar file
+True if
+.Ar file
+exists and is a character
+special file.
+.It Fl d Ar file
+True if
+.Ar file
+exists and is a directory.
+.It Fl e Ar file
+True if
+.Ar file
+exists (regardless of type).
+.It Fl f Ar file
+True if
+.Ar file
+exists and is a regular file.
+.It Fl g Ar file
+True if
+.Ar file
+exists and its set group ID flag
+is set.
+.It Fl h Ar file
+True if
+.Ar file
+exists and is a symbolic link.
+.It Fl k Ar file
+True if
+.Ar file
+exists and its sticky bit is set.
+.It Fl n Ar string
+True if the length of
+.Ar string
+is nonzero.
+.It Fl p Ar file
+True if
+.Ar file
+exists and is a named pipe (FIFO).
+.It Fl r Ar file
+True if
+.Ar file
+exists and is readable.
+.It Fl s Ar file
+True if
+.Ar file
+exists and has a size greater
+than zero.
+.It Fl t Ar file_descriptor
+True if the file whose file descriptor number
+is
+.Ar file_descriptor
+is open and is associated with a terminal.
+.It Fl u Ar file
+True if
+.Ar file
+exists and its set user ID flag
+is set.
+.It Fl w Ar file
+True if
+.Ar file
+exists and is writable.
+True
+indicates only that the write flag is on.
+The file is not writable on a read-only file
+system even if this test indicates true.
+.It Fl x Ar file
+True if
+.Ar file
+exists and is executable.
+True
+indicates only that the execute flag is on.
+If
+.Ar file
+is a directory, true indicates that
+.Ar file
+can be searched.
+.It Fl z Ar string
+True if the length of
+.Ar string
+is zero.
+.It Fl L Ar file
+True if
+.Ar file
+exists and is a symbolic link.
+This operator is retained for compatibility with previous versions of
+this program.
+Do not rely on its existence; use
+.Fl h
+instead.
+.It Fl O Ar file
+True if
+.Ar file
+exists and its owner matches the effective user id of this process.
+.It Fl G Ar file
+True if
+.Ar file
+exists and its group matches the effective group id of this process.
+.It Fl S Ar file
+True if
+.Ar file
+exists and is a socket.
+.It Ar file1 Fl nt Ar file2
+True if
+.Ar file1
+exists and is newer than
+.Ar file2 .
+.It Ar file1 Fl ot Ar file2
+True if
+.Ar file1
+exists and is older than
+.Ar file2 .
+.It Ar file1 Fl ef Ar file2
+True if
+.Ar file1
+and
+.Ar file2
+exist and refer to the same file.
+.It Ar string
+True if
+.Ar string
+is not the null
+string.
+.It Ar \&s\&1 Cm \&= Ar \&s\&2
+True if the strings
+.Ar \&s\&1
+and
+.Ar \&s\&2
+are identical.
+.It Ar \&s\&1 Cm \&!= Ar \&s\&2
+True if the strings
+.Ar \&s\&1
+and
+.Ar \&s\&2
+are not identical.
+.It Ar \&s\&1 Cm \&< Ar \&s\&2
+True if string
+.Ar \&s\&1
+comes before
+.Ar \&s\&2
+based on the ASCII value of their characters.
+.It Ar \&s\&1 Cm \&> Ar \&s\&2
+True if string
+.Ar \&s\&1
+comes after
+.Ar \&s\&2
+based on the ASCII value of their characters.
+.It Ar \&n\&1 Fl \&eq Ar \&n\&2
+True if the integers
+.Ar \&n\&1
+and
+.Ar \&n\&2
+are algebraically
+equal.
+.It Ar \&n\&1 Fl \&ne Ar \&n\&2
+True if the integers
+.Ar \&n\&1
+and
+.Ar \&n\&2
+are not
+algebraically equal.
+.It Ar \&n\&1 Fl \&gt Ar \&n\&2
+True if the integer
+.Ar \&n\&1
+is algebraically
+greater than the integer
+.Ar \&n\&2 .
+.It Ar \&n\&1 Fl \&ge Ar \&n\&2
+True if the integer
+.Ar \&n\&1
+is algebraically
+greater than or equal to the integer
+.Ar \&n\&2 .
+.It Ar \&n\&1 Fl \&lt Ar \&n\&2
+True if the integer
+.Ar \&n\&1
+is algebraically less
+than the integer
+.Ar \&n\&2 .
+.It Ar \&n\&1 Fl \&le Ar \&n\&2
+True if the integer
+.Ar \&n\&1
+is algebraically less
+than or equal to the integer
+.Ar \&n\&2 .
+.El
+.Pp
+These primaries can be combined with the following operators:
+.Bl -tag -width Ar
+.It Cm \&! Ar expression
+True if
+.Ar expression
+is false.
+.It Ar expression1 Fl a Ar expression2
+True if both
+.Ar expression1
+and
+.Ar expression2
+are true.
+.It Ar expression1 Fl o Ar expression2
+True if either
+.Ar expression1
+or
+.Ar expression2
+is true.
+.It Cm \&( Ar expression Cm \&)
+True if
+.Ar expression
+is true.
+.El
+.Pp
+The
+.Fl a
+operator has higher precedence than the
+.Fl o
+operator.
+.Pp
+Note that all file tests with the exception of
+.Fl h
+and
+.Fl L
+follow symbolic links and thus evaluate the test for the file pointed at.
+.Sh EXIT STATUS
+The
+.Nm
+utility exits with one of the following values:
+.Bl -tag -width Ds
+.It 0
+.Ar expression
+evaluated to true.
+.It 1
+.Ar expression
+evaluated to false or was missing.
+.It >1
+An error occurred.
+.El
+.Sh STANDARDS
+The
+.Nm
+utility implements a superset of the
+.St -p1003.2
+specification.
+.Sh HISTORY
+A
+.Nm
+utility appeared in
+.At v7 .
+.Sh CAVEATS
+The
+.Nm
+grammar is inherently ambiguous.
+In order to assure a degree of consistency, the cases described in
+.St -p1003.2
+section 4.62.4,
+are evaluated consistently according to the rules specified in the
+standards document.
+All other cases are subject to the ambiguity in the command semantics.
+.Pp
+This means that
+.Nm
+should not be used with more than 4 operands
+(where the terminating
+.Cm \&]
+in the case of the
+.Nm \&[
+command does not count as an operand,)
+and that the obsolete
+.Fl a
+and
+.Fl o
+options should not be used.
+Instead invoke
+.Nm
+multiple times connected by the
+.Dq &&
+and
+.Dq ||
+operators from
+.Xr sh 1 .
+When those operators are not used, there is no need
+for the parentheses as grouping symbols, so those should also be
+avoided.
+Using
+.Xr sh 1 Ns 's
+.Cm \&!
+command instead of the equivalent operator from
+.Nm
+can also protect the script from future test enhancements.
+.Pp
+Most expressions with 3 or less operands will evaluate as expected,
+though be aware that with 3 operands,
+if the second is a known binary operator,
+that is always evaluated,
+regardless of what the other operands might suggest had been intended.
+If, and only if, the middle operand is not a defined binary operator
+is the first operand examined to see if it is
+.Cm \&!
+in which case the remaining operands are evaluated as a two operand test,
+and the result inverted.
+The only other defined three operand case is the meaningless
+degenerate case where parentheses (1st and 3rd operands)
+surround a one operand expression.
+.Pp
+With 4 operands there are just two defined cases, the first
+where the first operand is
+.Cm \&!
+in which case the result of the three operand test on the
+remaining operands is inverted,
+and the second is similar to the 3 operand case,
+the degenerate case of parentheses surrounding an (in this case)
+2 operand test expression.
diff --git a/bin/test/test.c b/bin/test/test.c
new file mode 100644
index 0000000..8cf9dab
--- /dev/null
+++ b/bin/test/test.c
@@ -0,0 +1,904 @@
+/* $NetBSD: test.c,v 1.43 2018/09/13 22:00:58 kre Exp $ */
+
+/*
+ * test(1); version 7-like -- author Erik Baalbergen
+ * modified by Eric Gisin to be used as built-in.
+ * modified by Arnold Robbins to add SVR3 compatibility
+ * (-x -c -b -p -u -g -k) plus Korn's -L -nt -ot -ef and new -S (socket).
+ * modified by J.T. Conklin for NetBSD.
+ *
+ * This program is in the Public Domain.
+ */
+
+#include <sys/cdefs.h>
+#ifndef lint
+__RCSID("$NetBSD: test.c,v 1.43 2018/09/13 22:00:58 kre Exp $");
+#endif
+
+#include <sys/stat.h>
+#include <sys/types.h>
+
+#include <ctype.h>
+#include <err.h>
+#include <errno.h>
+#include <limits.h>
+#include <locale.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <stdarg.h>
+
+/* test(1) accepts the following grammar:
+ oexpr ::= aexpr | aexpr "-o" oexpr ;
+ aexpr ::= nexpr | nexpr "-a" aexpr ;
+ nexpr ::= primary | "!" primary
+ primary ::= unary-operator operand
+ | operand binary-operator operand
+ | operand
+ | "(" oexpr ")"
+ ;
+ unary-operator ::= "-r"|"-w"|"-x"|"-f"|"-d"|"-c"|"-b"|"-p"|
+ "-u"|"-g"|"-k"|"-s"|"-t"|"-z"|"-n"|"-o"|"-O"|"-G"|"-L"|"-S";
+
+ binary-operator ::= "="|"!="|"-eq"|"-ne"|"-ge"|"-gt"|"-le"|"-lt"|
+ "-nt"|"-ot"|"-ef";
+ operand ::= <any legal UNIX file name>
+*/
+
+enum token {
+ EOI,
+ FILRD,
+ FILWR,
+ FILEX,
+ FILEXIST,
+ FILREG,
+ FILDIR,
+ FILCDEV,
+ FILBDEV,
+ FILFIFO,
+ FILSOCK,
+ FILSYM,
+ FILGZ,
+ FILTT,
+ FILSUID,
+ FILSGID,
+ FILSTCK,
+ FILNT,
+ FILOT,
+ FILEQ,
+ FILUID,
+ FILGID,
+ STREZ,
+ STRNZ,
+ STREQ,
+ STRNE,
+ STRLT,
+ STRGT,
+ INTEQ,
+ INTNE,
+ INTGE,
+ INTGT,
+ INTLE,
+ INTLT,
+ UNOT,
+ BAND,
+ BOR,
+ LPAREN,
+ RPAREN,
+ OPERAND
+};
+
+enum token_types {
+ UNOP,
+ BINOP
+#ifndef SMALL
+ ,
+ BUNOP,
+ BBINOP,
+ PAREN
+#endif
+};
+
+struct t_op {
+ const char *op_text;
+ short op_num, op_type;
+};
+
+static const struct t_op cop[] = {
+#ifndef SMALL
+ {"!", UNOT, BUNOP},
+ {"(", LPAREN, PAREN},
+ {")", RPAREN, PAREN},
+#endif
+ {"<", STRLT, BINOP},
+ {"=", STREQ, BINOP},
+ {">", STRGT, BINOP},
+};
+
+static const struct t_op cop2[] = {
+ {"!=", STRNE, BINOP},
+};
+
+static const struct t_op mop3[] = {
+ {"ef", FILEQ, BINOP},
+ {"eq", INTEQ, BINOP},
+ {"ge", INTGE, BINOP},
+ {"gt", INTGT, BINOP},
+ {"le", INTLE, BINOP},
+ {"lt", INTLT, BINOP},
+ {"ne", INTNE, BINOP},
+ {"nt", FILNT, BINOP},
+ {"ot", FILOT, BINOP},
+};
+
+static const struct t_op mop2[] = {
+ {"G", FILGID, UNOP},
+ {"L", FILSYM, UNOP},
+ {"O", FILUID, UNOP},
+ {"S", FILSOCK,UNOP},
+#ifndef SMALL
+ {"a", BAND, BBINOP},
+#endif
+ {"b", FILBDEV,UNOP},
+ {"c", FILCDEV,UNOP},
+ {"d", FILDIR, UNOP},
+ {"e", FILEXIST,UNOP},
+ {"f", FILREG, UNOP},
+ {"g", FILSGID,UNOP},
+ {"h", FILSYM, UNOP}, /* for backwards compat */
+ {"k", FILSTCK,UNOP},
+ {"n", STRNZ, UNOP},
+#ifndef SMALL
+ {"o", BOR, BBINOP},
+#endif
+ {"p", FILFIFO,UNOP},
+ {"r", FILRD, UNOP},
+ {"s", FILGZ, UNOP},
+ {"t", FILTT, UNOP},
+ {"u", FILSUID,UNOP},
+ {"w", FILWR, UNOP},
+ {"x", FILEX, UNOP},
+ {"z", STREZ, UNOP},
+};
+
+#ifndef SMALL
+static char **t_wp;
+static struct t_op const *t_wp_op;
+#endif
+
+#ifndef SMALL
+__dead static void syntax(const char *, const char *);
+static int oexpr(enum token);
+static int aexpr(enum token);
+static int nexpr(enum token);
+static int primary(enum token);
+static int binop(void);
+static enum token t_lex(char *);
+static int isoperand(void);
+#endif
+static struct t_op const *findop(const char *);
+static int perform_unop(enum token, const char *);
+static int perform_binop(enum token, const char *, const char *);
+static int test_access(struct stat *, mode_t);
+static int filstat(const char *, enum token);
+static long long getn(const char *);
+static int newerf(const char *, const char *);
+static int olderf(const char *, const char *);
+static int equalf(const char *, const char *);
+
+static int one_arg(const char *);
+static int two_arg(const char *, const char *);
+static int three_arg(const char *, const char *, const char *);
+static int four_arg(const char *, const char *, const char *, const char *);
+
+#if defined(SHELL)
+extern void error(const char *, ...) __dead __printflike(1, 2);
+extern void *ckmalloc(size_t);
+#else
+static void error(const char *, ...) __dead __printflike(1, 2);
+
+static void
+error(const char *msg, ...)
+{
+ va_list ap;
+
+ va_start(ap, msg);
+ verrx(2, msg, ap);
+ /*NOTREACHED*/
+ va_end(ap);
+}
+
+static void *ckmalloc(size_t);
+static void *
+ckmalloc(size_t nbytes)
+{
+ void *p = malloc(nbytes);
+
+ if (!p)
+ error("Not enough memory!");
+ return p;
+}
+#endif
+
+#ifdef SHELL
+int testcmd(int, char **);
+
+int
+testcmd(int argc, char **argv)
+#else
+int
+main(int argc, char *argv[])
+#endif
+{
+ int res;
+ const char *argv0;
+
+#ifdef SHELL
+ argv0 = argv[0];
+#else
+ setprogname(argv[0]);
+ (void)setlocale(LC_ALL, "");
+ argv0 = getprogname();
+#endif
+ if (strcmp(argv0, "[") == 0) {
+ if (strcmp(argv[--argc], "]"))
+ error("missing ]");
+ argv[argc] = NULL;
+ }
+
+ /*
+ * POSIX defines operations of test for up to 4 args
+ * (depending upon what the args are in some cases)
+ *
+ * arg count does not include the command name, (but argc does)
+ * nor the closing ']' when the command was '[' (removed above)
+ *
+ * None of the following allow -a or -o as an operator (those
+ * only apply in the evaluation of unspeicified expressions)
+ *
+ * Note that the xxx_arg() functions return "shell" true/false
+ * (0 == true, 1 == false) or -1 for "unspecified case"
+ *
+ * Other functions return C true/false (1 == true, 0 == false)
+ *
+ * Hence we simply return the result from xxx_arg(), but
+ * invert the result of oexpr() below before returning it.
+ */
+ switch (argc - 1) {
+ case -1: /* impossible, but never mind */
+ case 0: /* test $a where a='' false */
+ return 1;
+
+ case 1: /* test "$a" */
+ return one_arg(argv[1]); /* always works */
+
+ case 2: /* test op "$a" */
+ res = two_arg(argv[1], argv[2]);
+ if (res >= 0)
+ return res;
+ break;
+
+ case 3: /* test "$a" op "$b" or test ! op "$a" */
+ res = three_arg(argv[1], argv[2], argv[3]);
+ if (res >= 0)
+ return res;
+ break;
+
+ case 4: /* test ! "$a" op "$b" or test ( op "$a" ) */
+ res = four_arg(argv[1], argv[2], argv[3], argv[4]);
+ if (res >= 0)
+ return res;
+ break;
+
+ default:
+ break;
+ }
+
+ /*
+ * All other cases produce unspecified results
+ * (including cases above with small arg counts where the
+ * args are not what was expected to be seen)
+ *
+ * We fall back to the old method, of attempting to parse
+ * the expr (highly ambiguous as there is no distinction between
+ * operators and operands that happen to look like operators)
+ */
+
+#ifdef SMALL
+ error("SMALL test, no fallback usage");
+#else
+
+ t_wp = &argv[1];
+ res = !oexpr(t_lex(*t_wp));
+
+ if (*t_wp != NULL && *++t_wp != NULL)
+ syntax(*t_wp, "unexpected operator");
+
+ return res;
+#endif
+}
+
+#ifndef SMALL
+static void
+syntax(const char *op, const char *msg)
+{
+
+ if (op && *op)
+ error("%s: %s", op, msg);
+ else
+ error("%s", msg);
+}
+#endif
+
+static int
+one_arg(const char *arg)
+{
+ /*
+ * True (exit 0, so false...) if arg is not a null string
+ * False (so exit 1, so true) if it is.
+ */
+ return *arg == '\0';
+}
+
+static int
+two_arg(const char *a1, const char *a2)
+{
+ static struct t_op const *op;
+
+ if (a1[0] == '!' && a1[1] == 0)
+ return !one_arg(a2);
+
+ op = findop(a1);
+ if (op != NULL && op->op_type == UNOP)
+ return !perform_unop(op->op_num, a2);
+
+#ifndef TINY
+ /*
+ * an extension, but as we've entered the realm of the unspecified
+ * we're allowed... test ( $a ) where a=''
+ */
+ if (a1[0] == '(' && a2[0] == ')' && (a1[1] | a2[1]) == 0)
+ return 1;
+#endif
+
+ return -1;
+}
+
+static int
+three_arg(const char *a1, const char *a2, const char *a3)
+{
+ static struct t_op const *op;
+ int res;
+
+ op = findop(a2);
+ if (op != NULL && op->op_type == BINOP)
+ return !perform_binop(op->op_num, a1, a3);
+
+ if (a1[1] != '\0')
+ return -1;
+
+ if (a1[0] == '!') {
+ res = two_arg(a2, a3);
+ if (res >= 0)
+ res = !res;
+ return res;
+ }
+
+#ifndef TINY
+ if (a1[0] == '(' && a3[0] == ')' && a3[1] == '\0')
+ return one_arg(a2);
+#endif
+
+ return -1;
+}
+
+static int
+four_arg(const char *a1, const char *a2, const char *a3, const char *a4)
+{
+ int res;
+
+ if (a1[1] != '\0')
+ return -1;
+
+ if (a1[0] == '!') {
+ res = three_arg(a2, a3, a4);
+ if (res >= 0)
+ res = !res;
+ return res;
+ }
+
+#ifndef TINY
+ if (a1[0] == '(' && a4[0] == ')' && a4[1] == '\0')
+ return two_arg(a2, a3);
+#endif
+
+ return -1;
+}
+
+#ifndef SMALL
+static int
+oexpr(enum token n)
+{
+ int res;
+
+ res = aexpr(n);
+ if (*t_wp == NULL)
+ return res;
+ if (t_lex(*++t_wp) == BOR)
+ return oexpr(t_lex(*++t_wp)) || res;
+ t_wp--;
+ return res;
+}
+
+static int
+aexpr(enum token n)
+{
+ int res;
+
+ res = nexpr(n);
+ if (*t_wp == NULL)
+ return res;
+ if (t_lex(*++t_wp) == BAND)
+ return aexpr(t_lex(*++t_wp)) && res;
+ t_wp--;
+ return res;
+}
+
+static int
+nexpr(enum token n)
+{
+
+ if (n == UNOT)
+ return !nexpr(t_lex(*++t_wp));
+ return primary(n);
+}
+
+static int
+primary(enum token n)
+{
+ enum token nn;
+ int res;
+
+ if (n == EOI)
+ return 0; /* missing expression */
+ if (n == LPAREN) {
+ if ((nn = t_lex(*++t_wp)) == RPAREN)
+ return 0; /* missing expression */
+ res = oexpr(nn);
+ if (t_lex(*++t_wp) != RPAREN)
+ syntax(NULL, "closing paren expected");
+ return res;
+ }
+ if (t_wp_op && t_wp_op->op_type == UNOP) {
+ /* unary expression */
+ if (*++t_wp == NULL)
+ syntax(t_wp_op->op_text, "argument expected");
+ return perform_unop(n, *t_wp);
+ }
+
+ if (t_lex(t_wp[1]), t_wp_op && t_wp_op->op_type == BINOP) {
+ return binop();
+ }
+
+ return strlen(*t_wp) > 0;
+}
+#endif /* !SMALL */
+
+static int
+perform_unop(enum token n, const char *opnd)
+{
+ switch (n) {
+ case STREZ:
+ return strlen(opnd) == 0;
+ case STRNZ:
+ return strlen(opnd) != 0;
+ case FILTT:
+ return isatty((int)getn(opnd));
+ default:
+ return filstat(opnd, n);
+ }
+}
+
+#ifndef SMALL
+static int
+binop(void)
+{
+ const char *opnd1, *opnd2;
+ struct t_op const *op;
+
+ opnd1 = *t_wp;
+ (void) t_lex(*++t_wp);
+ op = t_wp_op;
+
+ if ((opnd2 = *++t_wp) == NULL)
+ syntax(op->op_text, "argument expected");
+
+ return perform_binop(op->op_num, opnd1, opnd2);
+}
+#endif
+
+static int
+perform_binop(enum token op_num, const char *opnd1, const char *opnd2)
+{
+ switch (op_num) {
+ case STREQ:
+ return strcmp(opnd1, opnd2) == 0;
+ case STRNE:
+ return strcmp(opnd1, opnd2) != 0;
+ case STRLT:
+ return strcmp(opnd1, opnd2) < 0;
+ case STRGT:
+ return strcmp(opnd1, opnd2) > 0;
+ case INTEQ:
+ return getn(opnd1) == getn(opnd2);
+ case INTNE:
+ return getn(opnd1) != getn(opnd2);
+ case INTGE:
+ return getn(opnd1) >= getn(opnd2);
+ case INTGT:
+ return getn(opnd1) > getn(opnd2);
+ case INTLE:
+ return getn(opnd1) <= getn(opnd2);
+ case INTLT:
+ return getn(opnd1) < getn(opnd2);
+ case FILNT:
+ return newerf(opnd1, opnd2);
+ case FILOT:
+ return olderf(opnd1, opnd2);
+ case FILEQ:
+ return equalf(opnd1, opnd2);
+ default:
+ abort();
+ /* NOTREACHED */
+ }
+}
+
+/*
+ * The manual, and IEEE POSIX 1003.2, suggests this should check the mode bits,
+ * not use access():
+ *
+ * True shall indicate only that the write flag is on. The file is not
+ * writable on a read-only file system even if this test indicates true.
+ *
+ * Unfortunately IEEE POSIX 1003.1-2001, as quoted in SuSv3, says only:
+ *
+ * True shall indicate that permission to read from file will be granted,
+ * as defined in "File Read, Write, and Creation".
+ *
+ * and that section says:
+ *
+ * When a file is to be read or written, the file shall be opened with an
+ * access mode corresponding to the operation to be performed. If file
+ * access permissions deny access, the requested operation shall fail.
+ *
+ * and of course access permissions are described as one might expect:
+ *
+ * * If a process has the appropriate privilege:
+ *
+ * * If read, write, or directory search permission is requested,
+ * access shall be granted.
+ *
+ * * If execute permission is requested, access shall be granted if
+ * execute permission is granted to at least one user by the file
+ * permission bits or by an alternate access control mechanism;
+ * otherwise, access shall be denied.
+ *
+ * * Otherwise:
+ *
+ * * The file permission bits of a file contain read, write, and
+ * execute/search permissions for the file owner class, file group
+ * class, and file other class.
+ *
+ * * Access shall be granted if an alternate access control mechanism
+ * is not enabled and the requested access permission bit is set for
+ * the class (file owner class, file group class, or file other class)
+ * to which the process belongs, or if an alternate access control
+ * mechanism is enabled and it allows the requested access; otherwise,
+ * access shall be denied.
+ *
+ * and when I first read this I thought: surely we can't go about using
+ * open(O_WRONLY) to try this test! However the POSIX 1003.1-2001 Rationale
+ * section for test does in fact say:
+ *
+ * On historical BSD systems, test -w directory always returned false
+ * because test tried to open the directory for writing, which always
+ * fails.
+ *
+ * and indeed this is in fact true for Seventh Edition UNIX, UNIX 32V, and UNIX
+ * System III, and thus presumably also for BSD up to and including 4.3.
+ *
+ * Secondly I remembered why using open() and/or access() are bogus. They
+ * don't work right for detecting read and write permissions bits when called
+ * by root.
+ *
+ * Interestingly the 'test' in 4.4BSD was closer to correct (as per
+ * 1003.2-1992) and it was implemented efficiently with stat() instead of
+ * open().
+ *
+ * This was apparently broken in NetBSD around about 1994/06/30 when the old
+ * 4.4BSD implementation was replaced with a (arguably much better coded)
+ * implementation derived from pdksh.
+ *
+ * Note that modern pdksh is yet different again, but still not correct, at
+ * least not w.r.t. 1003.2-1992.
+ *
+ * As I think more about it and read more of the related IEEE docs I don't like
+ * that wording about 'test -r' and 'test -w' in 1003.1-2001 at all. I very
+ * much prefer the original wording in 1003.2-1992. It is much more useful,
+ * and so that's what I've implemented.
+ *
+ * (Note that a strictly conforming implementation of 1003.1-2001 is in fact
+ * totally useless for the case in question since its 'test -w' and 'test -r'
+ * can never fail for root for any existing files, i.e. files for which 'test
+ * -e' succeeds.)
+ *
+ * The rationale for 1003.1-2001 suggests that the wording was "clarified" in
+ * 1003.1-2001 to align with the 1003.2b draft. 1003.2b Draft 12 (July 1999),
+ * which is the latest copy I have, does carry the same suggested wording as is
+ * in 1003.1-2001, with its rationale saying:
+ *
+ * This change is a clarification and is the result of interpretation
+ * request PASC 1003.2-92 #23 submitted for IEEE Std 1003.2-1992.
+ *
+ * That interpretation can be found here:
+ *
+ * http://www.pasc.org/interps/unofficial/db/p1003.2/pasc-1003.2-23.html
+ *
+ * Not terribly helpful, unfortunately. I wonder who that fence sitter was.
+ *
+ * Worse, IMVNSHO, I think the authors of 1003.2b-D12 have mis-interpreted the
+ * PASC interpretation and appear to be gone against at least one widely used
+ * implementation (namely 4.4BSD). The problem is that for file access by root
+ * this means that if test '-r' and '-w' are to behave as if open() were called
+ * then there's no way for a shell script running as root to check if a file
+ * has certain access bits set other than by the grotty means of interpreting
+ * the output of 'ls -l'. This was widely considered to be a bug in V7's
+ * "test" and is, I believe, one of the reasons why direct use of access() was
+ * avoided in some more recent implementations!
+ *
+ * I have always interpreted '-r' to match '-w' and '-x' as per the original
+ * wording in 1003.2-1992, not the other way around. I think 1003.2b goes much
+ * too far the wrong way without any valid rationale and that it's best if we
+ * stick with 1003.2-1992 and test the flags, and not mimic the behaviour of
+ * open() since we already know very well how it will work -- existance of the
+ * file is all that matters to open() for root.
+ *
+ * Unfortunately the SVID is no help at all (which is, I guess, partly why
+ * we're in this mess in the first place :-).
+ *
+ * The SysV implementation (at least in the 'test' builtin in /bin/sh) does use
+ * access(name, 2) even though it also goes to much greater lengths for '-x'
+ * matching the 1003.2-1992 definition (which is no doubt where that definition
+ * came from).
+ *
+ * The ksh93 implementation uses access() for '-r' and '-w' if
+ * (euid==uid&&egid==gid), but uses st_mode for '-x' iff running as root.
+ * i.e. it does strictly conform to 1003.1-2001 (and presumably 1003.2b).
+ */
+static int
+test_access(struct stat *sp, mode_t stmode)
+{
+ gid_t *groups;
+ register int n;
+ uid_t euid;
+ int maxgroups;
+
+ /*
+ * I suppose we could use access() if not running as root and if we are
+ * running with ((euid == uid) && (egid == gid)), but we've already
+ * done the stat() so we might as well just test the permissions
+ * directly instead of asking the kernel to do it....
+ */
+ euid = geteuid();
+ if (euid == 0) /* any bit is good enough */
+ stmode = (stmode << 6) | (stmode << 3) | stmode;
+ else if (sp->st_uid == euid)
+ stmode <<= 6;
+ else if (sp->st_gid == getegid())
+ stmode <<= 3;
+ else {
+ /* XXX stolen almost verbatim from ksh93.... */
+ /* on some systems you can be in several groups */
+ if ((maxgroups = getgroups(0, NULL)) <= 0)
+ maxgroups = NGROUPS_MAX; /* pre-POSIX system? */
+ groups = ckmalloc((maxgroups + 1) * sizeof(gid_t));
+ n = getgroups(maxgroups, groups);
+ while (--n >= 0) {
+ if (groups[n] == sp->st_gid) {
+ stmode <<= 3;
+ break;
+ }
+ }
+ free(groups);
+ }
+
+ return sp->st_mode & stmode;
+}
+
+static int
+filstat(const char *nm, enum token mode)
+{
+ struct stat s;
+
+ if (mode == FILSYM ? lstat(nm, &s) : stat(nm, &s))
+ return 0;
+
+ switch (mode) {
+ case FILRD:
+ return test_access(&s, S_IROTH);
+ case FILWR:
+ return test_access(&s, S_IWOTH);
+ case FILEX:
+ return test_access(&s, S_IXOTH);
+ case FILEXIST:
+ return 1; /* the successful lstat()/stat() is good enough */
+ case FILREG:
+ return S_ISREG(s.st_mode);
+ case FILDIR:
+ return S_ISDIR(s.st_mode);
+ case FILCDEV:
+ return S_ISCHR(s.st_mode);
+ case FILBDEV:
+ return S_ISBLK(s.st_mode);
+ case FILFIFO:
+ return S_ISFIFO(s.st_mode);
+ case FILSOCK:
+ return S_ISSOCK(s.st_mode);
+ case FILSYM:
+ return S_ISLNK(s.st_mode);
+ case FILSUID:
+ return (s.st_mode & S_ISUID) != 0;
+ case FILSGID:
+ return (s.st_mode & S_ISGID) != 0;
+ case FILSTCK:
+ return (s.st_mode & S_ISVTX) != 0;
+ case FILGZ:
+ return s.st_size > (off_t)0;
+ case FILUID:
+ return s.st_uid == geteuid();
+ case FILGID:
+ return s.st_gid == getegid();
+ default:
+ return 1;
+ }
+}
+
+#define VTOC(x) (const unsigned char *)((const struct t_op *)x)->op_text
+
+static int
+compare1(const void *va, const void *vb)
+{
+ const unsigned char *a = va;
+ const unsigned char *b = VTOC(vb);
+
+ return a[0] - b[0];
+}
+
+static int
+compare2(const void *va, const void *vb)
+{
+ const unsigned char *a = va;
+ const unsigned char *b = VTOC(vb);
+ int z = a[0] - b[0];
+
+ return z ? z : (a[1] - b[1]);
+}
+
+static struct t_op const *
+findop(const char *s)
+{
+ if (s[0] == '-') {
+ if (s[1] == '\0')
+ return NULL;
+ if (s[2] == '\0')
+ return bsearch(s + 1, mop2, __arraycount(mop2),
+ sizeof(*mop2), compare1);
+ else if (s[3] != '\0')
+ return NULL;
+ else
+ return bsearch(s + 1, mop3, __arraycount(mop3),
+ sizeof(*mop3), compare2);
+ } else {
+ if (s[1] == '\0')
+ return bsearch(s, cop, __arraycount(cop), sizeof(*cop),
+ compare1);
+ else if (strcmp(s, cop2[0].op_text) == 0)
+ return cop2;
+ else
+ return NULL;
+ }
+}
+
+#ifndef SMALL
+static enum token
+t_lex(char *s)
+{
+ struct t_op const *op;
+
+ if (s == NULL) {
+ t_wp_op = NULL;
+ return EOI;
+ }
+
+ if ((op = findop(s)) != NULL) {
+ if (!((op->op_type == UNOP && isoperand()) ||
+ (op->op_num == LPAREN && *(t_wp+1) == 0))) {
+ t_wp_op = op;
+ return op->op_num;
+ }
+ }
+ t_wp_op = NULL;
+ return OPERAND;
+}
+
+static int
+isoperand(void)
+{
+ struct t_op const *op;
+ char *s, *t;
+
+ if ((s = *(t_wp+1)) == 0)
+ return 1;
+ if ((t = *(t_wp+2)) == 0)
+ return 0;
+ if ((op = findop(s)) != NULL)
+ return op->op_type == BINOP && (t[0] != ')' || t[1] != '\0');
+ return 0;
+}
+#endif
+
+/* atoi with error detection */
+static long long
+getn(const char *s)
+{
+ char *p;
+ long long r;
+
+ errno = 0;
+ r = strtoll(s, &p, 10);
+
+ if (errno != 0)
+ if (errno == ERANGE && (r == LLONG_MAX || r == LLONG_MIN))
+ error("%s: out of range", s);
+
+ if (p != s)
+ while (isspace((unsigned char)*p))
+ p++;
+
+ if (*p || p == s)
+ error("'%s': bad number", s);
+
+ return r;
+}
+
+static int
+newerf(const char *f1, const char *f2)
+{
+ struct stat b1, b2;
+
+ return (stat(f1, &b1) == 0 &&
+ stat(f2, &b2) == 0 &&
+ timespeccmp(&b1.st_mtim, &b2.st_mtim, >));
+}
+
+static int
+olderf(const char *f1, const char *f2)
+{
+ struct stat b1, b2;
+
+ return (stat(f1, &b1) == 0 &&
+ stat(f2, &b2) == 0 &&
+ timespeccmp(&b1.st_mtim, &b2.st_mtim, <));
+}
+
+static int
+equalf(const char *f1, const char *f2)
+{
+ struct stat b1, b2;
+
+ return (stat(f1, &b1) == 0 &&
+ stat(f2, &b2) == 0 &&
+ b1.st_dev == b2.st_dev &&
+ b1.st_ino == b2.st_ino);
+}
diff --git a/usr.bin/asa/asa.1 b/usr.bin/asa/asa.1
new file mode 100644
index 0000000..ab76d28
--- /dev/null
+++ b/usr.bin/asa/asa.1
@@ -0,0 +1,85 @@
+.\" $NetBSD: asa.1,v 1.14 2017/07/03 21:34:18 wiz Exp $
+.\"
+.\" Copyright (c) 1993 Winning Strategies, Inc.
+.\" All rights reserved.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\" notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must reproduce the above copyright
+.\" notice, this list of conditions and the following disclaimer in the
+.\" documentation and/or other materials provided with the distribution.
+.\" 3. All advertising materials mentioning features or use of this software
+.\" must display the following acknowledgement:
+.\" This product includes software developed by Winning Strategies, Inc.
+.\" 4. The name of the author may not be used to endorse or promote products
+.\" derived from this software without specific prior written permission
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+.\" IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+.\" OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+.\" IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+.\" INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+.\" NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+.\" DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+.\" THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+.\" (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+.\" THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+.\"
+.Dd June 2, 2016
+.Dt ASA 1
+.Os
+.Sh NAME
+.Nm asa
+.Nd interpret carriage-control characters
+.Sh SYNOPSIS
+.Nm
+.Op Ar
+.Sh DESCRIPTION
+The
+.Nm
+utility reads files sequentially, mapping
+.Tn FORTRAN
+carriage-control characters to line-printer control sequences,
+and writes them to the standard output.
+.Pp
+The first character of each line is interpreted as a carriage-control
+character. The following characters are interpreted as follows:
+.Bl -tag -width "<space>"
+.It <space>
+Output the rest of the line without change.
+.It 0
+Output a <newline> character before printing the rest of the line.
+.It 1
+Output a <formfeed> character before printing the rest of the line.
+.It +
+The trailing <newline> of the previous line is replaced by a <carriage-return>
+before printing the rest of the line.
+.El
+.Pp
+Lines beginning with characters other than the above are treated as if they
+begin with <space>.
+.Sh EXIT STATUS
+.Ex -std
+.Sh EXAMPLES
+To view a file containing the output of a
+.Tn FORTRAN
+program:
+.Dl asa file
+.Pp
+To format the output of a
+.Tn FORTRAN
+program and redirect it to a line-printer:
+.Dl a.out | asa | lpr
+.Sh STANDARDS
+The
+.Nm
+utility conforms to
+.St -p1003.2-92
+and to
+.St -xcu5 .
+.Sh AUTHORS
+.An J.T. Conklin ,
+Winning Strategies, Inc.
diff --git a/usr.bin/asa/asa.c b/usr.bin/asa/asa.c
new file mode 100644
index 0000000..6c1efd6
--- /dev/null
+++ b/usr.bin/asa/asa.c
@@ -0,0 +1,116 @@
+/* $NetBSD: asa.c,v 1.17 2016/09/05 00:40:28 sevan Exp $ */
+
+/*
+ * Copyright (c) 1993,94 Winning Strategies, Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. All advertising materials mentioning features or use of this software
+ * must display the following acknowledgement:
+ * This product includes software developed by Winning Strategies, Inc.
+ * 4. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+#ifndef lint
+__RCSID("$NetBSD: asa.c,v 1.17 2016/09/05 00:40:28 sevan Exp $");
+#endif
+
+#include <err.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+static void asa(FILE *);
+
+int
+/*ARGSUSED*/
+main (int argc, char *argv[])
+{
+ FILE *fp;
+
+ /* skip progname */
+ argv++;
+
+ if (*argv == NULL)
+ asa(stdin);
+ else
+ do {
+ if ((fp = fopen(*argv, "r")) == NULL) {
+ warn("%s", *argv);
+ continue;
+ }
+ asa(fp);
+ (void)fclose(fp);
+ } while (*++argv != NULL);
+
+ return 0;
+}
+
+static void
+asa(FILE *f)
+{
+ char *buf;
+ size_t len;
+
+ if ((buf = fgetln(f, &len)) != NULL) {
+ if (len > 0 && buf[len - 1] == '\n')
+ buf[--len] = '\0';
+ /* special case the first line */
+ switch (buf[0]) {
+ case '0':
+ (void)putchar('\n');
+ break;
+ case '1':
+ (void)putchar('\f');
+ break;
+ }
+
+ if (len > 1 && buf[0] && buf[1])
+ (void)fwrite(buf + 1, 1, len - 1, stdout);
+
+ while ((buf = fgetln(f, &len)) != NULL) {
+ if (len > 0 && buf[len - 1] == '\n')
+ buf[--len] = '\0';
+ switch (buf[0]) {
+ default:
+ case ' ':
+ (void)putchar('\n');
+ break;
+ case '0':
+ (void)putchar('\n');
+ (void)putchar('\n');
+ break;
+ case '1':
+ (void)putchar('\f');
+ break;
+ case '+':
+ (void)putchar('\r');
+ break;
+ }
+
+ if (len > 1 && buf[0] && buf[1])
+ (void)fwrite(buf + 1, 1, len - 1, stdout);
+ }
+
+ (void)putchar('\n');
+ }
+}
diff --git a/usr.bin/at/at.1 b/usr.bin/at/at.1
new file mode 100644
index 0000000..cdd97e1
--- /dev/null
+++ b/usr.bin/at/at.1
@@ -0,0 +1,339 @@
+.\" $NetBSD: at.1,v 1.29 2016/11/18 12:16:48 abhinav Exp $
+.\" $OpenBSD: at.1,v 1.6 1998/06/05 00:47:46 deraadt Exp $
+.\" $FreeBSD: at.man,v 1.6 1997/02/22 19:54:05 peter Exp $
+.Dd March 10, 2008
+.Dt AT 1
+.Os
+.Sh NAME
+.Nm at ,
+.Nm batch ,
+.Nm atq ,
+.Nm atrm
+.Nd queue, examine or delete jobs for later execution
+.Sh SYNOPSIS
+.Nm at
+.Op Fl bdlmrVv
+.Op Fl f Ar file
+.Op Fl q Ar queue
+.Fl t Ar [[CC]YY]MMDDhhmm[.SS]
+.Nm
+.Op Fl bdlmrVv
+.Op Fl f Ar file
+.Op Fl q Ar queue
+.Ar time
+.Nm
+.Op Fl V
+.Fl c Ar job Op Ar job ...
+.Nm atq
+.Op Fl Vv
+.Op Fl q Ar queue
+.Nm atrm
+.Op Fl V
+.Ar job
+.Op Ar job ...
+.Nm batch
+.Op Fl mVv
+.Op Fl f Ar file
+.Op Fl q Ar queue
+.Op Fl t Ar [[CC]YY]MMDDhhmm[.SS]
+.Nm batch
+.Op Fl mVv
+.Op Fl f Ar file
+.Op Fl q Ar queue
+.Op Ar time
+.Sh DESCRIPTION
+.Nm
+and
+.Nm batch
+read commands from standard input or a specified file which
+are to be executed at a later time, using
+.Xr sh 1 .
+.Bl -tag -width indent
+.It Nm at
+Executes commands at a specified time.
+.It Nm atq
+Lists the user's pending jobs, unless the user is the superuser.
+In that case, everybody's jobs are listed.
+.It Nm atrm
+Deletes jobs.
+.It Nm batch
+Executes commands when system load levels permit.
+In other words, when
+the load average drops below 1.5, or the value specified in the invocation of
+.Xr atrun 8 .
+.El
+.Pp
+.Nm
+allows some moderately complex
+.Ar time
+specifications.
+It accepts times of the form
+.Ar HHMM
+or
+.Ar HH:MM
+to run a job at a specific time of day.
+(If that time is already past, the next day is assumed.)
+You may also specify
+.Sq midnight ,
+.Sq noon ,
+or
+.Sq teatime
+(4pm)
+and you can have a time-of-day suffixed with
+.Sq AM
+or
+.Sq PM
+for running in the morning or the evening.
+You can also say what day the job will be run,
+by giving a date in the form
+.Ar %month-name day
+with an optional
+.Ar year ,
+or giving a date of the form
+.Ar MMDDYY
+or
+.Ar MM/DD/YY
+or
+.Ar DD.MM.YY .
+The specification of a date must follow the specification of
+the time of day.
+You can also give times like
+.Op Nm now
+or
+.Op Nm now
+.Sq + Ar count %time-units ,
+where the time-units can be
+.Sq minutes ,
+.Sq hours ,
+.Sq days ,
+.Sq weeks ,
+.Sq months ,
+or
+.Sq years
+and you can tell
+.Nm
+to run the job today by suffixing the time with
+.Sq today
+and to run the job tomorrow by suffixing the time with
+.Sq tomorrow .
+.Pp
+For example, to run a job at 4pm three days from now, you would do
+.Dl at 4pm + 3 days ,
+to run a job at 10:00am on July 31, you would do
+.Dl at 10am Jul 31
+and to run a job at 1am tomorrow, you would do
+.Dl at 1am tomorrow .
+.Pp
+Alternatively the time may be specified in a language-neutral fashion
+by using the
+.Fl t
+options.
+.Pp
+For both
+.Nm
+and
+.Nm batch ,
+commands are read from standard input or the file specified
+with the
+.Fl f
+option and executed.
+The working directory, the environment (except for the variables
+.Ev TERM ,
+.Ev TERMCAP ,
+.Ev DISPLAY
+and
+.Ev _ )
+and the
+.Ar umask
+are retained from the time of invocation.
+An
+.Nm
+or
+.Nm batch
+command invoked from a
+.Xr su 1
+shell will retain the current userid.
+The user will be mailed standard error and standard output from his
+commands, if any.
+Mail will be sent using the command
+.Xr sendmail 1 .
+If
+.Nm
+is executed from a
+.Xr su 1
+shell, the owner of the login shell will receive the mail.
+.Pp
+The superuser may use these commands in any case.
+For other users, permission to use
+.Nm at
+is determined by the files
+.Pa /var/at/at.allow
+and
+.Pa /var/at/at.deny .
+.Pp
+If the file
+.Pa /var/at/at.allow
+exists, only usernames mentioned in it are allowed to use
+.Nm .
+.Pp
+If
+.Pa /var/at/at.allow
+does not exist,
+.Pa /var/at/at.deny
+is checked; every username not mentioned in it is then allowed
+to use
+.Nm .
+.Pp
+If neither exists, only the superuser is allowed use of
+.Nm .
+.Pp
+An empty
+.Pa /var/at/at.deny
+means that every user is allowed use these commands.
+This is the default configuration.
+.Sh OPTIONS
+.Bl -tag -offset indent -width XqXqueueXX
+.It Fl b
+Is an alias for
+.Nm batch .
+.It Fl c
+Cats the jobs listed on the command line to standard output.
+.It Fl d
+Is an alias for
+.Nm atrm .
+.It Fl f Ar file
+Reads the job from
+.Ar file
+rather than standard input.
+.It Fl l
+Is an alias for
+.Nm atq .
+.It Fl m
+Send mail to the user when the job has completed even if there was no
+output.
+.It Fl q Ar queue
+Uses the specified queue.
+A queue designation consists of a single letter.
+Valid queue designations
+range from
+.Sq a
+to
+.Sq z
+and
+.Sq A
+to
+.Sq Z .
+The
+.Sq c
+queue is the default for
+.Nm
+and the
+.Sq E
+queue for
+.Nm batch .
+Queues with higher letters run with increased niceness.
+If a job is submitted to a queue designated with an uppercase letter, it
+is treated as if it had been submitted to batch at that time.
+If
+.Nm atq
+is given a specific queue, it will only show jobs pending in that queue.
+.It Fl r
+Is an alias for
+.Nm atrm .
+.It Fl t
+For both
+.Nm
+and
+.Nm batch ,
+the time may be specified in a language-neutral format consisting of:
+.Bl -tag -width Ds -compact -offset indent
+.It Ar CC
+The first two digits of the year (the century).
+.It Ar YY
+The second two digits of the year.
+If
+.Ar YY
+is specified, but
+.Ar CC
+is not, a value for
+.Ar YY
+between 69 and 99 results in a
+.Ar CC
+value of 19.
+Otherwise, a
+.Ar CC
+value of 20 is used.
+.It Ar MM
+The month of the year, from 01 to 12.
+.It Ar \&DD
+The day of the month, from 01 to 31.
+.It Ar hh
+The hour of the day, from 00 to 23.
+.It Ar mm
+The minute of the hour, from 00 to 59.
+.It Ar \&SS
+The second of the minute, from 00 to 61.
+.El
+.It Fl V
+Prints the version number to standard error.
+.It Fl v
+For
+.Nm atq ,
+shows completed but not yet deleted jobs in the queue.
+Otherwise shows the time the job will be executed.
+.El
+.Sh FILES
+.Bl -tag -width /var/at/.lockfile -compact
+.It Pa /var/at/jobs
+Directory containing job files
+.It Pa /var/at/spool
+Directory containing output spool files
+.It Pa /var/run/utmp
+Login records
+.It Pa /var/at/at.allow
+Allow permission control
+.It Pa /var/at/at.deny
+Deny permission control
+.It Pa /var/at/.lockfile
+Job-creation lock file.
+.El
+.Sh SEE ALSO
+.Xr nice 1 ,
+.Xr sendmail 1 ,
+.Xr sh 1 ,
+.Xr umask 2 ,
+.Xr atrun 8 ,
+.Xr cron 8
+.Sh STANDARDS
+The
+.Nm
+and
+.Nm batch
+utilities conform to
+.St -p1003.2-92 .
+.Sh AUTHORS
+.An -nosplit
+.Nm
+was mostly written by
+.An Thomas Koenig Aq Mt ig25@rz.uni-karlsruhe.de .
+The time parsing routines are implemented by
+.An David Parsons Aq Mt orc@pell.chi.il.us .
+.Sh BUGS
+If the file
+.Pa /var/run/utmp
+is not available or corrupted, or if the user is not logged on at the
+time
+.Nm
+is invoked, the mail is sent to the userid found
+in the environment variable
+.Ev LOGNAME .
+If that is undefined or empty, the current userid is assumed.
+.Pp
+.Nm
+and
+.Nm batch
+as presently implemented are not suitable when users are competing for
+resources.
+If this is the case for your site, you might want to consider another
+batch system, such as
+.Ic nqs .
diff --git a/usr.bin/at/at.c b/usr.bin/at/at.c
new file mode 100644
index 0000000..621b95d
--- /dev/null
+++ b/usr.bin/at/at.c
@@ -0,0 +1,758 @@
+/* $NetBSD: at.c,v 1.31 2016/03/13 00:32:09 dholland Exp $ */
+
+/*
+ * at.c : Put file into atrun queue
+ * Copyright (C) 1993, 1994 Thomas Koenig
+ *
+ * Atrun & Atq modifications
+ * Copyright (C) 1993 David Parsons
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. The name of the author(s) may not be used to endorse or promote
+ * products derived from this software without specific prior written
+ * permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/* System Headers */
+#include <sys/types.h>
+#include <sys/param.h>
+#include <sys/stat.h>
+#include <sys/wait.h>
+#include <ctype.h>
+#include <dirent.h>
+#include <err.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <locale.h>
+#include <pwd.h>
+#include <signal.h>
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <unistd.h>
+#include <util.h>
+
+/* Local headers */
+#include "at.h"
+#include "panic.h"
+#include "parsetime.h"
+#include "perm.h"
+#include "pathnames.h"
+#include "stime.h"
+#include "privs.h"
+
+/* Macros */
+#define ALARMC 10 /* Number of seconds to wait for timeout */
+
+#define TIMESIZE 50
+
+enum { ATQ, ATRM, AT, BATCH, CAT }; /* what program we want to run */
+
+/* File scope variables */
+#ifndef lint
+#if 0
+static char rcsid[] = "$OpenBSD: at.c,v 1.15 1998/06/03 16:20:26 deraadt Exp $";
+#else
+__RCSID("$NetBSD: at.c,v 1.31 2016/03/13 00:32:09 dholland Exp $");
+#endif
+#endif
+
+const char *no_export[] = {"TERM", "TERMCAP", "DISPLAY", "_"};
+static int send_mail = 0;
+
+/* External variables */
+
+extern char **environ;
+bool fcreated = false;
+char atfile[FILENAME_MAX];
+
+char *atinput = NULL; /* where to get input from */
+unsigned char atqueue = 0; /* which queue to examine for jobs (atq) */
+char atverify = 0; /* verify time instead of queuing job */
+
+/* Function declarations */
+
+__dead static void sigc (int);
+__dead static void alarmc (int);
+static char *cwdname (void);
+static int nextjob (void);
+static void writefile (time_t, unsigned char);
+static void list_jobs (void);
+static void process_jobs (int, char **, int);
+
+/* Signal catching functions */
+
+/*ARGSUSED*/
+static void
+sigc(int signo)
+{
+
+ /* If a signal interrupts us, remove the spool file and exit. */
+ if (fcreated) {
+ privs_enter();
+ (void)unlink(atfile);
+ privs_exit();
+ }
+ (void)raise_default_signal(signo);
+ exit(EXIT_FAILURE);
+}
+
+/*ARGSUSED*/
+static void
+alarmc(int signo)
+{
+
+ /* Time out after some seconds. */
+ warnx("File locking timed out");
+ sigc(signo);
+}
+
+/* Local functions */
+
+static char *
+cwdname(void)
+{
+
+ /*
+ * Read in the current directory; the name will be overwritten on
+ * subsequent calls.
+ */
+ static char path[MAXPATHLEN];
+
+ return getcwd(path, sizeof(path));
+}
+
+static int
+nextjob(void)
+{
+ int jobno;
+ FILE *fid;
+
+ if ((fid = fopen(_PATH_SEQFILE, "r+")) != NULL) {
+ if (fscanf(fid, "%5x", &jobno) == 1) {
+ (void)rewind(fid);
+ jobno = (1+jobno) % 0xfffff; /* 2^20 jobs enough? */
+ (void)fprintf(fid, "%05x\n", jobno);
+ } else
+ jobno = EOF;
+ (void)fclose(fid);
+ return jobno;
+ } else if ((fid = fopen(_PATH_SEQFILE, "w")) != NULL) {
+ (void)fprintf(fid, "%05x\n", jobno = 1);
+ (void)fclose(fid);
+ return 1;
+ }
+ return EOF;
+}
+
+static void
+writefile(time_t runtimer, unsigned char queue)
+{
+ /*
+ * This does most of the work if at or batch are invoked for
+ * writing a job.
+ */
+ int jobno;
+ char *ap, *ppos;
+ const char *mailname;
+ struct passwd *pass_entry;
+ struct stat statbuf;
+ int fdes, lockdes, fd2;
+ FILE *fp, *fpin;
+ struct sigaction act;
+ char **atenv;
+ int ch;
+ mode_t cmask;
+ struct flock lock;
+
+ (void)setlocale(LC_TIME, "");
+
+ /*
+ * Install the signal handler for SIGINT; terminate after removing the
+ * spool file if necessary
+ */
+ (void)memset(&act, 0, sizeof(act));
+ act.sa_handler = sigc;
+ (void)sigemptyset(&act.sa_mask);
+ act.sa_flags = 0;
+
+ (void)sigaction(SIGINT, &act, NULL);
+
+ (void)strlcpy(atfile, _PATH_ATJOBS, sizeof(atfile));
+ ppos = atfile + strlen(atfile);
+
+ /*
+ * Loop over all possible file names for running something at this
+ * particular time, see if a file is there; the first empty slot at
+ * any particular time is used. Lock the file _PATH_LOCKFILE first
+ * to make sure we're alone when doing this.
+ */
+
+ privs_enter();
+
+ if ((lockdes = open(_PATH_LOCKFILE, O_WRONLY | O_CREAT, S_IWUSR | S_IRUSR)) < 0)
+ perr("Cannot open lockfile " _PATH_LOCKFILE);
+
+ lock.l_type = F_WRLCK;
+ lock.l_whence = SEEK_SET;
+ lock.l_start = 0;
+ lock.l_len = 0;
+
+ act.sa_handler = alarmc;
+ (void)sigemptyset(&act.sa_mask);
+ act.sa_flags = 0;
+
+ /*
+ * Set an alarm so a timeout occurs after ALARMC seconds, in case
+ * something is seriously broken.
+ */
+ (void)sigaction(SIGALRM, &act, NULL);
+ (void)alarm(ALARMC);
+ (void)fcntl(lockdes, F_SETLKW, &lock);
+ (void)alarm(0);
+
+ if ((jobno = nextjob()) == EOF)
+ perr("Cannot generate job number");
+
+ (void)snprintf(ppos, sizeof(atfile) - (ppos - atfile),
+ "%c%5x%8lx", queue, jobno, (unsigned long) (runtimer/60));
+
+ for (ap = ppos; *ap != '\0'; ap++)
+ if (*ap == ' ')
+ *ap = '0';
+
+ if (stat(atfile, &statbuf) == -1)
+ if (errno != ENOENT)
+ perr("Cannot access " _PATH_ATJOBS);
+
+ /*
+ * Create the file. The x bit is only going to be set after it has
+ * been completely written out, to make sure it is not executed in
+ * the meantime. To make sure they do not get deleted, turn off
+ * their r bit. Yes, this is a kluge.
+ */
+ cmask = umask(S_IRUSR | S_IWUSR | S_IXUSR);
+ if ((fdes = open(atfile, O_CREAT | O_TRUNC | O_WRONLY, S_IRUSR)) == -1)
+ perr("Cannot create atjob file");
+
+ if ((fd2 = dup(fdes)) == -1)
+ perr("Error in dup() of job file");
+
+ if (fchown(fd2, real_uid, real_gid) == -1)
+ perr("Cannot give away file");
+
+ privs_exit();
+
+ /*
+ * We've successfully created the file; let's set the flag so it
+ * gets removed in case of an interrupt or error.
+ */
+ fcreated = true;
+
+ /* Now we can release the lock, so other people can access it */
+ lock.l_type = F_UNLCK;
+ lock.l_whence = SEEK_SET;
+ lock.l_start = 0;
+ lock.l_len = 0;
+ (void)fcntl(lockdes, F_SETLKW, &lock);
+ (void)close(lockdes);
+
+ if ((fp = fdopen(fdes, "w")) == NULL)
+ panic("Cannot reopen atjob file");
+
+ /*
+ * Get the userid to mail to, first by trying getlogin(), which reads
+ * /etc/utmp, then from $LOGNAME or $USER, finally from getpwuid().
+ */
+ mailname = getlogin();
+ if (mailname == NULL && (mailname = getenv("LOGNAME")) == NULL)
+ mailname = getenv("USER");
+
+ if (mailname == NULL || mailname[0] == '\0' ||
+ strlen(mailname) > LOGIN_NAME_MAX || getpwnam(mailname) == NULL) {
+ pass_entry = getpwuid(real_uid);
+ if (pass_entry != NULL)
+ mailname = pass_entry->pw_name;
+ }
+
+ if (atinput != NULL) {
+ fpin = freopen(atinput, "r", stdin);
+ if (fpin == NULL)
+ perr("Cannot open input file");
+ }
+ (void)fprintf(fp,
+ "#!/bin/sh\n"
+ "# atrun uid=%u gid=%u\n"
+ "# mail %s %d\n",
+ real_uid, real_gid, mailname, send_mail);
+
+ /* Write out the umask at the time of invocation */
+ (void)fprintf(fp, "umask %o\n", cmask);
+
+ /*
+ * Write out the environment. Anything that may look like a special
+ * character to the shell is quoted, except for \n, which is done
+ * with a pair of "'s. Dont't export the no_export list (such as
+ * TERM or DISPLAY) because we don't want these.
+ */
+ for (atenv = environ; *atenv != NULL; atenv++) {
+ int export = 1;
+ char *eqp;
+
+ eqp = strchr(*atenv, '=');
+ if (eqp == NULL)
+ eqp = *atenv;
+ else {
+ size_t i;
+
+ for (i = 0; i < __arraycount(no_export); i++) {
+ export = export &&
+ strncmp(*atenv, no_export[i],
+ (size_t)(eqp - *atenv)) != 0;
+ }
+ eqp++;
+ }
+
+ if (export) {
+ (void)fwrite(*atenv, sizeof(char),
+ (size_t)(eqp - *atenv), fp);
+ for (ap = eqp; *ap != '\0'; ap++) {
+ if (*ap == '\n')
+ (void)fprintf(fp, "\"\n\"");
+ else {
+ if (!isalnum((unsigned char)*ap)) {
+ switch (*ap) {
+ case '%': case '/': case '{':
+ case '[': case ']': case '=':
+ case '}': case '@': case '+':
+ case '#': case ',': case '.':
+ case ':': case '-': case '_':
+ break;
+ default:
+ (void)fputc('\\', fp);
+ break;
+ }
+ }
+ (void)fputc(*ap, fp);
+ }
+ }
+ (void)fputs("; export ", fp);
+ (void)fwrite(*atenv, sizeof(char),
+ (size_t)(eqp - *atenv - 1), fp);
+ (void)fputc('\n', fp);
+ }
+ }
+ /*
+ * Cd to the directory at the time and write out all the
+ * commands the user supplies from stdin.
+ */
+ (void)fputs("cd ", fp);
+ for (ap = cwdname(); *ap != '\0'; ap++) {
+ if (*ap == '\n')
+ (void)fprintf(fp, "\"\n\"");
+ else {
+ if (*ap != '/' && !isalnum((unsigned char)*ap))
+ (void)fputc('\\', fp);
+
+ (void)fputc(*ap, fp);
+ }
+ }
+ /*
+ * Test cd's exit status: die if the original directory has been
+ * removed, become unreadable or whatever.
+ */
+ (void)fprintf(fp,
+ " || {\n"
+ "\t echo 'Execution directory inaccessible' >&2\n"
+ "\t exit 1\n"
+ "}\n");
+
+ if ((ch = getchar()) == EOF)
+ panic("Input error");
+
+ do {
+ (void)fputc(ch, fp);
+ } while ((ch = getchar()) != EOF);
+
+ (void)fprintf(fp, "\n");
+ if (ferror(fp))
+ panic("Output error");
+
+ if (ferror(stdin))
+ panic("Input error");
+
+ (void)fclose(fp);
+
+ privs_enter();
+
+ /*
+ * Set the x bit so that we're ready to start executing
+ */
+ if (fchmod(fd2, S_IRUSR | S_IWUSR | S_IXUSR) == -1)
+ perr("Cannot give away file");
+
+ privs_exit();
+
+ (void)close(fd2);
+ (void)fprintf(stderr,
+ "Job %d will be executed using /bin/sh\n", jobno);
+}
+
+static void
+list_jobs(void)
+{
+ /*
+ * List all a user's jobs in the queue, by looping through
+ * _PATH_ATJOBS, or everybody's if we are root
+ */
+ struct passwd *pw;
+ DIR *spool;
+ struct dirent *dirent;
+ struct stat buf;
+ struct tm runtime;
+ unsigned long ctm;
+ unsigned char queue;
+ int jobno;
+ time_t runtimer;
+ char timestr[TIMESIZE];
+ int first = 1;
+
+ privs_enter();
+
+ if (chdir(_PATH_ATJOBS) == -1)
+ perr("Cannot change to " _PATH_ATJOBS);
+
+ if ((spool = opendir(".")) == NULL)
+ perr("Cannot open " _PATH_ATJOBS);
+
+ /* Loop over every file in the directory */
+ while ((dirent = readdir(spool)) != NULL) {
+ if (stat(dirent->d_name, &buf) == -1)
+ perr("Cannot stat in " _PATH_ATJOBS);
+
+ /*
+ * See it's a regular file and has its x bit turned on and
+ * is the user's
+ */
+ if (!S_ISREG(buf.st_mode)
+ || (buf.st_uid != real_uid && real_uid != 0)
+ || !(S_IXUSR & buf.st_mode || atverify))
+ continue;
+
+ if (sscanf(dirent->d_name, "%c%5x%8lx", &queue, &jobno, &ctm) != 3)
+ continue;
+
+ if (atqueue && queue != atqueue)
+ continue;
+
+ runtimer = 60 * (time_t)ctm;
+ runtime = *localtime(&runtimer);
+#if 1
+ /*
+ * Provide a consistent date/time format instead of a
+ * locale-specific one that might have 2 digit years
+ */
+ (void)strftime(timestr, TIMESIZE, "%T %F", &runtime);
+#else
+ (void)strftime(timestr, TIMESIZE, "%X %x", &runtime);
+#endif
+ if (first) {
+ (void)printf("%-*s %-*s %-*s %s\n",
+ (int)strlen(timestr), "Date",
+ LOGIN_NAME_MAX, "Owner",
+ 7, "Queue",
+ "Job");
+ first = 0;
+ }
+ pw = getpwuid(buf.st_uid);
+
+ (void)printf("%s %-*s %c%-*s %d\n",
+ timestr,
+ LOGIN_NAME_MAX, pw ? pw->pw_name : "???",
+ queue,
+ 6, (S_IXUSR & buf.st_mode) ? "" : "(done)",
+ jobno);
+ }
+ (void)closedir(spool);
+ privs_exit();
+}
+
+static void
+process_jobs(int argc, char **argv, int what)
+{
+ /* Delete every argument (job - ID) given */
+ int i;
+ struct stat buf;
+ DIR *spool;
+ struct dirent *dirent;
+ unsigned long ctm;
+ unsigned char queue;
+ int jobno;
+
+ privs_enter();
+
+ if (chdir(_PATH_ATJOBS) == -1)
+ perr("Cannot change to " _PATH_ATJOBS);
+
+ if ((spool = opendir(".")) == NULL)
+ perr("Cannot open " _PATH_ATJOBS);
+
+ privs_exit();
+
+ /* Loop over every file in the directory */
+ while((dirent = readdir(spool)) != NULL) {
+
+ privs_enter();
+ if (stat(dirent->d_name, &buf) == -1)
+ perr("Cannot stat in " _PATH_ATJOBS);
+ privs_exit();
+
+ if (sscanf(dirent->d_name, "%c%5x%8lx", &queue, &jobno, &ctm) !=3)
+ continue;
+
+ for (i = optind; i < argc; i++) {
+ if (atoi(argv[i]) == jobno) {
+ if (buf.st_uid != real_uid && real_uid != 0)
+ errx(EXIT_FAILURE,
+ "%s: Not owner", argv[i]);
+
+ switch (what) {
+ case ATRM:
+ privs_enter();
+
+ if (unlink(dirent->d_name) == -1)
+ perr(dirent->d_name);
+
+ privs_exit();
+ break;
+
+ case CAT: {
+ FILE *fp;
+ int ch;
+
+ privs_enter();
+
+ fp = fopen(dirent->d_name, "r");
+
+ privs_exit();
+
+ if (!fp)
+ perr("Cannot open file");
+ else {
+ while((ch = getc(fp)) != EOF)
+ (void)putchar(ch);
+ (void)fclose(fp);
+ }
+ }
+ break;
+
+ default:
+ errx(EXIT_FAILURE,
+ "Internal error, process_jobs = %d",
+ what);
+ break;
+ }
+ }
+ }
+ }
+ (void)closedir(spool);
+}
+
+/* Global functions */
+
+int
+main(int argc, char **argv)
+{
+ int c;
+ unsigned char queue = DEFAULT_AT_QUEUE;
+ char queue_set = 0;
+ char time_set = 0;
+ char *pgm;
+
+ int program = AT; /* our default program */
+ const char *options = "q:f:t:mvldbrVc"; /* default options for at */
+ int disp_version = 0;
+ time_t timer;
+
+ privs_relinquish();
+
+ /* Eat any leading paths */
+ if ((pgm = strrchr(argv[0], '/')) == NULL)
+ pgm = argv[0];
+ else
+ pgm++;
+
+ /* find out what this program is supposed to do */
+ if (strcmp(pgm, "atq") == 0) {
+ program = ATQ;
+ options = "q:vV";
+ } else if (strcmp(pgm, "atrm") == 0) {
+ program = ATRM;
+ options = "V";
+ } else if (strcmp(pgm, "batch") == 0) {
+ program = BATCH;
+ options = "f:q:t:mvV";
+ }
+
+ /* process whatever options we can process */
+ opterr = 1;
+ while ((c = getopt(argc, argv, options)) != -1) {
+ switch (c) {
+ case 'v': /* verify time settings */
+ atverify = 1;
+ break;
+
+ case 'm': /* send mail when job is complete */
+ send_mail = 1;
+ break;
+
+ case 'f':
+ atinput = optarg;
+ break;
+
+ case 'q': /* specify queue */
+ if (strlen(optarg) > 1)
+ usage();
+
+ atqueue = queue = *optarg;
+ if (!(islower(queue) || isupper(queue)))
+ usage();
+
+ queue_set = 1;
+ break;
+ case 't': /* touch(1) date format */
+ timer = stime(optarg);
+ time_set = 1;
+ break;
+
+ case 'd':
+ case 'r':
+ if (program != AT)
+ usage();
+
+ program = ATRM;
+ options = "V";
+ break;
+
+ case 'l':
+ if (program != AT)
+ usage();
+
+ program = ATQ;
+ options = "q:vV";
+ break;
+
+ case 'b':
+ if (program != AT)
+ usage();
+
+ program = BATCH;
+ options = "f:q:mvV";
+ break;
+
+ case 'V':
+ disp_version = 1;
+ break;
+
+ case 'c':
+ program = CAT;
+ options = "";
+ break;
+
+ default:
+ usage();
+ break;
+ }
+ } /* end of options eating */
+
+ if (disp_version)
+ (void)fprintf(stderr, "%s version %.1f\n", pgm, AT_VERSION);
+
+ if (!check_permission())
+ errx(EXIT_FAILURE,
+ "You do not have permission to use %s.", pgm);
+
+ /* select our program */
+ switch (program) {
+ case ATQ:
+ if (optind != argc)
+ usage();
+ list_jobs();
+ break;
+
+ case ATRM:
+ case CAT:
+ if (optind == argc)
+ usage();
+ process_jobs(argc, argv, program);
+ break;
+
+ case AT:
+ if (argc > optind) {
+ /* -t and timespec argument are mutually exclusive */
+ if (time_set) {
+ usage();
+ exit(EXIT_FAILURE);
+ } else {
+ timer = parsetime(argc, argv);
+ time_set = 1;
+ }
+ }
+
+ if (atverify) {
+ struct tm *tm = localtime(&timer);
+ (void)fprintf(stderr, "%s\n", asctime(tm));
+ }
+ writefile(timer, queue);
+ break;
+
+ case BATCH:
+ if (queue_set)
+ queue = toupper(queue);
+ else
+ queue = DEFAULT_BATCH_QUEUE;
+
+ if (argc > optind) {
+ /* -t and timespec argument are mutually exclusive */
+ if (time_set) {
+ usage();
+ exit(EXIT_FAILURE);
+ } else {
+ timer = parsetime(argc, argv);
+ time_set = 1;
+ }
+ } else if (!time_set)
+ timer = time(NULL);
+
+ if (atverify) {
+ struct tm *tm = localtime(&timer);
+ (void)fprintf(stderr, "%s\n", asctime(tm));
+ }
+
+ writefile(timer, queue);
+ break;
+
+ default:
+ panic("Internal error");
+ break;
+ }
+ return EXIT_SUCCESS;
+}
diff --git a/usr.bin/at/at.h b/usr.bin/at/at.h
new file mode 100644
index 0000000..64a4a53
--- /dev/null
+++ b/usr.bin/at/at.h
@@ -0,0 +1,43 @@
+/* $NetBSD: at.h,v 1.5 2008/04/05 16:26:57 christos Exp $ */
+
+/*
+ * at.h - header for at(1)
+ * Copyright (C) 1993 Thomas Koenig
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. The name of the author(s) may not be used to endorse or promote
+ * products derived from this software without specific prior written
+ * permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * From: $OpenBSD: at.h,v 1.3 1997/03/01 23:40:09 millert Exp $
+ */
+
+#ifndef _AT_H_
+#define _AT_H_
+
+extern bool fcreated;
+extern char atfile[];
+extern char atverify;
+
+#define AT_MAXJOBS 255 /* max jobs outstanding per user */
+#define AT_VERSION 2.9 /* our version number */
+
+#define DEFAULT_BATCH_QUEUE 'E'
+#define DEFAULT_AT_QUEUE 'c'
+
+#endif /* _AT_H_ */
diff --git a/usr.bin/at/panic.c b/usr.bin/at/panic.c
new file mode 100644
index 0000000..b0b09ca
--- /dev/null
+++ b/usr.bin/at/panic.c
@@ -0,0 +1,110 @@
+/* $NetBSD: panic.c,v 1.14 2016/03/13 00:32:09 dholland Exp $ */
+
+/*
+ * panic.c - terminate fast in case of error
+ * Copyright (c) 1993 by Thomas Koenig
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. The name of the author(s) may not be used to endorse or promote
+ * products derived from this software without specific prior written
+ * permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/* System Headers */
+
+#include <err.h>
+#include <errno.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+/* Local headers */
+
+#include "panic.h"
+#include "at.h"
+#include "privs.h"
+
+/* File scope variables */
+
+#ifndef lint
+#if 0
+static char rcsid[] = "$OpenBSD: panic.c,v 1.4 1997/03/01 23:40:09 millert Exp $";
+#else
+__RCSID("$NetBSD: panic.c,v 1.14 2016/03/13 00:32:09 dholland Exp $");
+#endif
+#endif
+
+/* Global functions */
+
+__dead
+void
+panic(const char *a)
+{
+
+ /*
+ * Something fatal has happened, print error message and exit.
+ */
+ if (fcreated) {
+ privs_enter();
+ (void)unlink(atfile);
+ privs_exit();
+ }
+ errx(EXIT_FAILURE, "%s", a);
+}
+
+__dead
+void
+perr(const char *a)
+{
+
+ /*
+ * Some operating system error; print error message and exit.
+ */
+ perror(a);
+ if (fcreated) {
+ privs_enter();
+ (void)unlink(atfile);
+ privs_exit();
+ }
+ exit(EXIT_FAILURE);
+}
+
+__dead
+void
+privs_fail(const char *msg)
+{
+ perr(msg);
+}
+
+__dead
+void
+usage(void)
+{
+
+ /* Print usage and exit. */
+ (void)fprintf(stderr,
+ "usage: at [-bdlmrVv] [-f file] [-q queue] -t [[CC]YY]MMDDhhmm[.SS]\n"
+ " at [-bdlmrVv] [-f file] [-q queue] time\n"
+ " at [-V] -c job [job ...]\n"
+ " atq [-Vv] [-q queue]\n"
+ " atrm [-V] job [job ...]\n"
+ " batch [-mVv] [-f file] [-q queue] [-t [[CC]YY]MMDDhhmm[.SS]]\n"
+ " batch [-mVv] [-f file] [-q queue] [time]\n");
+ exit(EXIT_FAILURE);
+}
diff --git a/usr.bin/at/panic.h b/usr.bin/at/panic.h
new file mode 100644
index 0000000..3f470fe
--- /dev/null
+++ b/usr.bin/at/panic.h
@@ -0,0 +1,37 @@
+/* $NetBSD: panic.h,v 1.5 2008/04/05 16:26:57 christos Exp $ */
+
+/*
+ * panic.h - header for at(1)
+ * Copyright (c) 1993 Thomas Koenig
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. The name of the author(s) may not be used to endorse or promote
+ * products derived from this software without specific prior written
+ * permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * From: $OpenBSD: panic.h,v 1.3 1997/03/01 23:40:10 millert Exp $
+ */
+
+#ifndef _PANIC_H_
+#define _PANIC_H_
+
+void panic(const char *) __dead;
+void perr(const char *) __dead;
+void usage(void) __dead;
+
+#endif /* _PANIC_H_ */
diff --git a/usr.bin/at/parsetime.c b/usr.bin/at/parsetime.c
new file mode 100644
index 0000000..5b1cc15
--- /dev/null
+++ b/usr.bin/at/parsetime.c
@@ -0,0 +1,652 @@
+/* $NetBSD: parsetime.c,v 1.19 2009/01/18 01:02:31 lukem Exp $ */
+
+/*
+ * parsetime.c - parse time for at(1)
+ * Copyright (C) 1993, 1994 Thomas Koenig
+ *
+ * modifications for english-language times
+ * Copyright (C) 1993 David Parsons
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. The name of the author(s) may not be used to endorse or promote
+ * products derived from this software without specific prior written
+ * permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * at [NOW] PLUS NUMBER MINUTES|HOURS|DAYS|WEEKS
+ * /NUMBER [DOT NUMBER] [AM|PM]\ /[MONTH NUMBER [NUMBER]] \
+ * |NOON | |[TOMORROW] |
+ * |MIDNIGHT | |[DAY OF WEEK] |
+ * \TEATIME / |NUMBER [SLASH NUMBER [SLASH NUMBER]]|
+ * \PLUS NUMBER MINUTES|HOURS|DAYS|WEEKS/
+ */
+
+/* System Headers */
+
+#include <sys/types.h>
+#include <err.h>
+#include <ctype.h>
+#include <errno.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <tzfile.h>
+#include <unistd.h>
+
+/* Local headers */
+
+#include "at.h"
+#include "panic.h"
+#include "parsetime.h"
+#include "stime.h"
+
+/* Structures and unions */
+
+typedef enum { /* symbols */
+ MIDNIGHT, NOON, TEATIME,
+ PM, AM, TOMORROW, TODAY, NOW,
+ MINUTES, HOURS, DAYS, WEEKS, MONTHS, YEARS,
+ NUMBER, PLUS, DOT, SLASH, ID, JUNK,
+ JAN, FEB, MAR, APR, MAY, JUN,
+ JUL, AUG, SEP, OCT, NOV, DEC,
+ SUN, MON, TUE, WED, THU, FRI, SAT,
+ TOKEOF /* EOF marker */
+} tokid_t;
+
+/*
+ * parse translation table - table driven parsers can be your FRIEND!
+ */
+static const struct {
+ const char *name; /* token name */
+ tokid_t value; /* token id */
+ bool plural; /* is this plural? */
+} Specials[] = {
+ {"midnight", MIDNIGHT, false}, /* 00:00:00 of today or tomorrow */
+ {"noon", NOON, false}, /* 12:00:00 of today or tomorrow */
+ {"teatime", TEATIME, false}, /* 16:00:00 of today or tomorrow */
+ {"am", AM, false}, /* morning times for 0-12 clock */
+ {"pm", PM, false}, /* evening times for 0-12 clock */
+ {"tomorrow", TOMORROW, false}, /* execute 24 hours from time */
+ {"today", TODAY, false}, /* execute today - don't advance time */
+ {"now", NOW, false}, /* opt prefix for PLUS */
+
+ {"minute", MINUTES, false}, /* minutes multiplier */
+ {"min", MINUTES, false},
+ {"m", MINUTES, false},
+ {"minutes", MINUTES, true}, /* (pluralized) */
+ {"hour", HOURS, false}, /* hours ... */
+ {"hr", HOURS, false}, /* abbreviated */
+ {"h", HOURS, false},
+ {"hours", HOURS, true}, /* (pluralized) */
+ {"day", DAYS, false}, /* days ... */
+ {"d", DAYS, false},
+ {"days", DAYS, true}, /* (pluralized) */
+ {"week", WEEKS, false}, /* week ... */
+ {"w", WEEKS, false},
+ {"weeks", WEEKS, true}, /* (pluralized) */
+ { "month", MONTHS, 0 }, /* month ... */
+ { "months", MONTHS, 1 }, /* (pluralized) */
+ { "year", YEARS, 0 }, /* year ... */
+ { "years", YEARS, 1 }, /* (pluralized) */
+ {"jan", JAN, false},
+ {"feb", FEB, false},
+ {"mar", MAR, false},
+ {"apr", APR, false},
+ {"may", MAY, false},
+ {"jun", JUN, false},
+ {"jul", JUL, false},
+ {"aug", AUG, false},
+ {"sep", SEP, false},
+ {"oct", OCT, false},
+ {"nov", NOV, false},
+ {"dec", DEC, false},
+ {"january", JAN, false},
+ {"february", FEB, false},
+ {"march", MAR, false},
+ {"april", APR, false},
+ {"may", MAY, false},
+ {"june", JUN, false},
+ {"july", JUL, false},
+ {"august", AUG, false},
+ {"september", SEP, false},
+ {"october", OCT, false},
+ {"november", NOV, false},
+ {"december", DEC, false},
+ {"sunday", SUN, false},
+ {"sun", SUN, false},
+ {"monday", MON, false},
+ {"mon", MON, false},
+ {"tuesday", TUE, false},
+ {"tue", TUE, false},
+ {"wednesday", WED, false},
+ {"wed", WED, false},
+ {"thursday", THU, false},
+ {"thu", THU, false},
+ {"friday", FRI, false},
+ {"fri", FRI, false},
+ {"saturday", SAT, false},
+ {"sat", SAT, false}
+};
+
+/* File scope variables */
+
+static char **scp; /* scanner - pointer at arglist */
+static char scc; /* scanner - count of remaining arguments */
+static char *sct; /* scanner - next char pointer in current argument */
+static bool need; /* scanner - need to advance to next argument */
+
+static char *sc_token; /* scanner - token buffer */
+static size_t sc_len; /* scanner - length of token buffer */
+static tokid_t sc_tokid;/* scanner - token id */
+static bool sc_tokplur; /* scanner - is token plural? */
+
+#ifndef lint
+#if 0
+static char rcsid[] = "$OpenBSD: parsetime.c,v 1.4 1997/03/01 23:40:10 millert Exp $";
+#else
+__RCSID("$NetBSD: parsetime.c,v 1.19 2009/01/18 01:02:31 lukem Exp $");
+#endif
+#endif
+
+/* Local functions */
+static void assign_date(struct tm *, int, int, int);
+static void expect(tokid_t);
+static void init_scanner(int, char **);
+static void month(struct tm *);
+static tokid_t parse_token(char *);
+static void plonk(tokid_t) __dead;
+static void plus(struct tm *);
+static void tod(struct tm *);
+static tokid_t token(void);
+
+/*
+ * parse a token, checking if it's something special to us
+ */
+static tokid_t
+parse_token(char *arg)
+{
+ size_t i;
+
+ for (i=0; i < __arraycount(Specials); i++) {
+ if (strcasecmp(Specials[i].name, arg) == 0) {
+ sc_tokplur = Specials[i].plural;
+ return sc_tokid = Specials[i].value;
+ }
+ }
+
+ /* not special - must be some random id */
+ return ID;
+}
+
+/*
+ * init_scanner() sets up the scanner to eat arguments
+ */
+static void
+init_scanner(int argc, char **argv)
+{
+
+ scp = argv;
+ scc = argc;
+ need = true;
+ sc_len = 1;
+ while (argc-- > 0)
+ sc_len += strlen(*argv++);
+
+ if ((sc_token = malloc(sc_len)) == NULL)
+ panic("Insufficient virtual memory");
+}
+
+/*
+ * token() fetches a token from the input stream
+ */
+static tokid_t
+token(void)
+{
+ int idx;
+
+ for(;;) {
+ (void)memset(sc_token, 0, sc_len);
+ sc_tokid = TOKEOF;
+ sc_tokplur = false;
+ idx = 0;
+
+ /*
+ * if we need to read another argument, walk along the
+ * argument list; when we fall off the arglist, we'll
+ * just return TOKEOF forever
+ */
+ if (need) {
+ if (scc < 1)
+ return sc_tokid;
+ sct = *scp;
+ scp++;
+ scc--;
+ need = false;
+ }
+ /*
+ * eat whitespace now - if we walk off the end of the argument,
+ * we'll continue, which puts us up at the top of the while loop
+ * to fetch the next argument in
+ */
+ while (isspace((unsigned char)*sct))
+ ++sct;
+ if (!*sct) {
+ need = true;
+ continue;
+ }
+
+ /*
+ * preserve the first character of the new token
+ */
+ sc_token[0] = *sct++;
+
+ /*
+ * then see what it is
+ */
+ if (isdigit((unsigned char)sc_token[0])) {
+ while (isdigit((unsigned char)*sct))
+ sc_token[++idx] = *sct++;
+ sc_token[++idx] = 0;
+ return sc_tokid = NUMBER;
+ } else if (isalpha((unsigned char)sc_token[0])) {
+ while (isalpha((unsigned char)*sct))
+ sc_token[++idx] = *sct++;
+ sc_token[++idx] = 0;
+ return parse_token(sc_token);
+ }
+ else if (sc_token[0] == ':' || sc_token[0] == '.')
+ return sc_tokid = DOT;
+ else if (sc_token[0] == '+')
+ return sc_tokid = PLUS;
+ else if (sc_token[0] == '/')
+ return sc_tokid = SLASH;
+ else
+ return sc_tokid = JUNK;
+ }
+}
+
+/*
+ * plonk() gives an appropriate error message if a token is incorrect
+ */
+__dead
+static void
+plonk(tokid_t tok)
+{
+
+ panic(tok == TOKEOF ? "incomplete time" : "garbled time");
+}
+
+/*
+ * expect() gets a token and dies most horribly if it's not the token we want
+ */
+static void
+expect(tokid_t desired)
+{
+
+ if (token() != desired)
+ plonk(sc_tokid); /* and we die here... */
+}
+
+/*
+ * plus() parses a now + time
+ *
+ * at [NOW] PLUS NUMBER [MINUTES|HOURS|DAYS|WEEKS|MONTHS|YEARS]
+ *
+ */
+static void
+plus(struct tm *tm)
+{
+ int delay;
+ int expectplur;
+
+ expect(NUMBER);
+
+ delay = atoi(sc_token);
+ expectplur = delay != 1;
+
+ switch (token()) {
+ case YEARS:
+ tm->tm_year += delay;
+ break;
+ case MONTHS:
+ tm->tm_mon += delay;
+ break;
+ case WEEKS:
+ delay *= 7;
+ /*FALLTHROUGH*/
+ case DAYS:
+ tm->tm_mday += delay;
+ break;
+ case HOURS:
+ tm->tm_hour += delay;
+ break;
+ case MINUTES:
+ tm->tm_min += delay;
+ break;
+ default:
+ plonk(sc_tokid);
+ break;
+ }
+
+ if (expectplur != sc_tokplur)
+ warnx("pluralization is wrong");
+
+ tm->tm_isdst = -1;
+ if (mktime(tm) == -1)
+ plonk(sc_tokid);
+}
+
+/*
+ * tod() computes the time of day
+ * [NUMBER [DOT NUMBER] [AM|PM]]
+ */
+static void
+tod(struct tm *tm)
+{
+ int hour, minute;
+ size_t tlen;
+
+ minute = 0;
+ hour = atoi(sc_token);
+ tlen = strlen(sc_token);
+
+ /*
+ * first pick out the time of day - if it's 4 digits, we assume
+ * a HHMM time, otherwise it's HH DOT MM time
+ */
+ if (token() == DOT) {
+ expect(NUMBER);
+ minute = atoi(sc_token);
+ (void)token();
+ } else if (tlen == 4) {
+ minute = hour % 100;
+ hour = hour / 100;
+ }
+
+ if (minute > 59)
+ panic("garbled time");
+
+ /*
+ * check if an AM or PM specifier was given
+ */
+ if (sc_tokid == AM || sc_tokid == PM) {
+ if (hour > 12)
+ panic("garbled time");
+
+ if (sc_tokid == PM) {
+ if (hour != 12) /* 12:xx PM is 12:xx, not 24:xx */
+ hour += 12;
+ } else {
+ if (hour == 12) /* 12:xx AM is 00:xx, not 12:xx */
+ hour = 0;
+ }
+ (void)token();
+ } else if (hour > 23)
+ panic("garbled time");
+
+ /*
+ * if we specify an absolute time, we don't want to bump the day even
+ * if we've gone past that time - but if we're specifying a time plus
+ * a relative offset, it's okay to bump things
+ */
+ if ((sc_tokid == TOKEOF || sc_tokid == PLUS) && tm->tm_hour > hour) {
+ tm->tm_mday++;
+ tm->tm_wday++;
+ }
+
+ tm->tm_hour = hour;
+ tm->tm_min = minute;
+}
+
+/*
+ * assign_date() assigns a date, wrapping to next year if needed.
+ * Accept years in 4-digit, 2-digit, or current year (-1).
+ */
+static void
+assign_date(struct tm *tm, int mday, int mon, int year)
+{
+
+ if (year > 99) { /* four digit year */
+ if (year >= TM_YEAR_BASE)
+ tm->tm_year = year - TM_YEAR_BASE;
+ else
+ panic("garbled time");
+ }
+ else if (year >= 0) { /* two digit year */
+ tm->tm_year = conv_2dig_year(year) - TM_YEAR_BASE;
+ }
+ else if (year == -1) { /* year not given (use default in tm) */
+ /* allow for 1 year range from current date */
+ if (tm->tm_mon > mon ||
+ (tm->tm_mon == mon && tm->tm_mday > mday))
+ tm->tm_year++;
+ }
+ else
+ panic("invalid year");
+
+ tm->tm_mday = mday;
+ tm->tm_mon = mon;
+}
+
+/*
+ * month() picks apart a month specification
+ *
+ * /[<month> NUMBER [NUMBER]] \
+ * |[TOMORROW] |
+ * |[DAY OF WEEK] |
+ * |NUMBER [SLASH NUMBER [SLASH NUMBER]]|
+ * \PLUS NUMBER MINUTES|HOURS|DAYS|WEEKS/
+ */
+static void
+month(struct tm *tm)
+{
+ int year;
+ int mday, wday, mon;
+ size_t tlen;
+
+ year = -1;
+ mday = 0;
+ switch (sc_tokid) {
+ case PLUS:
+ plus(tm);
+ break;
+
+ case TOMORROW:
+ /* do something tomorrow */
+ tm->tm_mday++;
+ tm->tm_wday++;
+ /*FALLTHROUGH*/
+ case TODAY:
+ /* force ourselves to stay in today - no further processing */
+ (void)token();
+ break;
+
+ case JAN: case FEB: case MAR: case APR: case MAY: case JUN:
+ case JUL: case AUG: case SEP: case OCT: case NOV: case DEC:
+ /*
+ * do month mday [year]
+ */
+ mon = sc_tokid - JAN;
+ expect(NUMBER);
+ mday = atoi(sc_token);
+ if (token() == NUMBER) {
+ year = atoi(sc_token);
+ (void)token();
+ }
+ assign_date(tm, mday, mon, year);
+ break;
+
+ case SUN: case MON: case TUE:
+ case WED: case THU: case FRI:
+ case SAT:
+ /* do a particular day of the week */
+ wday = sc_tokid - SUN;
+
+ mday = tm->tm_mday;
+
+ /* if this day is < today, then roll to next week */
+ if (wday < tm->tm_wday)
+ mday += 7 - (tm->tm_wday - wday);
+ else
+ mday += (wday - tm->tm_wday);
+
+ tm->tm_wday = wday;
+
+ assign_date(tm, mday, tm->tm_mon, tm->tm_year + TM_YEAR_BASE);
+ break;
+
+ case NUMBER:
+ /*
+ * get numeric MMDDYY, mm/dd/yy, or dd.mm.yy
+ */
+ tlen = strlen(sc_token);
+ mon = atoi(sc_token);
+ (void)token();
+
+ if (sc_tokid == SLASH || sc_tokid == DOT) {
+ tokid_t sep;
+
+ sep = sc_tokid;
+ expect(NUMBER);
+ mday = atoi(sc_token);
+ if (token() == sep) {
+ expect(NUMBER);
+ year = atoi(sc_token);
+ (void)token();
+ }
+
+ /*
+ * flip months and days for european timing
+ */
+ if (sep == DOT) {
+ int x = mday;
+ mday = mon;
+ mon = x;
+ }
+ } else if (tlen == 6 || tlen == 8) {
+ if (tlen == 8) {
+ year = (mon % 10000) - 1900;
+ mon /= 10000;
+ } else {
+ year = mon % 100;
+ mon /= 100;
+ }
+ mday = mon % 100;
+ mon /= 100;
+ } else
+ panic("garbled time");
+
+ mon--;
+ if (mon < 0 || mon > 11 || mday < 1 || mday > 31)
+ panic("garbled time");
+
+ assign_date(tm, mday, mon, year);
+ break;
+ default:
+ /* plonk(sc_tokid); */ /* XXX - die here? */
+ break;
+ }
+}
+
+
+/* Global functions */
+
+time_t
+parsetime(int argc, char **argv)
+{
+ /*
+ * Do the argument parsing, die if necessary, and return the
+ * time the job should be run.
+ */
+ time_t nowtimer, runtimer;
+ struct tm nowtime, runtime;
+ int hr = 0; /* this MUST be initialized to zero for
+ midnight/noon/teatime */
+
+ nowtimer = time(NULL);
+ nowtime = *localtime(&nowtimer);
+
+ runtime = nowtime;
+ runtime.tm_sec = 0;
+
+ if (argc <= optind)
+ usage();
+
+ init_scanner(argc - optind, argv + optind);
+
+ switch (token()) {
+ case NOW:
+ if (scc < 1)
+ return nowtimer;
+
+ /* now is optional prefix for PLUS tree */
+ expect(PLUS);
+ /*FALLTHROUGH*/
+ case PLUS:
+ plus(&runtime);
+ break;
+
+ case NUMBER:
+ tod(&runtime);
+ month(&runtime);
+ break;
+
+ /*
+ * evil coding for TEATIME|NOON|MIDNIGHT - we've initialised
+ * hr to zero up above, then fall into this case in such a
+ * way so we add +12 +4 hours to it for teatime, +12 hours
+ * to it for noon, and nothing at all for midnight, then
+ * set our runtime to that hour before leaping into the
+ * month scanner
+ */
+ case TEATIME:
+ hr += 4;
+ /*FALLTHROUGH*/
+ case NOON:
+ hr += 12;
+ /*FALLTHROUGH*/
+ case MIDNIGHT:
+ if (runtime.tm_hour >= hr) {
+ runtime.tm_mday++;
+ runtime.tm_wday++;
+ }
+ runtime.tm_hour = hr;
+ runtime.tm_min = 0;
+ (void)token();
+ /*FALLTHROUGH*/ /* fall through to month setting */
+ default:
+ month(&runtime);
+ break;
+ }
+ expect(TOKEOF);
+
+ /*
+ * adjust for daylight savings time
+ */
+ runtime.tm_isdst = -1;
+ runtimer = mktime(&runtime);
+
+ if (runtimer == (time_t)-1)
+ panic("Invalid time");
+
+ if (nowtimer > runtimer)
+ panic("Trying to travel back in time");
+
+ return runtimer;
+}
diff --git a/usr.bin/at/parsetime.h b/usr.bin/at/parsetime.h
new file mode 100644
index 0000000..a2c9a69
--- /dev/null
+++ b/usr.bin/at/parsetime.h
@@ -0,0 +1,35 @@
+/* $NetBSD: parsetime.h,v 1.5 2008/04/05 16:26:57 christos Exp $ */
+
+/*
+ * parsetime.h - header for at(1)
+ * Copyright (c) 1993 Thomas Koenig
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. The name of the author(s) may not be used to endorse or promote
+ * products derived from this software without specific prior written
+ * permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * From: $OpenBSD: parsetime.h,v 1.3 1997/03/01 23:40:11 millert Exp $
+ */
+
+#ifndef _PARSETIME_H_
+#define _PARSETIME_H_
+
+time_t parsetime(int, char **);
+
+#endif /* _PARSETIME_H_ */
diff --git a/usr.bin/at/pathnames.h b/usr.bin/at/pathnames.h
new file mode 100644
index 0000000..88e6a53
--- /dev/null
+++ b/usr.bin/at/pathnames.h
@@ -0,0 +1,51 @@
+/* $NetBSD: pathnames.h,v 1.8 2008/04/05 16:26:57 christos Exp $ */
+
+/*
+ * Copyright (c) 1993 Christopher G. Demetriou
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. All advertising materials mentioning features or use of this software
+ * must display the following acknowledgement:
+ * This product includes software developed for the
+ * NetBSD Project. See http://www.NetBSD.org/ for
+ * information about NetBSD.
+ * 4. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * <<Id: LICENSE,v 1.2 2000/06/14 15:57:33 cgd Exp>>
+ *
+ * From: $OpenBSD: pathnames.h,v 1.3 1997/03/01 23:40:11 millert Exp $
+ */
+
+#ifndef _PATHNAMES_H_
+#define _PATHNAMES_H_
+
+#include <paths.h>
+
+#define _PATH_ATJOBS "/var/at/jobs/"
+#define _PATH_ATSPOOL "/var/at/spool/"
+#define _PATH_LOCKFILE "/var/at/.lockfile"
+#define _PATH_SEQFILE "/var/at/.SEQ"
+#define _PATH_AT_ALLOW "/var/at/at.allow"
+#define _PATH_AT_DENY "/var/at/at.deny"
+
+#endif /* _PATHNAMES_H_ */
diff --git a/usr.bin/at/perm.c b/usr.bin/at/perm.c
new file mode 100644
index 0000000..64e4fbb
--- /dev/null
+++ b/usr.bin/at/perm.c
@@ -0,0 +1,118 @@
+/* $NetBSD: perm.c,v 1.4 2016/03/13 00:32:09 dholland Exp $ */
+
+/*
+ * perm.c - check user permission for at(1)
+ * Copyright (C) 1994 Thomas Koenig
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. The name of the author(s) may not be used to endorse or promote
+ * products derived from this software without specific prior written
+ * permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/* System Headers */
+
+#include <sys/types.h>
+#include <errno.h>
+#include <pwd.h>
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+/* Local headers */
+
+#include "at.h"
+#include "panic.h"
+#include "pathnames.h"
+#include "privs.h"
+#include "perm.h"
+
+/* File scope variables */
+
+#ifndef lint
+#if 0
+static char rcsid[] = "$OpenBSD: perm.c,v 1.1 1997/03/01 23:40:12 millert Exp $";
+#else
+__RCSID("$NetBSD: perm.c,v 1.4 2016/03/13 00:32:09 dholland Exp $");
+#endif
+#endif
+
+/* Local functions */
+
+static bool
+check_for_user(FILE *fp, const char *name)
+{
+ char *buffer;
+ size_t len;
+ bool found = false;
+
+ len = strlen(name);
+ if ((buffer = malloc(len + 2)) == NULL)
+ panic("Insufficient virtual memory");
+
+ while (fgets(buffer, (int)len + 2, fp) != NULL) {
+ if (strncmp(name, buffer, len) == 0 && buffer[len] == '\n') {
+ found = true;
+ break;
+ }
+ }
+ (void)fclose(fp);
+ free(buffer);
+ return found;
+}
+
+/* Global functions */
+
+bool
+check_permission(void)
+{
+ FILE *fp;
+ uid_t uid = geteuid();
+ struct passwd *pentry;
+
+ if (uid == 0)
+ return true;
+
+ if ((pentry = getpwuid(uid)) == NULL) {
+ perror("Cannot access user database");
+ exit(EXIT_FAILURE);
+ }
+
+ privs_enter();
+
+ fp = fopen(_PATH_AT_ALLOW, "r");
+
+ privs_exit();
+
+ if (fp != NULL) {
+ return check_for_user(fp, pentry->pw_name);
+ } else {
+ privs_enter();
+
+ fp = fopen(_PATH_AT_DENY, "r");
+
+ privs_exit();
+
+ if (fp != NULL)
+ return !check_for_user(fp, pentry->pw_name);
+ }
+ return false;
+}
diff --git a/usr.bin/at/perm.h b/usr.bin/at/perm.h
new file mode 100644
index 0000000..89d0178
--- /dev/null
+++ b/usr.bin/at/perm.h
@@ -0,0 +1,33 @@
+/* $NetBSD: perm.h,v 1.3 2008/04/05 16:26:57 christos Exp $ */
+
+/*
+ * perm.h - header for at(1)
+ * Copyright (C) 1994 Thomas Koenig
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. The name of the author(s) may not be used to endorse or promote
+ * products derived from this software without specific prior written
+ * permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef _PERM_H_
+#define _PERM_H_
+
+bool check_permission(void);
+
+#endif /* _PERM_H_ */
diff --git a/usr.bin/at/privs.c b/usr.bin/at/privs.c
new file mode 100644
index 0000000..1b7e12d
--- /dev/null
+++ b/usr.bin/at/privs.c
@@ -0,0 +1,104 @@
+/* $NetBSD: privs.c,v 1.3 2018/02/25 23:48:16 htodd Exp $ */
+
+/*
+ * privs.c - privileged operations
+ * Copyright (C) 1993 Thomas Koenig
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. The name of the author(s) may not be used to endorse or promote
+ * products derived from this software without specific prior written
+ * permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * From: OpenBSD: privs.h,v 1.4 1997/03/01 23:40:12 millert Exp
+ */
+
+#include <unistd.h>
+
+#include "privs.h"
+
+/*
+ * Used by: usr.bin/at
+ * Used by: libexec/atrun
+ */
+
+/*
+ * Relinquish privileges temporarily for a setuid or setgid program
+ * with the option of getting them back later. This is done by
+ * using POSIX saved user and groups ids. Call RELINQUISH_PRIVS once
+ * at the beginning of the main program. This will cause all operations
+ * to be executed with the real userid. When you need the privileges
+ * of the setuid/setgid invocation, call PRIV_START; when you no longer
+ * need it, call PRIV_END. Note that it is an error to call PRIV_START
+ * and not PRIV_END within the same function.
+ *
+ * Use RELINQUISH_PRIVS_ROOT(a,b) if your program started out running
+ * as root, and you want to drop back the effective userid to a
+ * and the effective group id to b, with the option to get them back
+ * later.
+ *
+ * Problems: Do not use return between PRIV_START and PRIV_END; this
+ * will cause the program to continue running in an unprivileged
+ * state.
+ *
+ * It is NOT safe to call exec(), system() or popen() with a user-
+ * supplied program (i.e. without carefully checking PATH and any
+ * library load paths) with relinquished privileges; the called program
+ * can acquire them just as easily. Set both effective and real userid
+ * to the real userid before calling any of them.
+ */
+
+uid_t real_uid, effective_uid;
+gid_t real_gid, effective_gid;
+
+void
+privs_enter(void)
+{
+ if (seteuid(effective_uid) == -1)
+ privs_fail("Cannot get user privs");
+ if (setegid(effective_gid) == -1)
+ privs_fail("Cannot get group privs");
+}
+
+void
+privs_exit(void)
+{
+ if (setegid(real_gid) == -1)
+ privs_fail("Cannot relinquish group privs");
+ if (seteuid(real_uid) == -1)
+ privs_fail("Cannot relinquish user privs");
+}
+
+void
+privs_relinquish(void)
+{
+ real_uid = getuid();
+ effective_uid = geteuid();
+ real_gid = getgid();
+ effective_gid = getegid();
+ privs_exit();
+}
+
+void
+privs_relinquish_root(uid_t ruid, gid_t rgid)
+{
+ real_uid = ruid;
+ real_gid = rgid;
+ effective_uid = geteuid();
+ effective_gid = getegid();
+ privs_exit();
+}
diff --git a/usr.bin/at/privs.h b/usr.bin/at/privs.h
new file mode 100644
index 0000000..4344cbe
--- /dev/null
+++ b/usr.bin/at/privs.h
@@ -0,0 +1,76 @@
+/* $NetBSD: privs.h,v 1.10 2016/03/13 00:33:12 dholland Exp $ */
+
+/*
+ * privs.h - header for privileged operations
+ * Copyright (C) 1993 Thomas Koenig
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. The name of the author(s) may not be used to endorse or promote
+ * products derived from this software without specific prior written
+ * permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * From: OpenBSD: privs.h,v 1.4 1997/03/01 23:40:12 millert Exp
+ */
+
+#ifndef _PRIVS_H_
+#define _PRIVS_H_
+
+/*
+ * Used by: usr.bin/at
+ * Used by: libexec/atrun
+ */
+
+/*
+ * Relinquish privileges temporarily for a setuid or setgid program
+ * with the option of getting them back later. This is done by
+ * using POSIX saved user and groups ids. Call RELINQUISH_PRIVS once
+ * at the beginning of the main program. This will cause all operations
+ * to be executed with the real userid. When you need the privileges
+ * of the setuid/setgid invocation, call PRIV_START; when you no longer
+ * need it, call PRIV_END. Note that it is an error to call PRIV_START
+ * and not PRIV_END within the same function.
+ *
+ * Use RELINQUISH_PRIVS_ROOT(a,b) if your program started out running
+ * as root, and you want to drop back the effective userid to a
+ * and the effective group id to b, with the option to get them back
+ * later.
+ *
+ * Problems: Do not use return between PRIV_START and PRIV_END; this
+ * will cause the program to continue running in an unprivileged
+ * state.
+ *
+ * It is NOT safe to call exec(), system() or popen() with a user-
+ * supplied program (i.e. without carefully checking PATH and any
+ * library load paths) with relinquished privileges; the called program
+ * can acquire them just as easily. Set both effective and real userid
+ * to the real userid before calling any of them.
+ */
+
+extern uid_t real_uid, effective_uid;
+extern gid_t real_gid, effective_gid;
+
+void privs_relinquish(void);
+void privs_relinquish_root(uid_t ruid, gid_t rgid);
+
+void privs_enter(void);
+void privs_exit(void);
+
+/* caller provides this */
+__dead void privs_fail(const char *msg);
+
+#endif /* _PRIV_H_ */
diff --git a/usr.bin/at/stime.c b/usr.bin/at/stime.c
new file mode 100644
index 0000000..567b848
--- /dev/null
+++ b/usr.bin/at/stime.c
@@ -0,0 +1,116 @@
+/* $NetBSD: stime.c,v 1.7 2008/07/21 14:19:21 lukem Exp $ */
+
+/*
+ * Copyright (c) 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+#ifndef lint
+__COPYRIGHT("@(#) Copyright (c) 1993\
+ The Regents of the University of California. All rights reserved.");
+#endif /* not lint */
+
+#ifndef lint
+#if 0
+static char sccsid[] = "from: @(#)touch.c 8.2 (Berkeley) 4/28/95";
+#endif
+__RCSID("$NetBSD: stime.c,v 1.7 2008/07/21 14:19:21 lukem Exp $");
+#endif /* not lint */
+
+#include <err.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <tzfile.h>
+
+#include "stime.h"
+
+#define ATOI2(s) ((s) += 2, ((s)[-2] - '0') * 10 + ((s)[-1] - '0'))
+
+time_t
+stime(char *arg)
+{
+ struct tm *t;
+ time_t tmptime;
+ int yearset;
+ char *p;
+ /* Start with the current time. */
+ if (time(&tmptime) == (time_t)-1)
+ err(EXIT_FAILURE, "time");
+
+ if ((t = localtime(&tmptime)) == NULL)
+ err(EXIT_FAILURE, "localtime");
+ /* [[CC]YY]MMDDhhmm[.SS] */
+ if ((p = strchr(arg, '.')) == NULL)
+ t->tm_sec = 0; /* Seconds defaults to 0. */
+ else {
+ if (strlen(p + 1) != 2)
+ goto terr;
+ *p++ = '\0';
+ t->tm_sec = ATOI2(p);
+ }
+
+ yearset = 0;
+ switch (strlen(arg)) {
+ case 12: /* CCYYMMDDhhmm */
+ t->tm_year = ATOI2(arg) * 100 - TM_YEAR_BASE;
+ yearset = 1;
+ /* FALLTHROUGH */
+ case 10: /* YYMMDDhhmm */
+ if (yearset) {
+ t->tm_year += ATOI2(arg);
+ } else {
+ yearset = ATOI2(arg);
+ t->tm_year = conv_2dig_year(yearset) - TM_YEAR_BASE;
+ }
+ /* FALLTHROUGH */
+ case 8: /* MMDDhhmm */
+ t->tm_mon = ATOI2(arg);
+ --t->tm_mon; /* Convert from 01-12 to 00-11 */
+ /* FALLTHROUGH */
+ case 6:
+ t->tm_mday = ATOI2(arg);
+ /* FALLTHROUGH */
+ case 4:
+ t->tm_hour = ATOI2(arg);
+ /* FALLTHROUGH */
+ case 2:
+ t->tm_min = ATOI2(arg);
+ break;
+ default:
+ goto terr;
+ }
+
+ t->tm_isdst = -1; /* Figure out DST. */
+ tmptime = mktime(t);
+ if (tmptime == (time_t)-1) {
+ terr: errx(EXIT_FAILURE,
+ "out of range or illegal time specification: [[CC]YY]MMDDhhmm[.SS]");
+ }
+ return tmptime;
+}
diff --git a/usr.bin/at/stime.h b/usr.bin/at/stime.h
new file mode 100644
index 0000000..266953e
--- /dev/null
+++ b/usr.bin/at/stime.h
@@ -0,0 +1,53 @@
+/* $NetBSD: stime.h,v 1.4 2008/04/05 16:26:57 christos Exp $ */
+
+/*
+ * Copyright (c) 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#ifndef _STIME_H_
+#define _STIME_H_
+
+/*
+ * Convert a 2-digit year to a valid year as:
+ * 00-68 <-> 2000-2068
+ * 69-99 <-> 1969-1999
+ *
+ * Note: We choose the earliest year as 1969 rather than 1970 so that
+ * dates near the Epoch (Jan 1, 1970 00:00:00 UTC) can be represented
+ * with 2-digit years in all time zones. This is the convention used
+ * in parsedate(3), though it is not consistently applied in the
+ * source tree.
+ *
+ * XXX - move this (or something like it) to time.h and use it
+ * consistently in the tree.
+ */
+#define conv_2dig_year(y) ((y) < 69 ? (y) + 2000 : (y) + 1900)
+
+time_t stime(char *);
+
+#endif /* _STIME_H_ */
diff --git a/usr.bin/basename/basename.1 b/usr.bin/basename/basename.1
new file mode 100644
index 0000000..166b506
--- /dev/null
+++ b/usr.bin/basename/basename.1
@@ -0,0 +1,92 @@
+.\" $NetBSD: basename.1,v 1.18 2017/07/03 21:34:18 wiz Exp $
+.\"
+.\" Copyright (c) 1990, 1993, 1994
+.\" The Regents of the University of California. All rights reserved.
+.\"
+.\" This code is derived from software contributed to Berkeley by
+.\" the Institute of Electrical and Electronics Engineers, Inc.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\" notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must reproduce the above copyright
+.\" notice, this list of conditions and the following disclaimer in the
+.\" documentation and/or other materials provided with the distribution.
+.\" 3. Neither the name of the University nor the names of its contributors
+.\" may be used to endorse or promote products derived from this software
+.\" without specific prior written permission.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+.\" ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+.\" SUCH DAMAGE.
+.\"
+.\" @(#)basename.1 8.2 (Berkeley) 4/18/94
+.\"
+.Dd April 18, 1994
+.Dt BASENAME 1
+.Os
+.Sh NAME
+.Nm basename ,
+.Nm dirname
+.Nd return filename or directory portion of pathname
+.Sh SYNOPSIS
+.Nm
+.Ar string
+.Op Ar suffix
+.Nm dirname
+.Ar string
+.Sh DESCRIPTION
+.Nm
+deletes any prefix ending with the last slash
+.Ql \&/
+character present in
+.Ar string ,
+and a
+.Ar suffix ,
+if given.
+The resulting filename is written to the standard output.
+A non-existent suffix is ignored.
+.Pp
+.Nm dirname
+deletes the filename portion, beginning
+with the last slash
+.Ql \&/
+character to the end of
+.Ar string ,
+and writes the result to the standard output.
+.Sh EXIT STATUS
+Both the
+.Nm
+and
+.Nm dirname
+utilities
+exit 0 on success, and >0 if an error occurs.
+.Sh EXAMPLES
+The following line sets the shell variable
+.Ev FOO
+to
+.Pa /usr/bin .
+.Pp
+.Dl FOO=`dirname /usr/bin/trail`
+.Sh SEE ALSO
+.Xr csh 1 ,
+.Xr sh 1 ,
+.Xr basename 3 ,
+.Xr dirname 3
+.Sh STANDARDS
+The
+.Nm
+and
+.Nm dirname
+utilities conform to
+.St -p1003.2-92 .
diff --git a/usr.bin/basename/basename.c b/usr.bin/basename/basename.c
new file mode 100644
index 0000000..78b5d8b
--- /dev/null
+++ b/usr.bin/basename/basename.c
@@ -0,0 +1,103 @@
+/* $NetBSD: basename.c,v 1.16 2019/02/01 08:29:04 mrg Exp $ */
+
+/*-
+ * Copyright (c) 1991, 1993, 1994
+ * The Regents of the University of California. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+#ifndef lint
+__COPYRIGHT("@(#) Copyright (c) 1991, 1993, 1994\
+ The Regents of the University of California. All rights reserved.");
+#endif /* not lint */
+
+#ifndef lint
+#if 0
+static char sccsid[] = "@(#)basename.c 8.4 (Berkeley) 5/4/95";
+#endif
+__RCSID("$NetBSD: basename.c,v 1.16 2019/02/01 08:29:04 mrg Exp $");
+#endif /* not lint */
+
+#include <err.h>
+#include <libgen.h>
+#include <locale.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+__dead static void usage(void);
+
+int
+main(int argc, char **argv)
+{
+ char *p;
+ int ch;
+
+ setlocale(LC_ALL, "");
+
+ while ((ch = getopt(argc, argv, "")) != -1)
+ switch (ch) {
+ case '?':
+ default:
+ usage();
+ }
+ argc -= optind;
+ argv += optind;
+
+ if (argc != 1 && argc != 2)
+ usage();
+
+ if (**argv == '\0') {
+ (void)printf("\n");
+ exit(0);
+ }
+ if ((p = basename(*argv)) == NULL)
+ err(1, "%s", *argv);
+ if (*++argv != NULL) {
+ int suffixlen, stringlen, off;
+
+ suffixlen = strlen(*argv);
+ stringlen = strlen(p);
+
+ if (suffixlen < stringlen) {
+ off = stringlen - suffixlen;
+ if (strcmp(p + off, *argv) == 0)
+ p[off] = '\0';
+ }
+ }
+ (void)printf("%s\n", p);
+ exit(0);
+}
+
+static void
+usage(void)
+{
+
+ (void)fprintf(stderr, "usage: basename string [suffix]\n");
+ exit(1);
+}
diff --git a/usr.bin/c99/c99.1 b/usr.bin/c99/c99.1
new file mode 100644
index 0000000..0ac95ae
--- /dev/null
+++ b/usr.bin/c99/c99.1
@@ -0,0 +1,81 @@
+.\" $NetBSD: c99.1,v 1.1 2016/01/14 04:27:26 christos Exp $
+.\"
+.\" Copyright (c) 1999-2008 The NetBSD Foundation, Inc.
+.\" All rights reserved.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\" notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must reproduce the above copyright
+.\" notice, this list of conditions and the following disclaimer in the
+.\" documentation and/or other materials provided with the distribution.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
+.\" ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+.\" TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+.\" PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
+.\" BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+.\" CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+.\" SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+.\" INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+.\" CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+.\" ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+.\" POSSIBILITY OF SUCH DAMAGE.
+.\"
+.Dd September 24, 2008
+.Dt C99 1
+.Os
+.Sh NAME
+.Nm c99
+.Nd ANSI (1999) C compiler
+.Sh SYNOPSIS
+.Nm
+.Op Fl pedantic
+.Op Fl pedantic-errors
+.Op Fl D_ANSI_SOURCE
+.Op options ...
+.Sh DESCRIPTION
+Calls the C compiler (cc) with the given
+.Ar options ,
+using a C language environment compatible with the
+.St -isoC-99
+specification.
+.Pp
+This includes
+inline functions, variable-length arrays, support for one-line
+comments beginning with //,
+disabling non-ANSI compiler features (such as
+.Ar asm ,
+.Ar typeof ,
+and the $ character in identifiers),
+and definition of the preprocessor symbol
+.Ev __STRICT_ANSI__ .
+.Pp
+The following options are available:
+.Bl -tag -width "-pedantic-errorsxx"
+.It Fl pedantic
+Issue extra warnings defined by ANSI for use of non-ANSI features.
+.It Fl pedantic-errors
+Issue errors instead of warnings that normally would be presented by
+.Fl pedantic .
+.It Fl D_ANSI_SOURCE
+Tell the system header file set to use an ANSI-conformant "clean" namespace.
+.El
+.Sh SEE ALSO
+.Xr cc 1
+.Sh STANDARDS
+.Nm
+conforms to
+.St -p1003.2-92 .
+.Sh HISTORY
+.Nm
+first appeared in
+.Nx 5.0 .
+.Sh BUGS
+Since
+.Nm
+is a shell wrapper script to
+.Ar cc ,
+compile errors are prefixed by "cc:".
diff --git a/usr.bin/c99/c99.sh b/usr.bin/c99/c99.sh
new file mode 100644
index 0000000..15cab47
--- /dev/null
+++ b/usr.bin/c99/c99.sh
@@ -0,0 +1,2 @@
+#!/bin/sh
+exec /usr/bin/cc -std=c99 "$@"
diff --git a/usr.bin/cal/README b/usr.bin/cal/README
new file mode 100644
index 0000000..638ac9d
--- /dev/null
+++ b/usr.bin/cal/README
@@ -0,0 +1,42 @@
+The cal(1) date routines were written from scratch, basically from first
+principles. The algorithm for calculating the day of week from any
+Gregorian date was "reverse engineered". This was necessary as most of
+the documented algorithms have to do with date calculations for other
+calendars (e.g. julian) and are only accurate when converted to gregorian
+within a narrow range of dates.
+
+1 Jan 1 is a Saturday because that's what cal says and I couldn't change
+that even if I was dumb enough to try. From this we can easily calculate
+the day of week for any date. The algorithm for a zero based day of week:
+
+ calculate the number of days in all prior years (year-1)*365
+ add the number of leap years (days?) since year 1
+ (not including this year as that is covered later)
+ add the day number within the year
+ this compensates for the non-inclusive leap year
+ calculation
+ if the day in question occurs before the gregorian reformation
+ (3 sep 1752 for our purposes), then simply return
+ (value so far - 1 + SATURDAY's value of 6) modulo 7.
+ if the day in question occurs during the reformation (3 sep 1752
+ to 13 sep 1752 inclusive) return THURSDAY. This is my
+ idea of what happened then. It does not matter much as
+ this program never tries to find day of week for any day
+ that is not the first of a month.
+ otherwise, after the reformation, use the same formula as the
+ days before with the additional step of subtracting the
+ number of days (11) that were adjusted out of the calendar
+ just before taking the modulo.
+
+It must be noted that the number of leap years calculation is sensitive
+to the date for which the leap year is being calculated. A year that occurs
+before the reformation is determined to be a leap year if its modulo of
+4 equals zero. But after the reformation, a year is only a leap year if
+its modulo of 4 equals zero and its modulo of 100 does not. Of course,
+there is an exception for these century years. If the modulo of 400 equals
+zero, then the year is a leap year anyway. This is, in fact, what the
+gregorian reformation was all about (a bit of error in the old algorithm
+that caused the calendar to be inaccurate.)
+
+Once we have the day in year for the first of the month in question, the
+rest is trivial.
diff --git a/usr.bin/cal/cal.1 b/usr.bin/cal/cal.1
new file mode 100644
index 0000000..c99cd5d
--- /dev/null
+++ b/usr.bin/cal/cal.1
@@ -0,0 +1,164 @@
+.\" $NetBSD: cal.1,v 1.23 2018/05/29 08:37:33 wiz Exp $
+.\"
+.\" Copyright (c) 1989, 1990, 1993
+.\" The Regents of the University of California. All rights reserved.
+.\"
+.\" This code is derived from software contributed to Berkeley by
+.\" Kim Letkeman.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\" notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must reproduce the above copyright
+.\" notice, this list of conditions and the following disclaimer in the
+.\" documentation and/or other materials provided with the distribution.
+.\" 3. Neither the name of the University nor the names of its contributors
+.\" may be used to endorse or promote products derived from this software
+.\" without specific prior written permission.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+.\" ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+.\" SUCH DAMAGE.
+.\"
+.\" @(#)cal.1 8.2 (Berkeley) 4/28/95
+.\"
+.Dd May 29, 2018
+.Dt CAL 1
+.Os
+.Sh NAME
+.Nm cal
+.Nd displays a calendar
+.Sh SYNOPSIS
+.Nm
+.Op Fl 3hjry
+.Op Fl A Ar after
+.Op Fl B Ar before
+.Op Fl C Ar context
+.Op Fl d Ar day-of-week
+.Op Fl R Ar reform-spec
+.Op Oo Ar month Oc Ar year
+.Sh DESCRIPTION
+.Nm
+displays a simple calendar.
+If arguments are not specified,
+the current month is displayed.
+The options are as follows:
+.Bl -tag -width Ds
+.It Fl 3
+Same as
+.Dq Fl A Ar 1 Fl B Ar 1 .
+.It Fl A Ar after
+Display
+.Ar after
+months after the specified month.
+.It Fl B Ar before
+Display
+.Ar before
+months before the specified month.
+.It Fl C Ar context
+Display
+.Ar context
+months before and after the specified month.
+.It Fl d Ar day-of-week
+Specifies the day of the week on which the calendar should start.
+Valid values are 0 through 6, presenting Sunday through Saturday,
+inclusively.
+The default output starts on Sundays.
+.It Fl h
+Highlight the current day, if present in the displayed calendar.
+If output is to a terminal, then the appropriate terminal sequences
+are used, otherwise overstriking is used.
+If more than one
+.Fl h
+is used and output is to a terminal, the current date will be
+highlighted in inverse video instead of bold.
+.It Fl j
+Display Julian dates (days one-based, numbered from January 1).
+.It Fl R Ar reform-spec
+Selects an alternate Gregorian reform point from the default of
+September 3rd, 1752.
+The
+.Ar reform-spec
+can be selected by one of the built-in names (see
+.Sx NOTES
+for a list) or by a date of the form YYYY/MM/DD.
+The date and month may be omitted, provided that what is specified
+uniquely selects a given built-in reform point.
+If an exact date is specified, then that date is taken to be the first
+missing date of the Gregorian Reform to be applied.
+.It Fl r
+Display the month in which the Gregorian Reform adjustment was
+applied, if no other
+.Ar month
+or
+.Ar year
+information is given.
+If used in conjunction with
+.Fl y ,
+then the entire year is displayed.
+.It Fl y
+Display a calendar for the current year.
+.El
+.Pp
+If no parameters are specified, the current month's calendar is
+displayed.
+A single parameter specifies the year and optionally the month
+in ISO format:
+.Dq Li cal 2007-12
+Two parameters denote the month (1 - 12) and year.
+Note that the century must be included in the year.
+.Pp
+A year starts on Jan 1.
+.Sh NOTES
+In the USA and Great Britain the Gregorian Reformation occurred in 1752.
+By this time, most countries had recognized the reformation (although a
+few did not recognize it until the 1900's.)
+Eleven days following September 2, 1752 were eliminated by the reformation,
+so the calendar for that month is a bit unusual.
+.Pp
+In view of the chaotic way the Gregorian calendar was adopted throughout
+the world in the years between 1582 and 1928 make sure to take into account
+the date of the Gregorian Reformation in your region if you are checking a
+calendar for a very old date.
+.Pp
+.Nm
+has a decent built-in list of Gregorian Reform dates and the names of
+the countries where the reform was adopted:
+.Bd -literal
+ Italy Oct. 5, 1582 Denmark Feb. 19, 1700
+ Spain Oct. 5, 1582 Great Britain Sep. 3, 1752
+ Portugal Oct. 5, 1582 Sweden Feb. 18, 1753
+ Poland Oct. 5, 1582 Finland Feb. 18, 1753
+ France Dec. 12, 1582 Japan Dec. 20, 1872
+ Luxembourg Dec. 22, 1582 China Nov. 7, 1911
+ Netherlands Dec. 22, 1582 Bulgaria Apr. 1, 1916
+ Bavaria Oct. 6, 1583 U.S.S.R. Feb. 1, 1918
+ Austria Jan. 7, 1584 Serbia Jan. 19, 1919
+ Switzerland Jan. 12, 1584 Romania Jan. 19, 1919
+ Hungary Oct. 22, 1587 Greece Mar. 10, 1924
+ Germany Feb. 19, 1700 Turkey Dec. 19, 1925
+ Norway Feb. 19, 1700 Egypt Sep. 18, 1928
+.Ed
+.Pp
+The country known as
+.Em Great Britain
+can also be referred to as
+.Em England
+since that has less letters and no spaces in it.
+This is meant only as a measure of expediency, not as a possible
+slight to anyone involved.
+.Sh HISTORY
+A
+.Nm
+command appeared in
+.At v1 .
diff --git a/usr.bin/cal/cal.c b/usr.bin/cal/cal.c
new file mode 100644
index 0000000..712fa59
--- /dev/null
+++ b/usr.bin/cal/cal.c
@@ -0,0 +1,924 @@
+/* $NetBSD: cal.c,v 1.29 2015/06/16 22:54:10 christos Exp $ */
+
+/*
+ * Copyright (c) 1989, 1993, 1994
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Kim Letkeman.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+#ifndef lint
+__COPYRIGHT("@(#) Copyright (c) 1989, 1993, 1994\
+ The Regents of the University of California. All rights reserved.");
+#endif /* not lint */
+
+#ifndef lint
+#if 0
+static char sccsid[] = "@(#)cal.c 8.4 (Berkeley) 4/2/94";
+#else
+__RCSID("$NetBSD: cal.c,v 1.29 2015/06/16 22:54:10 christos Exp $");
+#endif
+#endif /* not lint */
+
+#include <sys/types.h>
+
+#include <ctype.h>
+#include <err.h>
+#include <errno.h>
+#include <limits.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <term.h>
+#include <time.h>
+#include <tzfile.h>
+#include <unistd.h>
+
+#define SATURDAY 6 /* 1 Jan 1 was a Saturday */
+
+#define FIRST_MISSING_DAY reform->first_missing_day
+#define NUMBER_MISSING_DAYS reform->missing_days
+
+#define MAXDAYS 42 /* max slots in a month array */
+#define SPACE -1 /* used in day array */
+
+static int days_in_month[2][13] = {
+ {0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31},
+ {0, 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31},
+};
+
+static int empty[MAXDAYS] = {
+ SPACE, SPACE, SPACE, SPACE, SPACE, SPACE, SPACE,
+ SPACE, SPACE, SPACE, SPACE, SPACE, SPACE, SPACE,
+ SPACE, SPACE, SPACE, SPACE, SPACE, SPACE, SPACE,
+ SPACE, SPACE, SPACE, SPACE, SPACE, SPACE, SPACE,
+ SPACE, SPACE, SPACE, SPACE, SPACE, SPACE, SPACE,
+ SPACE, SPACE, SPACE, SPACE, SPACE, SPACE, SPACE,
+};
+static int shift_days[2][4][MAXDAYS + 1];
+
+static const char *month_names[12] = {
+ "January", "February", "March", "April", "May", "June",
+ "July", "August", "September", "October", "November", "December",
+};
+
+static const char *day_headings = " S M Tu W Th F S";
+static const char *j_day_headings = " S M Tu W Th F S";
+
+/* leap years according to the julian calendar */
+#define j_leap_year(y, m, d) \
+ (((m) > 2) && \
+ !((y) % 4))
+
+/* leap years according to the gregorian calendar */
+#define g_leap_year(y, m, d) \
+ (((m) > 2) && \
+ ((!((y) % 4) && ((y) % 100)) || \
+ !((y) % 400)))
+
+/* leap year -- account for gregorian reformation at some point */
+#define leap_year(yr) \
+ ((yr) <= reform->year ? j_leap_year((yr), 3, 1) : \
+ g_leap_year((yr), 3, 1))
+
+/* number of julian leap days that have passed by a given date */
+#define j_leap_days(y, m, d) \
+ ((((y) - 1) / 4) + j_leap_year(y, m, d))
+
+/* number of gregorian leap days that have passed by a given date */
+#define g_leap_days(y, m, d) \
+ ((((y) - 1) / 4) - (((y) - 1) / 100) + (((y) - 1) / 400) + \
+ g_leap_year(y, m, d))
+
+/*
+ * Subtracting the gregorian leap day count (for a given date) from
+ * the julian leap day count (for the same date) describes the number
+ * of days from the date before the shift to the next date that
+ * appears in the calendar. Since we want to know the number of
+ * *missing* days, not the number of days that the shift spans, we
+ * subtract 2.
+ *
+ * Alternately...
+ *
+ * There's a reason they call the Dark ages the Dark Ages. Part of it
+ * is that we don't have that many records of that period of time.
+ * One of the reasons for this is that a lot of the Dark Ages never
+ * actually took place. At some point in the first millenium A.D., a
+ * ruler of some power decided that he wanted the number of the year
+ * to be different than what it was, so he changed it to coincide
+ * nicely with some event (a birthday or anniversary, perhaps a
+ * wedding, or maybe a centennial for a largish city). One of the
+ * side effects of this upon the Gregorian reform is that two Julian
+ * leap years (leap days celebrated during centennial years that are
+ * not quatro-centennial years) were skipped.
+ */
+#define GREGORIAN_MAGIC 2
+
+/* number of centuries since the reform, not inclusive */
+#define centuries_since_reform(yr) \
+ ((yr) > reform->year ? ((yr) / 100) - (reform->year / 100) : 0)
+
+/* number of centuries since the reform whose modulo of 400 is 0 */
+#define quad_centuries_since_reform(yr) \
+ ((yr) > reform->year ? ((yr) / 400) - (reform->year / 400) : 0)
+
+/* number of leap years between year 1 and this year, not inclusive */
+#define leap_years_since_year_1(yr) \
+ ((yr) / 4 - centuries_since_reform(yr) + quad_centuries_since_reform(yr))
+
+static struct reform {
+ const char *country;
+ int ambiguity, year, month, date;
+ long first_missing_day;
+ int missing_days;
+ /*
+ * That's 2 for standard/julian display, 4 for months possibly
+ * affected by the Gregorian shift, and MAXDAYS + 1 for the
+ * days that get displayed, plus a crib slot.
+ */
+} *reform, reforms[] = {
+ { "DEFAULT", 0, 1752, 9, 3, 0, 0 },
+ { "Italy", 1, 1582, 10, 5, 0, 0 },
+ { "Spain", 1, 1582, 10, 5, 0, 0 },
+ { "Portugal", 1, 1582, 10, 5, 0, 0 },
+ { "Poland", 1, 1582, 10, 5, 0, 0 },
+ { "France", 2, 1582, 12, 10, 0, 0 },
+ { "Luxembourg", 2, 1582, 12, 22, 0, 0 },
+ { "Netherlands", 2, 1582, 12, 22, 0, 0 },
+ { "Bavaria", 0, 1583, 10, 6, 0, 0 },
+ { "Austria", 2, 1584, 1, 7, 0, 0 },
+ { "Switzerland", 2, 1584, 1, 12, 0, 0 },
+ { "Hungary", 0, 1587, 10, 22, 0, 0 },
+ { "Germany", 0, 1700, 2, 19, 0, 0 },
+ { "Norway", 0, 1700, 2, 19, 0, 0 },
+ { "Denmark", 0, 1700, 2, 19, 0, 0 },
+ { "Great Britain", 0, 1752, 9, 3, 0, 0 },
+ { "England", 0, 1752, 9, 3, 0, 0 },
+ { "America", 0, 1752, 9, 3, 0, 0 },
+ { "Sweden", 0, 1753, 2, 18, 0, 0 },
+ { "Finland", 0, 1753, 2, 18, 0, 0 },
+ { "Japan", 0, 1872, 12, 20, 0, 0 },
+ { "China", 0, 1911, 11, 7, 0, 0 },
+ { "Bulgaria", 0, 1916, 4, 1, 0, 0 },
+ { "U.S.S.R.", 0, 1918, 2, 1, 0, 0 },
+ { "Serbia", 0, 1919, 1, 19, 0, 0 },
+ { "Romania", 0, 1919, 1, 19, 0, 0 },
+ { "Greece", 0, 1924, 3, 10, 0, 0 },
+ { "Turkey", 0, 1925, 12, 19, 0, 0 },
+ { "Egypt", 0, 1928, 9, 18, 0, 0 },
+ { NULL, 0, 0, 0, 0, 0, 0 },
+};
+
+static int julian;
+static int dow;
+static int hilite;
+static const char *md, *me;
+
+static void init_hilite(void);
+static int getnum(const char *);
+static void gregorian_reform(const char *);
+static void reform_day_array(int, int, int *, int *, int *,int *,int *,int *);
+static int ascii_day(char *, int);
+static void center(const char *, int, int);
+static void day_array(int, int, int *);
+static int day_in_week(int, int, int);
+static int day_in_year(int, int, int);
+static void monthrange(int, int, int, int, int);
+static void trim_trailing_spaces(char *);
+__dead static void usage(void);
+
+int
+main(int argc, char **argv)
+{
+ struct tm *local_time;
+ time_t now;
+ int ch, yflag;
+ long month, year;
+ int before, after, use_reform;
+ int yearly = 0;
+ char *when, *eoi;
+
+ before = after = 0;
+ use_reform = yflag = year = 0;
+ when = NULL;
+ while ((ch = getopt(argc, argv, "A:B:C:d:hjR:ry3")) != -1) {
+ switch (ch) {
+ case 'A':
+ after = getnum(optarg);
+ if (after < 0)
+ errx(1, "Argument to -A must be positive");
+ break;
+ case 'B':
+ before = getnum(optarg);
+ if (before < 0)
+ errx(1, "Argument to -B must be positive");
+ break;
+ case 'C':
+ after = before = getnum(optarg);
+ if (after < 0)
+ errx(1, "Argument to -C must be positive");
+ break;
+ case 'd':
+ dow = getnum(optarg);
+ if (dow < 0 || dow > 6)
+ errx(1, "illegal day of week value: use 0-6");
+ break;
+ case 'h':
+ init_hilite();
+ break;
+ case 'j':
+ julian = 1;
+ break;
+ case 'R':
+ when = optarg;
+ break;
+ case 'r':
+ use_reform = 1;
+ break;
+ case 'y':
+ yflag = 1;
+ break;
+ case '3':
+ before = after = 1;
+ break;
+ case '?':
+ default:
+ usage();
+ /* NOTREACHED */
+ }
+ }
+
+ argc -= optind;
+ argv += optind;
+
+ if (when != NULL)
+ gregorian_reform(when);
+ if (reform == NULL)
+ gregorian_reform("DEFAULT");
+
+ month = 0;
+ switch (argc) {
+ case 2:
+ month = strtol(*argv++, &eoi, 10);
+ if (month < 1 || month > 12 || *eoi != '\0')
+ errx(1, "illegal month value: use 1-12");
+ year = strtol(*argv, &eoi, 10);
+ if (year < 1 || year > 9999 || *eoi != '\0')
+ errx(1, "illegal year value: use 1-9999");
+ break;
+ case 1:
+ year = strtol(*argv, &eoi, 10);
+ if (year < 1 || year > 9999 || (*eoi != '\0' && *eoi != '/' && *eoi != '-'))
+ errx(1, "illegal year value: use 1-9999");
+ if (*eoi != '\0') {
+ month = strtol(eoi + 1, &eoi, 10);
+ if (month < 1 || month > 12 || *eoi != '\0')
+ errx(1, "illegal month value: use 1-12");
+ }
+ break;
+ case 0:
+ (void)time(&now);
+ local_time = localtime(&now);
+ if (use_reform)
+ year = reform->year;
+ else
+ year = local_time->tm_year + TM_YEAR_BASE;
+ if (!yflag) {
+ if (use_reform)
+ month = reform->month;
+ else
+ month = local_time->tm_mon + 1;
+ }
+ break;
+ default:
+ usage();
+ }
+
+ if (!month) {
+ /* yearly */
+ month = 1;
+ before = 0;
+ after = 11;
+ yearly = 1;
+ }
+
+ monthrange(month, year, before, after, yearly);
+
+ exit(0);
+}
+
+#define DAY_LEN 3 /* 3 spaces per day */
+#define J_DAY_LEN 4 /* 4 spaces per day */
+#define WEEK_LEN 20 /* 7 * 3 - one space at the end */
+#define J_WEEK_LEN 27 /* 7 * 4 - one space at the end */
+#define HEAD_SEP 2 /* spaces between day headings */
+#define J_HEAD_SEP 2
+#define MONTH_PER_ROW 3 /* how many monthes in a row */
+#define J_MONTH_PER_ROW 2
+
+static void
+monthrange(int month, int year, int before, int after, int yearly)
+{
+ int startmonth, startyear;
+ int endmonth, endyear;
+ int i, row;
+ int days[3][MAXDAYS];
+ char lineout[256];
+ int inayear;
+ int newyear;
+ int day_len, week_len, head_sep;
+ int month_per_row;
+ int skip, r_off, w_off;
+
+ if (julian) {
+ day_len = J_DAY_LEN;
+ week_len = J_WEEK_LEN;
+ head_sep = J_HEAD_SEP;
+ month_per_row = J_MONTH_PER_ROW;
+ }
+ else {
+ day_len = DAY_LEN;
+ week_len = WEEK_LEN;
+ head_sep = HEAD_SEP;
+ month_per_row = MONTH_PER_ROW;
+ }
+
+ month--;
+
+ startyear = year - (before + 12 - 1 - month) / 12;
+ startmonth = 12 - 1 - ((before + 12 - 1 - month) % 12);
+ endyear = year + (month + after) / 12;
+ endmonth = (month + after) % 12;
+
+ if (startyear < 0 || endyear > 9999) {
+ errx(1, "year should be in 1-9999");
+ }
+
+ year = startyear;
+ month = startmonth;
+ inayear = newyear = (year != endyear || yearly);
+ if (inayear) {
+ skip = month % month_per_row;
+ month -= skip;
+ }
+ else {
+ skip = 0;
+ }
+
+ do {
+ if (newyear) {
+ (void)snprintf(lineout, sizeof(lineout), "%d", year);
+ center(lineout, week_len * month_per_row +
+ head_sep * (month_per_row - 1), 0);
+ (void)printf("\n\n");
+ newyear = 0;
+ }
+
+ for (i = 0; i < skip; i++)
+ center("", week_len, head_sep);
+
+ for (; i < month_per_row; i++) {
+ int sep;
+
+ if (year == endyear && month + i > endmonth)
+ break;
+
+ sep = (i == month_per_row - 1) ? 0 : head_sep;
+ day_array(month + i + 1, year, days[i]);
+ if (inayear) {
+ center(month_names[month + i], week_len, sep);
+ }
+ else {
+ snprintf(lineout, sizeof(lineout), "%s %d",
+ month_names[month + i], year);
+ center(lineout, week_len, sep);
+ }
+ }
+ printf("\n");
+
+ for (i = 0; i < skip; i++)
+ center("", week_len, head_sep);
+
+ for (; i < month_per_row; i++) {
+ int sep;
+
+ if (year == endyear && month + i > endmonth)
+ break;
+
+ sep = (i == month_per_row - 1) ? 0 : head_sep;
+ if (dow) {
+ printf("%s ", (julian) ?
+ j_day_headings + 4 * dow :
+ day_headings + 3 * dow);
+ printf("%.*s", dow * (julian ? 4 : 3) - 1,
+ (julian) ? j_day_headings : day_headings);
+ } else
+ printf("%s", (julian) ? j_day_headings : day_headings);
+ printf("%*s", sep, "");
+ }
+ printf("\n");
+
+ for (row = 0; row < 6; row++) {
+ char *p = NULL;
+
+ memset(lineout, ' ', sizeof(lineout));
+ for (i = 0; i < skip; i++) {
+ /* nothing */
+ }
+ w_off = 0;
+ for (; i < month_per_row; i++) {
+ int col, *dp;
+
+ if (year == endyear && month + i > endmonth)
+ break;
+
+ p = lineout + i * (week_len + 2) + w_off;
+ dp = &days[i][row * 7];
+ for (col = 0; col < 7;
+ col++, p += day_len + r_off) {
+ r_off = ascii_day(p, *dp++);
+ w_off += r_off;
+ }
+ }
+ *p = '\0';
+ trim_trailing_spaces(lineout);
+ (void)printf("%s\n", lineout);
+ }
+
+ skip = 0;
+ month += month_per_row;
+ if (month >= 12) {
+ month -= 12;
+ year++;
+ newyear = 1;
+ }
+ } while (year < endyear || (year == endyear && month <= endmonth));
+}
+
+/*
+ * day_array --
+ * Fill in an array of 42 integers with a calendar. Assume for a moment
+ * that you took the (maximum) 6 rows in a calendar and stretched them
+ * out end to end. You would have 42 numbers or spaces. This routine
+ * builds that array for any month from Jan. 1 through Dec. 9999.
+ */
+static void
+day_array(int month, int year, int *days)
+{
+ int day, dw, dm;
+ time_t t;
+ struct tm *tm;
+
+ t = time(NULL);
+ tm = localtime(&t);
+ tm->tm_year += TM_YEAR_BASE;
+ tm->tm_mon++;
+ tm->tm_yday++; /* jan 1 is 1 for us, not 0 */
+
+ for (dm = month + year * 12, dw = 0; dw < 4; dw++) {
+ if (dm == shift_days[julian][dw][MAXDAYS]) {
+ memmove(days, shift_days[julian][dw],
+ MAXDAYS * sizeof(int));
+ return;
+ }
+ }
+
+ memmove(days, empty, MAXDAYS * sizeof(int));
+ dm = days_in_month[leap_year(year)][month];
+ dw = day_in_week(1, month, year);
+ day = julian ? day_in_year(1, month, year) : 1;
+ while (dm--) {
+ if (hilite && year == tm->tm_year &&
+ (julian ? (day == tm->tm_yday) :
+ (month == tm->tm_mon && day == tm->tm_mday)))
+ days[dw++] = SPACE - day++;
+ else
+ days[dw++] = day++;
+ }
+}
+
+/*
+ * day_in_year --
+ * return the 1 based day number within the year
+ */
+static int
+day_in_year(int day, int month, int year)
+{
+ int i, leap;
+
+ leap = leap_year(year);
+ for (i = 1; i < month; i++)
+ day += days_in_month[leap][i];
+ return (day);
+}
+
+/*
+ * day_in_week
+ * return the 0 based day number for any date from 1 Jan. 1 to
+ * 31 Dec. 9999. Returns the day of the week of the first
+ * missing day for any given Gregorian shift.
+ */
+static int
+day_in_week(int day, int month, int year)
+{
+ long temp;
+
+ temp = (long)(year - 1) * 365 + leap_years_since_year_1(year - 1)
+ + day_in_year(day, month, year);
+ if (temp < FIRST_MISSING_DAY)
+ return ((temp - dow + 6 + SATURDAY) % 7);
+ if (temp >= (FIRST_MISSING_DAY + NUMBER_MISSING_DAYS))
+ return (((temp - dow + 6 + SATURDAY) - NUMBER_MISSING_DAYS) % 7);
+ return ((FIRST_MISSING_DAY - dow + 6 + SATURDAY) % 7);
+}
+
+static int
+ascii_day(char *p, int day)
+{
+ int display, val, rc;
+ char *b;
+ static const char *aday[] = {
+ "",
+ " 1", " 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",
+ };
+
+ if (day == SPACE) {
+ memset(p, ' ', julian ? J_DAY_LEN : DAY_LEN);
+ return (0);
+ }
+ if (day < SPACE) {
+ b = p;
+ day = SPACE - day;
+ } else
+ b = NULL;
+ if (julian) {
+ if ((val = day / 100) != 0) {
+ day %= 100;
+ *p++ = val + '0';
+ display = 1;
+ } else {
+ *p++ = ' ';
+ display = 0;
+ }
+ val = day / 10;
+ if (val || display)
+ *p++ = val + '0';
+ else
+ *p++ = ' ';
+ *p++ = day % 10 + '0';
+ } else {
+ *p++ = aday[day][0];
+ *p++ = aday[day][1];
+ }
+
+ rc = 0;
+ if (b != NULL) {
+ const char *t;
+ char h[64];
+ int l;
+
+ l = p - b;
+ memcpy(h, b, l);
+ p = b;
+
+ if (md != NULL) {
+ for (t = md; *t; rc++)
+ *p++ = *t++;
+ memcpy(p, h, l);
+ p += l;
+ for (t = me; *t; rc++)
+ *p++ = *t++;
+ } else {
+ for (t = &h[0]; l--; t++) {
+ *p++ = *t;
+ rc++;
+ *p++ = '\b';
+ rc++;
+ *p++ = *t;
+ }
+ }
+ }
+
+ *p = ' ';
+ return (rc);
+}
+
+static void
+trim_trailing_spaces(char *s)
+{
+ char *p;
+
+ for (p = s; *p; ++p)
+ continue;
+ while (p > s && isspace((unsigned char)*--p))
+ continue;
+ if (p > s)
+ ++p;
+ *p = '\0';
+}
+
+static void
+center(const char *str, int len, int separate)
+{
+
+ len -= strlen(str);
+ (void)printf("%*s%s%*s", len / 2, "", str, len / 2 + len % 2, "");
+ if (separate)
+ (void)printf("%*s", separate, "");
+}
+
+/*
+ * gregorian_reform --
+ * Given a description of date on which the Gregorian Reform was
+ * applied. The argument can be any of the "country" names
+ * listed in the reforms array (case insensitive) or a date of
+ * the form YYYY/MM/DD. The date and month can be omitted if
+ * doing so would not select more than one different built-in
+ * reform point.
+ */
+static void
+gregorian_reform(const char *p)
+{
+ int year, month, date;
+ int i, days, diw, diy;
+ char c;
+
+ i = sscanf(p, "%d%*[/,-]%d%*[/,-]%d%c", &year, &month, &date, &c);
+ switch (i) {
+ case 4:
+ /*
+ * If the character was sscanf()ed, then there's more
+ * stuff than we need.
+ */
+ errx(1, "date specifier %s invalid", p);
+ case 0:
+ /*
+ * Not a form we can sscanf(), so void these, and we
+ * can try matching "country" names later.
+ */
+ year = month = date = -1;
+ break;
+ case 1:
+ month = 0;
+ /*FALLTHROUGH*/
+ case 2:
+ date = 0;
+ /*FALLTHROUGH*/
+ case 3:
+ /*
+ * At last, some sanity checking on the values we were
+ * given.
+ */
+ if (year < 1 || year > 9999)
+ errx(1, "%d: illegal year value: use 1-9999", year);
+ if (i > 1 && (month < 1 || month > 12))
+ errx(1, "%d: illegal month value: use 1-12", month);
+ if ((i == 3 && date < 1) || date < 0 ||
+ date > days_in_month[1][month])
+ /*
+ * What about someone specifying a leap day in
+ * a non-leap year? Well...that's a tricky
+ * one. We can't yet *say* whether the year
+ * in question is a leap year. What if the
+ * date given was, for example, 1700/2/29? is
+ * that a valid leap day?
+ *
+ * So...we punt, and hope that saying 29 in
+ * the case of February isn't too bad an idea.
+ */
+ errx(1, "%d: illegal date value: use 1-%d", date,
+ days_in_month[1][month]);
+ break;
+ }
+
+ /*
+ * A complete date was specified, so use the other pope.
+ */
+ if (date > 0) {
+ static struct reform Goestheveezl;
+
+ reform = &Goestheveezl;
+ reform->country = "Bompzidaize";
+ reform->year = year;
+ reform->month = month;
+ reform->date = date;
+ }
+
+ /*
+ * No date information was specified, so let's try to match on
+ * country name.
+ */
+ else if (year == -1) {
+ for (reform = &reforms[0]; reform->year; reform++) {
+ if (strcasecmp(p, reform->country) == 0)
+ break;
+ }
+ }
+
+ /*
+ * We have *some* date information, but not a complete date.
+ * Let's see if we have enough to pick a single entry from the
+ * list that's not ambiguous.
+ */
+ else {
+ for (reform = &reforms[0]; reform->year; reform++) {
+ if ((year == 0 || year == reform->year) &&
+ (month == 0 || month == reform->month) &&
+ (date == 0 || month == reform->date))
+ break;
+ }
+
+ if (i <= reform->ambiguity)
+ errx(1, "%s: ambiguous short reform date specification", p);
+ }
+
+ /*
+ * Oops...we reached the end of the list.
+ */
+ if (reform->year == 0)
+ errx(1, "reform name %s invalid", p);
+
+ /*
+ *
+ */
+ reform->missing_days =
+ j_leap_days(reform->year, reform->month, reform->date) -
+ g_leap_days(reform->year, reform->month, reform->date) -
+ GREGORIAN_MAGIC;
+
+ reform->first_missing_day =
+ (reform->year - 1) * 365 +
+ day_in_year(reform->date, reform->month, reform->year) +
+ date +
+ j_leap_days(reform->year, reform->month, reform->date);
+
+ /*
+ * Once we know the day of the week of the first missing day,
+ * skip back to the first of the month's day of the week.
+ */
+ diw = day_in_week(reform->date, reform->month, reform->year);
+ diw = (diw + 8 - (reform->date % 7)) % 7;
+ diy = day_in_year(1, reform->month, reform->year);
+
+ /*
+ * We might need all four of these (if you switch from Julian
+ * to Gregorian at some point after 9900, you get a gap of 73
+ * days, and that can affect four months), and it doesn't hurt
+ * all that much to precompute them, so there.
+ */
+ date = 1;
+ days = 0;
+ for (i = 0; i < 4; i++)
+ reform_day_array(reform->month + i, reform->year,
+ &days, &date, &diw, &diy,
+ shift_days[0][i],
+ shift_days[1][i]);
+}
+
+/*
+ * reform_day_array --
+ * Pre-calculates the given month's calendar (in both "standard"
+ * and "julian day" representations) with respect for days
+ * skipped during a reform period.
+ */
+static void
+reform_day_array(int month, int year, int *done, int *date, int *diw, int *diy,
+ int *scal, int *jcal)
+{
+ int mdays;
+
+ /*
+ * If the reform was in the month of october or later, then
+ * the month number from the caller could "overflow".
+ */
+ if (month > 12) {
+ month -= 12;
+ year++;
+ }
+
+ /*
+ * Erase months, and set crib number. The crib number is used
+ * later to determine if the month to be displayed is here or
+ * should be built on the fly with the generic routine
+ */
+ memmove(scal, empty, MAXDAYS * sizeof(int));
+ scal[MAXDAYS] = month + year * 12;
+ memmove(jcal, empty, MAXDAYS * sizeof(int));
+ jcal[MAXDAYS] = month + year * 12;
+
+ /*
+ * It doesn't matter what the actual month is when figuring
+ * out if this is a leap year or not, just so long as February
+ * gets the right number of days in it.
+ */
+ mdays = days_in_month[g_leap_year(year, 3, 1)][month];
+
+ /*
+ * Bounce back to the first "row" in the day array, and fill
+ * in any days that actually occur.
+ */
+ for (*diw %= 7; (*date - *done) <= mdays; (*date)++, (*diy)++) {
+ /*
+ * "date" doesn't get reset by the caller across calls
+ * to this routine, so we can actually tell that we're
+ * looking at April the 41st. Much easier than trying
+ * to calculate the absolute julian day for a given
+ * date and then checking that.
+ */
+ if (*date < reform->date ||
+ *date >= reform->date + reform->missing_days) {
+ scal[*diw] = *date - *done;
+ jcal[*diw] = *diy;
+ (*diw)++;
+ }
+ }
+ *done += mdays;
+}
+
+static int
+getnum(const char *p)
+{
+ unsigned long result;
+ char *ep;
+
+ errno = 0;
+ result = strtoul(p, &ep, 10);
+ if (p[0] == '\0' || *ep != '\0')
+ goto error;
+ if (errno == ERANGE && result == ULONG_MAX)
+ goto error;
+ if (result > INT_MAX)
+ goto error;
+
+ return (int)result;
+
+error:
+ errx(1, "bad number: %s", p);
+ /*NOTREACHED*/
+}
+
+static void
+init_hilite(void)
+{
+ const char *term;
+ int errret;
+
+ hilite++;
+
+ if (!isatty(fileno(stdout)))
+ return;
+
+ term = getenv("TERM");
+ if (term == NULL)
+ term = "dumb";
+ if (setupterm(term, fileno(stdout), &errret) != 0 && errret != 1)
+ return;
+
+ if (hilite > 1)
+ md = enter_reverse_mode;
+ else
+ md = enter_bold_mode;
+ me = exit_attribute_mode;
+ if (me == NULL || md == NULL)
+ md = me = NULL;
+}
+
+static void
+usage(void)
+{
+
+ (void)fprintf(stderr,
+ "usage: cal [-3hjry] [-A after] [-B before] [-C context] [-d day-of-week] "
+ "[-R reform-spec]\n [[month] year]\n");
+ exit(1);
+}
diff --git a/usr.bin/cksum/cksum.1 b/usr.bin/cksum/cksum.1
new file mode 100644
index 0000000..57c63b1
--- /dev/null
+++ b/usr.bin/cksum/cksum.1
@@ -0,0 +1,341 @@
+.\" $NetBSD: cksum.1,v 1.48 2017/07/03 21:34:18 wiz Exp $
+.\"
+.\" Copyright (c) 1991, 1993
+.\" The Regents of the University of California. All rights reserved.
+.\"
+.\" This code is derived from software contributed to Berkeley by
+.\" the Institute of Electrical and Electronics Engineers, Inc.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\" notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must reproduce the above copyright
+.\" notice, this list of conditions and the following disclaimer in the
+.\" documentation and/or other materials provided with the distribution.
+.\" 3. Neither the name of the University nor the names of its contributors
+.\" may be used to endorse or promote products derived from this software
+.\" without specific prior written permission.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+.\" ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+.\" SUCH DAMAGE.
+.\"
+.\" @(#)cksum.1 8.2 (Berkeley) 4/28/95
+.\"
+.Dd August 31, 2014
+.Dt CKSUM 1
+.Os
+.Sh NAME
+.Nm cksum ,
+.Nm md2 ,
+.Nm md4 ,
+.Nm md5 ,
+.Nm rmd160 ,
+.Nm sha1 ,
+.Nm sum
+.Nd display file checksums and block counts
+.Sh SYNOPSIS
+.Nm cksum
+.Op Fl n
+.Op Fl a Ar algorithm Oo Fl pqtx Oc Oo Fl s Ar string Oc
+.Op Fl o Ar 1 Ns | Ns Ar 2
+.Op Ar Li \&| Fl c Oo Fl w Oc Oo Ar sumfile Oc
+.Nm sum
+.Op Fl n
+.Op Fl a Ar algorithm Oo Fl pqtx Oc Oo Fl s Ar string Oc
+.Op Fl o Ar 1 Ns | Ns Ar 2
+.Op Ar Li \&| Fl c Oo Fl w Oc Oo Ar sumfile Oc
+.Nm md2
+.Op Fl npqtx
+.Op Fl s Ar string
+.Op Ar Li \&| Fl c Oo Fl w Oc Oo Ar sumfile Oc
+.Nm md4
+.Op Fl npqtx
+.Op Fl s Ar string
+.Op Ar Li \&| Fl c Oo Fl w Oc Oo Ar sumfile Oc
+.Nm md5
+.Op Fl npqtx
+.Op Fl s Ar string
+.Op Ar Li \&| Fl c Oo Fl w Oc Oo Ar sumfile Oc
+.Nm rmd160
+.Op Fl npqtx
+.Op Fl s Ar string
+.Op Ar Li \&| Fl c Oo Fl w Oc Oo Ar sumfile Oc
+.Nm sha1
+.Op Fl npqtx
+.Op Fl s Ar string
+.Op Ar Li \&| Fl c Oo Fl w Oc Oo Ar sumfile Oc
+.Sh DESCRIPTION
+The
+.Nm
+utility writes to the standard output three whitespace separated
+fields for each input file.
+These fields are a checksum
+.Tn CRC ,
+the total number of octets in the file and the file name.
+If no file name is specified, the standard input is used and no file name
+is written.
+.Pp
+The
+.Nm sum
+utility is identical to the
+.Nm
+utility, except that it defaults to using historic algorithm 1, as
+described below.
+It is provided for compatibility only.
+.Pp
+The
+.Nm md2 ,
+.Nm md4 ,
+.Nm md5 ,
+.Nm sha1 ,
+and
+.Nm rmd160
+utilities compute cryptographic hash functions, and write to standard
+output the hexadecimal representation of the hash of their input.
+.Pp
+The options are as follows:
+.Bl -tag -width indent
+.It Fl a Ar algorithm
+When invoked as
+.Nm cksum ,
+use the specified
+.Ar algorithm .
+Valid algorithms are:
+.Bl -column -offset indent ".Sy Algorithm" ".Sy Bits" ".Sy Description"
+.It Sy Algorithm Ta Sy Bits Ta Sy Description
+.It Li CRC Ta 32 Ta Default CRC algorithm
+.It Li MD2 Ta 128 Ta MD2, per Li RFC1319
+.It Li MD4 Ta 128 Ta MD4, per Li RFC1320
+.It Li MD5 Ta 128 Ta MD5, per Li RFC1321
+.It Li RMD160 Ta 160 Ta RIPEMD-160
+.It Li SHA1 Ta 160 Ta SHA-1, per Li FIPS PUB 180-1
+.It Li SHA256 Ta 256 Ta SHA-2
+.It Li SHA384 Ta 384 Ta SHA-2
+.It Li SHA512 Ta 512 Ta SHA-2
+.It Li old1 Ta 16 Ta Algorithm 1, per Fl o Ar 1
+.It Li old2 Ta 16 Ta Algorithm 2, per Fl o Ar 2
+.El
+.It Fl c Op Ar sumfile
+Verify (check) files against a list of checksums.
+The list is read from
+.Ar sumfile ,
+or from stdin if no filename is given.
+E.g. first run
+.Dl Ic md5 *.tgz > MD5
+.Dl Ic sha1 *.tgz > SHA1
+to generate a list of MD5 checksums in
+.Pa MD5 ,
+then use the following command to verify them:
+.Dl Ic cat MD5 SHA1 | cksum -c
+If an error is found during checksum verification, an error
+message is printed, and the program returns an error code of 1.
+.It Fl o
+Use historic algorithms instead of the (superior) default one.
+.Pp
+Algorithm 1 is the algorithm used by historic
+.Bx
+systems as the
+.Xr sum 1
+algorithm and by historic
+.At V
+systems as the
+.Xr sum 1
+algorithm when using the
+.Fl r
+option.
+This is a 16-bit checksum, with a right rotation before each addition;
+overflow is discarded.
+.Pp
+Algorithm 2 is the algorithm used by historic
+.At V
+systems as the
+default
+.Xr sum 1
+algorithm.
+This is a 32-bit checksum, and is defined as follows:
+.Bd -unfilled -offset indent
+s = sum of all bytes;
+r = s % 2^16 + (s % 2^32) / 2^16;
+cksum = (r % 2^16) + r / 2^16;
+.Ed
+.Pp
+Both algorithm 1 and 2 write to the standard output the same fields as
+the default algorithm except that the size of the file in bytes is
+replaced with the size of the file in blocks.
+For historic reasons, the block size is 1024 for algorithm 1 and 512
+for algorithm 2.
+Partial blocks are rounded up.
+.It Fl w
+Print warnings about malformed checksum files when verifying
+checksums with
+.Fl c .
+.El
+.Pp
+The following options apply only when using the one of the message
+digest algorithms:
+.Bl -tag -width indent
+.It Fl n
+Print the hash and the filename in the normal sum output form, with
+the hash at the left and the filename following on the right.
+.It Fl p
+Echo input from standard input to standard output, and append the
+selected message digest.
+.It Fl q
+Quiet mode \(em only the checksum is printed out.
+Overrides the
+.Fl n
+option.
+.It Fl s Ar string
+Print the hash of the given string
+.Ar string .
+.It Fl t
+Run a built-in message digest time trial.
+.It Fl x
+Run a built-in message digest test script.
+The tests that are run
+are supposed to encompass all the various tests in the suites that
+accompany the algorithms' descriptions with the exception of the
+last test for the SHA-1 algorithm and the RIPEMD-160 algorithm.
+The
+last test for these is one million copies of the lower letter a.
+.El
+.Pp
+The default
+.Tn CRC
+used is based on the polynomial used for
+.Tn CRC
+error checking
+in the networking standard
+.St -iso8802-3 .
+The
+.Tn CRC
+checksum encoding is defined by the generating polynomial:
+.Pp
+.Bd -unfilled -offset indent
+G(x) = x^32 + x^26 + x^23 + x^22 + x^16 + x^12 +
+ x^11 + x^10 + x^8 + x^7 + x^5 + x^4 + x^2 + x + 1
+.Ed
+.Pp
+Mathematically, the
+.Tn CRC
+value corresponding to a given file is defined by
+the following procedure:
+.Bd -filled -offset indent
+The
+.Ar n
+bits to be evaluated are considered to be the coefficients of a mod 2
+polynomial M(x) of degree
+.Ar n Ns \-1 .
+These
+.Ar n
+bits are the bits from the file, with the most significant bit being the most
+significant bit of the first octet of the file and the last bit being the least
+significant bit of the last octet, padded with zero bits (if necessary) to
+achieve an integral number of octets, followed by one or more octets
+representing the length of the file as a binary value, least significant octet
+first.
+The smallest number of octets capable of representing this integer are used.
+.Pp
+M(x) is multiplied by x^32 (i.e., shifted left 32 bits) and divided by
+G(x) using mod 2 division, producing a remainder R(x) of degree \*[Le] 31.
+.Pp
+The coefficients of R(x) are considered to be a 32-bit sequence.
+.Pp
+The bit sequence is complemented and the result is the CRC.
+.Ed
+.Pp
+The
+.Nm
+and
+.Nm sum
+utilities exit 0 on success, and >0 if an error occurs.
+.Sh SEE ALSO
+.Xr openssl 1 ,
+.Xr mtree 8
+.Pp
+The default calculation is identical to that given in pseudo-code
+in the following
+.Tn ACM
+article.
+.Rs
+.%T "Computation of Cyclic Redundancy Checks Via Table Lookup"
+.%A Dilip V. Sarwate
+.%J "Communications of the ACM"
+.%D "August 1988"
+.Re
+.Rs
+.%A R. Rivest
+.%T The MD2 Message-Digest Algorithm
+.%O RFC 1319
+.Re
+.Rs
+.%A R. Rivest
+.%T The MD4 Message-Digest Algorithm
+.%O RFC 1186 and RFC 1320
+.Re
+.Rs
+.%A R. Rivest
+.%T The MD5 Message-Digest Algorithm
+.%O RFC 1321
+.Re
+.Rs
+.%A U.S. DOC/NIST
+.%T Secure Hash Standard
+.%O FIPS PUB 180-1
+.Re
+.Sh STANDARDS
+The
+.Nm
+utility is expected to conform to
+.St -p1003.1-2004 .
+.Sh HISTORY
+The
+.Nm
+utility appeared in
+.Bx 4.4 .
+.Nm md5
+was added in
+.Nx 1.3 .
+The functionality for
+.Nm md2 ,
+.Nm md4 ,
+.Nm sha1 ,
+and
+.Nm rmd160
+was added in
+.Nx 1.6 .
+Support for the SHA-2 algorithms
+.Po
+.Li SHA256 ,
+.Li SHA384 ,
+and
+.Li SHA512
+.Pc
+was added in
+.Nx 3.0 .
+The functionality to verify checksum stored in a file
+.Pq Fl c
+first appeared in
+.Nx 4.0 .
+Quiet mode
+.Pq Fl q
+was added in
+.Nx 7.0 .
+.\" .Pp
+.\" The
+.\" .Nm sum
+.\" utility appeared in
+.\" .Bx ?.?
+.\" and
+.\" .At V .
diff --git a/usr.bin/cksum/cksum.c b/usr.bin/cksum/cksum.c
new file mode 100644
index 0000000..9e7e87a
--- /dev/null
+++ b/usr.bin/cksum/cksum.c
@@ -0,0 +1,551 @@
+/* $NetBSD: cksum.c,v 1.48 2015/06/16 22:54:10 christos Exp $ */
+
+/*-
+ * Copyright (c) 1991, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * James W. Williams of NASA Goddard Space Flight Center.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+/*-
+ * Copyright (c) 1997 Jason R. Thorpe. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * James W. Williams of NASA Goddard Space Flight Center.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. All advertising materials mentioning features or use of this software
+ * must display the following acknowledgement:
+ * This product includes software developed by the University of
+ * California, Berkeley and its contributors.
+ * 4. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#if HAVE_NBTOOL_CONFIG_H
+#include "nbtool_config.h"
+#endif
+
+#include <sys/cdefs.h>
+#if defined(__COPYRIGHT) && !defined(lint)
+__COPYRIGHT("@(#) Copyright (c) 1991, 1993\
+ The Regents of the University of California. All rights reserved.");
+#endif /* not lint */
+
+#if defined(__RCSID) && !defined(lint)
+#if 0
+static char sccsid[] = "@(#)cksum.c 8.2 (Berkeley) 4/28/95";
+#endif
+__RCSID("$NetBSD: cksum.c,v 1.48 2015/06/16 22:54:10 christos Exp $");
+#endif /* not lint */
+
+#include <sys/types.h>
+
+#include <ctype.h>
+#include <err.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <locale.h>
+#include <md2.h>
+#include <md4.h>
+#include <md5.h>
+#include <rmd160.h>
+#include <sha1.h>
+#include <sha2.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "extern.h"
+
+#define PRINT_NORMAL 0x01
+#define PRINT_QUIET 0x02
+
+typedef char *(*_filefunc)(const char *, char *);
+
+const struct hash {
+ const char *progname;
+ const char *hashname;
+ void (*stringfunc)(const char *);
+ void (*timetrialfunc)(void);
+ void (*testsuitefunc)(void);
+ void (*filterfunc)(int);
+ char *(*filefunc)(const char *, char *);
+} hashes[] = {
+ { "md2", "MD2",
+ MD2String, MD2TimeTrial, MD2TestSuite,
+ MD2Filter, MD2File },
+ { "md4", "MD4",
+ MD4String, MD4TimeTrial, MD4TestSuite,
+ MD4Filter, MD4File },
+ { "md5", "MD5",
+ MD5String, MD5TimeTrial, MD5TestSuite,
+ MD5Filter, MD5File },
+ { "rmd160", "RMD160",
+ RMD160String, RMD160TimeTrial, RMD160TestSuite,
+ RMD160Filter, (_filefunc) RMD160File },
+ { "sha1", "SHA1",
+ SHA1String, SHA1TimeTrial, SHA1TestSuite,
+ SHA1Filter, (_filefunc) SHA1File },
+ { "sha256", "SHA256",
+ SHA256_String, SHA256_TimeTrial, SHA256_TestSuite,
+ SHA256_Filter, (_filefunc) SHA256_File },
+ { "sha384", "SHA384",
+ SHA384_String, SHA384_TimeTrial, SHA384_TestSuite,
+ SHA384_Filter, (_filefunc) SHA384_File },
+ { "sha512", "SHA512",
+ SHA512_String, SHA512_TimeTrial, SHA512_TestSuite,
+ SHA512_Filter, (_filefunc) SHA512_File },
+ { .progname = NULL, },
+};
+
+static int hash_digest_file(char *, const struct hash *, int);
+__dead static void requirehash(const char *);
+__dead static void usage(void);
+
+int
+main(int argc, char **argv)
+{
+ int ch, fd, rval, pflag, nohashstdin;
+ u_int32_t val;
+ off_t len;
+ char *fn;
+ const char *progname;
+ int (*cfncn) (int, u_int32_t *, off_t *);
+ void (*pfncn) (char *, u_int32_t, off_t);
+ const struct hash *hash;
+ int i, check_warn, do_check;
+ int print_flags;
+
+ cfncn = NULL;
+ pfncn = NULL;
+ pflag = nohashstdin = 0;
+ check_warn = 0;
+ do_check = 0;
+ print_flags = 0;
+
+ setlocale(LC_ALL, "");
+
+ progname = getprogname();
+
+ for (hash = hashes; hash->hashname != NULL; hash++)
+ if (strcmp(progname, hash->progname) == 0)
+ break;
+
+ if (hash->hashname == NULL) {
+ hash = NULL;
+
+ if (!strcmp(progname, "sum")) {
+ cfncn = csum1;
+ pfncn = psum1;
+ } else {
+ cfncn = crc;
+ pfncn = pcrc;
+ }
+ }
+
+ while ((ch = getopt(argc, argv, "a:cno:pqs:twx")) != -1)
+ switch(ch) {
+ case 'a':
+ if (hash) {
+ warnx("illegal use of -a option");
+ usage();
+ }
+ i = 0;
+ while (hashes[i].hashname != NULL) {
+ if (!strcasecmp(hashes[i].hashname, optarg)) {
+ hash = &hashes[i];
+ break;
+ }
+ i++;
+ }
+ if (hash == NULL) {
+ if (!strcasecmp(optarg, "old1")) {
+ cfncn = csum1;
+ pfncn = psum1;
+ } else if (!strcasecmp(optarg, "old2")) {
+ cfncn = csum2;
+ pfncn = psum2;
+ } else if (!strcasecmp(optarg, "crc")) {
+ cfncn = crc;
+ pfncn = pcrc;
+ } else {
+ warnx("illegal argument to -a option");
+ usage();
+ }
+ }
+ break;
+ case 'c':
+ do_check = 1;
+ break;
+ case 'n':
+ print_flags |= PRINT_NORMAL;
+ break;
+ case 'o':
+ if (hash) {
+ warnx("%s mutually exclusive with sum",
+ hash->hashname);
+ usage();
+ }
+ if (!strcmp(optarg, "1")) {
+ cfncn = csum1;
+ pfncn = psum1;
+ } else if (!strcmp(optarg, "2")) {
+ cfncn = csum2;
+ pfncn = psum2;
+ } else {
+ warnx("illegal argument to -o option");
+ usage();
+ }
+ break;
+ case 'p':
+ if (hash == NULL)
+ requirehash("-p");
+ pflag = 1;
+ break;
+ case 'q':
+ print_flags |= PRINT_QUIET;
+ break;
+ case 's':
+ if (hash == NULL)
+ requirehash("-s");
+ nohashstdin = 1;
+ hash->stringfunc(optarg);
+ break;
+ case 't':
+ if (hash == NULL)
+ requirehash("-t");
+ nohashstdin = 1;
+ hash->timetrialfunc();
+ break;
+ case 'w':
+ check_warn = 1;
+ break;
+ case 'x':
+ if (hash == NULL)
+ requirehash("-x");
+ nohashstdin = 1;
+ hash->testsuitefunc();
+ break;
+ case '?':
+ default:
+ usage();
+ }
+ argc -= optind;
+ argv += optind;
+
+ if (do_check) {
+ /*
+ * Verify checksums
+ */
+ FILE *f;
+ char buf[BUFSIZ];
+ char *s, *p_filename, *p_cksum;
+ int l_filename, l_cksum;
+ char filename[BUFSIZ];
+ char cksum[BUFSIZ];
+ int ok,cnt,badcnt;
+
+ rval = 0;
+ cnt = badcnt = 0;
+
+ if (argc == 0) {
+ f = fdopen(STDIN_FILENO, "r");
+ } else {
+ f = fopen(argv[0], "r");
+ }
+ if (f == NULL)
+ err(1, "Cannot read %s",
+ argc>0?argv[0]:"stdin");
+
+ while(fgets(buf, sizeof(buf), f) != NULL) {
+ s = strrchr(buf, '\n');
+ if (s)
+ *s = '\0';
+
+ p_cksum = p_filename = NULL;
+
+ p_filename = strchr(buf, '(');
+ if (p_filename) {
+ /*
+ * Assume 'normal' output if there's a '('
+ */
+ p_filename += 1;
+ print_flags &= ~(PRINT_NORMAL);
+
+ p_cksum = strrchr(p_filename, ')');
+ if (p_cksum == NULL) {
+ if (check_warn)
+ warnx("bogus format: %s. "
+ "Skipping...",
+ buf);
+ rval = 1;
+ continue;
+ }
+ p_cksum += 4;
+
+ l_cksum = strlen(p_cksum);
+ l_filename = p_cksum - p_filename - 4;
+
+ /* Sanity check, and find proper hash if
+ * it's not the same as the current program
+ */
+ if (hash == NULL ||
+ strncmp(buf, hash->hashname,
+ strlen(hash->hashname)) != 0) {
+ /*
+ * Search proper hash
+ */
+ const struct hash *nhash;
+
+ for (nhash = hashes ;
+ nhash->hashname != NULL;
+ nhash++)
+ if (strncmp(buf,
+ nhash->hashname,
+ strlen(nhash->hashname)) == 0)
+ break;
+
+
+ if (nhash->hashname == NULL) {
+ if (check_warn)
+ warnx("unknown hash: %s",
+ buf);
+ rval = 1;
+ continue;
+ } else {
+ hash = nhash;
+ }
+ }
+
+ } else {
+ if (hash) {
+ int nspaces;
+
+ /*
+ * 'normal' output, no (ck)sum
+ */
+ print_flags |= PRINT_NORMAL;
+ nspaces = 1;
+
+ p_cksum = buf;
+ p_filename = strchr(buf, ' ');
+ if (p_filename == NULL) {
+ if (check_warn)
+ warnx("no filename in %s? "
+ "Skipping...", buf);
+ rval = 1;
+ continue;
+ }
+ while (isspace((int)*++p_filename))
+ nspaces++;
+ l_filename = strlen(p_filename);
+ l_cksum = p_filename - buf - nspaces;
+ } else {
+ /*
+ * sum/cksum output format
+ */
+ p_cksum = buf;
+ s=strchr(p_cksum, ' ');
+ if (s == NULL) {
+ if (check_warn)
+ warnx("bogus format: %s."
+ " Skipping...",
+ buf);
+ rval = 1;
+ continue;
+ }
+ l_cksum = s - p_cksum;
+
+ p_filename = strrchr(buf, ' ');
+ if (p_filename == NULL) {
+ if (check_warn)
+ warnx("no filename in %s?"
+ " Skipping...",
+ buf);
+ rval = 1;
+ continue;
+ }
+ p_filename++;
+ l_filename = strlen(p_filename);
+ }
+ }
+
+ strlcpy(filename, p_filename, l_filename+1);
+ strlcpy(cksum, p_cksum, l_cksum+1);
+
+ if (hash) {
+ if (access(filename, R_OK) == 0
+ && strcmp(cksum, hash->filefunc(filename, NULL)) == 0)
+ ok = 1;
+ else
+ ok = 0;
+ } else {
+ if ((fd = open(filename, O_RDONLY, 0)) < 0) {
+ if (check_warn)
+ warn("%s", filename);
+ rval = 1;
+ ok = 0;
+ } else {
+ if (cfncn(fd, &val, &len))
+ ok = 0;
+ else {
+ u_int32_t should_val;
+
+ should_val =
+ strtoul(cksum, NULL, 10);
+ if (val == should_val)
+ ok = 1;
+ else
+ ok = 0;
+ }
+ close(fd);
+ }
+ }
+
+ if (! ok) {
+ if (hash)
+ printf("(%s) ", hash->hashname);
+ printf("%s: FAILED\n", filename);
+ badcnt++;
+ }
+ cnt++;
+
+ }
+ fclose(f);
+
+ if (badcnt > 0)
+ rval = 1;
+
+ } else {
+ /*
+ * Calculate checksums
+ */
+
+ fd = STDIN_FILENO;
+ fn = NULL;
+ rval = 0;
+ do {
+ if (*argv) {
+ fn = *argv++;
+ if (hash != NULL) {
+ if (hash_digest_file(fn, hash, print_flags)) {
+ warn("%s", fn);
+ rval = 1;
+ }
+ continue;
+ }
+ if ((fd = open(fn, O_RDONLY, 0)) < 0) {
+ warn("%s", fn);
+ rval = 1;
+ continue;
+ }
+ } else if (hash && !nohashstdin) {
+ hash->filterfunc(pflag);
+ }
+
+ if (hash == NULL) {
+ if (cfncn(fd, &val, &len)) {
+ warn("%s", fn ? fn : "stdin");
+ rval = 1;
+ } else
+ pfncn(fn, val, len);
+ (void)close(fd);
+ }
+ } while (*argv);
+ }
+ exit(rval);
+}
+
+static int
+hash_digest_file(char *fn, const struct hash *hash, int flags)
+{
+ char *cp;
+
+ cp = hash->filefunc(fn, NULL);
+ if (cp == NULL)
+ return 1;
+
+ if (flags & PRINT_QUIET)
+ printf("%s\n", cp);
+ else if (flags & PRINT_NORMAL)
+ printf("%s %s\n", cp, fn);
+ else
+ printf("%s (%s) = %s\n", hash->hashname, fn, cp);
+
+ free(cp);
+
+ return 0;
+}
+
+static void
+requirehash(const char *flg)
+{
+ warnx("%s flag requires `-a algorithm'", flg);
+ usage();
+}
+
+static void
+usage(void)
+{
+ const char fileargs[] = "[file ... | -c [-w] [sumfile]]";
+ const char sumargs[] = "[-n] [-a algorithm [-ptx] [-s string]] [-o 1|2]";
+ const char hashargs[] = "[-nptx] [-s string]";
+
+ (void)fprintf(stderr, "usage: cksum %s\n %s\n",
+ sumargs, fileargs);
+ (void)fprintf(stderr, " sum %s\n %s\n",
+ sumargs, fileargs);
+ (void)fprintf(stderr, " md2 %s %s\n", hashargs, fileargs);
+ (void)fprintf(stderr, " md4 %s %s\n", hashargs, fileargs);
+ (void)fprintf(stderr, " md5 %s %s\n", hashargs, fileargs);
+ (void)fprintf(stderr, " rmd160 %s %s\n", hashargs, fileargs);
+ (void)fprintf(stderr, " sha1 %s %s\n", hashargs, fileargs);
+ exit(1);
+}
diff --git a/usr.bin/cksum/crc.c b/usr.bin/cksum/crc.c
new file mode 100644
index 0000000..b3979db
--- /dev/null
+++ b/usr.bin/cksum/crc.c
@@ -0,0 +1,162 @@
+/* $NetBSD: crc.c,v 1.19 2014/10/29 18:09:35 uebayasi Exp $ */
+
+/*-
+ * Copyright (c) 1991, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * James W. Williams of NASA Goddard Space Flight Center.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#if HAVE_NBTOOL_CONFIG_H
+#include "nbtool_config.h"
+#endif
+
+#include <sys/cdefs.h>
+#if defined(__RCSID) && !defined(lint)
+#if 0
+static char sccsid[] = "@(#)crc.c 8.1 (Berkeley) 6/17/93";
+#else
+__RCSID("$NetBSD: crc.c,v 1.19 2014/10/29 18:09:35 uebayasi Exp $");
+#endif
+#endif /* not lint */
+
+#include <sys/types.h>
+#include <unistd.h>
+
+#include "extern.h"
+
+static const u_int32_t crctab[] = {
+ 0x0,
+ 0x04c11db7, 0x09823b6e, 0x0d4326d9, 0x130476dc, 0x17c56b6b,
+ 0x1a864db2, 0x1e475005, 0x2608edb8, 0x22c9f00f, 0x2f8ad6d6,
+ 0x2b4bcb61, 0x350c9b64, 0x31cd86d3, 0x3c8ea00a, 0x384fbdbd,
+ 0x4c11db70, 0x48d0c6c7, 0x4593e01e, 0x4152fda9, 0x5f15adac,
+ 0x5bd4b01b, 0x569796c2, 0x52568b75, 0x6a1936c8, 0x6ed82b7f,
+ 0x639b0da6, 0x675a1011, 0x791d4014, 0x7ddc5da3, 0x709f7b7a,
+ 0x745e66cd, 0x9823b6e0, 0x9ce2ab57, 0x91a18d8e, 0x95609039,
+ 0x8b27c03c, 0x8fe6dd8b, 0x82a5fb52, 0x8664e6e5, 0xbe2b5b58,
+ 0xbaea46ef, 0xb7a96036, 0xb3687d81, 0xad2f2d84, 0xa9ee3033,
+ 0xa4ad16ea, 0xa06c0b5d, 0xd4326d90, 0xd0f37027, 0xddb056fe,
+ 0xd9714b49, 0xc7361b4c, 0xc3f706fb, 0xceb42022, 0xca753d95,
+ 0xf23a8028, 0xf6fb9d9f, 0xfbb8bb46, 0xff79a6f1, 0xe13ef6f4,
+ 0xe5ffeb43, 0xe8bccd9a, 0xec7dd02d, 0x34867077, 0x30476dc0,
+ 0x3d044b19, 0x39c556ae, 0x278206ab, 0x23431b1c, 0x2e003dc5,
+ 0x2ac12072, 0x128e9dcf, 0x164f8078, 0x1b0ca6a1, 0x1fcdbb16,
+ 0x018aeb13, 0x054bf6a4, 0x0808d07d, 0x0cc9cdca, 0x7897ab07,
+ 0x7c56b6b0, 0x71159069, 0x75d48dde, 0x6b93dddb, 0x6f52c06c,
+ 0x6211e6b5, 0x66d0fb02, 0x5e9f46bf, 0x5a5e5b08, 0x571d7dd1,
+ 0x53dc6066, 0x4d9b3063, 0x495a2dd4, 0x44190b0d, 0x40d816ba,
+ 0xaca5c697, 0xa864db20, 0xa527fdf9, 0xa1e6e04e, 0xbfa1b04b,
+ 0xbb60adfc, 0xb6238b25, 0xb2e29692, 0x8aad2b2f, 0x8e6c3698,
+ 0x832f1041, 0x87ee0df6, 0x99a95df3, 0x9d684044, 0x902b669d,
+ 0x94ea7b2a, 0xe0b41de7, 0xe4750050, 0xe9362689, 0xedf73b3e,
+ 0xf3b06b3b, 0xf771768c, 0xfa325055, 0xfef34de2, 0xc6bcf05f,
+ 0xc27dede8, 0xcf3ecb31, 0xcbffd686, 0xd5b88683, 0xd1799b34,
+ 0xdc3abded, 0xd8fba05a, 0x690ce0ee, 0x6dcdfd59, 0x608edb80,
+ 0x644fc637, 0x7a089632, 0x7ec98b85, 0x738aad5c, 0x774bb0eb,
+ 0x4f040d56, 0x4bc510e1, 0x46863638, 0x42472b8f, 0x5c007b8a,
+ 0x58c1663d, 0x558240e4, 0x51435d53, 0x251d3b9e, 0x21dc2629,
+ 0x2c9f00f0, 0x285e1d47, 0x36194d42, 0x32d850f5, 0x3f9b762c,
+ 0x3b5a6b9b, 0x0315d626, 0x07d4cb91, 0x0a97ed48, 0x0e56f0ff,
+ 0x1011a0fa, 0x14d0bd4d, 0x19939b94, 0x1d528623, 0xf12f560e,
+ 0xf5ee4bb9, 0xf8ad6d60, 0xfc6c70d7, 0xe22b20d2, 0xe6ea3d65,
+ 0xeba91bbc, 0xef68060b, 0xd727bbb6, 0xd3e6a601, 0xdea580d8,
+ 0xda649d6f, 0xc423cd6a, 0xc0e2d0dd, 0xcda1f604, 0xc960ebb3,
+ 0xbd3e8d7e, 0xb9ff90c9, 0xb4bcb610, 0xb07daba7, 0xae3afba2,
+ 0xaafbe615, 0xa7b8c0cc, 0xa379dd7b, 0x9b3660c6, 0x9ff77d71,
+ 0x92b45ba8, 0x9675461f, 0x8832161a, 0x8cf30bad, 0x81b02d74,
+ 0x857130c3, 0x5d8a9099, 0x594b8d2e, 0x5408abf7, 0x50c9b640,
+ 0x4e8ee645, 0x4a4ffbf2, 0x470cdd2b, 0x43cdc09c, 0x7b827d21,
+ 0x7f436096, 0x7200464f, 0x76c15bf8, 0x68860bfd, 0x6c47164a,
+ 0x61043093, 0x65c52d24, 0x119b4be9, 0x155a565e, 0x18197087,
+ 0x1cd86d30, 0x029f3d35, 0x065e2082, 0x0b1d065b, 0x0fdc1bec,
+ 0x3793a651, 0x3352bbe6, 0x3e119d3f, 0x3ad08088, 0x2497d08d,
+ 0x2056cd3a, 0x2d15ebe3, 0x29d4f654, 0xc5a92679, 0xc1683bce,
+ 0xcc2b1d17, 0xc8ea00a0, 0xd6ad50a5, 0xd26c4d12, 0xdf2f6bcb,
+ 0xdbee767c, 0xe3a1cbc1, 0xe760d676, 0xea23f0af, 0xeee2ed18,
+ 0xf0a5bd1d, 0xf464a0aa, 0xf9278673, 0xfde69bc4, 0x89b8fd09,
+ 0x8d79e0be, 0x803ac667, 0x84fbdbd0, 0x9abc8bd5, 0x9e7d9662,
+ 0x933eb0bb, 0x97ffad0c, 0xafb010b1, 0xab710d06, 0xa6322bdf,
+ 0xa2f33668, 0xbcb4666d, 0xb8757bda, 0xb5365d03, 0xb1f740b4
+};
+
+/*
+ * Compute a POSIX 1003.2 checksum. This routine has been broken out so that
+ * other programs can use it. It takes a file descriptor to read from and
+ * locations to store the crc and the number of bytes read. It returns 0 on
+ * success and 1 on failure. Errno is set on failure.
+ */
+int
+crc(int fd, u_int32_t *cval, off_t *clen)
+{
+ u_char *p;
+ ssize_t nr;
+ u_int32_t thecrc;
+ off_t len;
+ u_char buf[16 * 1024];
+
+#define COMPUTE(var, ch) (var) = (var) << 8 ^ crctab[(var) >> 24 ^ (ch)]
+
+ thecrc = 0;
+ len = 0;
+ while ((nr = read(fd, buf, sizeof(buf))) > 0)
+ for (len += nr, p = buf; nr--; ++p) {
+ COMPUTE(thecrc, *p);
+ }
+ if (nr < 0)
+ return 1;
+
+ *clen = len;
+
+ /* Include the length of the file. */
+ for (; len != 0; len >>= 8) {
+ COMPUTE(thecrc, len & 0xff);
+ }
+
+ *cval = ~thecrc;
+ return 0;
+}
+
+/* These two are rather more useful to the outside world */
+
+uint32_t
+crc_buf(uint32_t thecrc, const void *buf, size_t len)
+{
+ const uint8_t *p = buf;
+
+ for (p = buf; len; p++, len--)
+ COMPUTE(thecrc, *p);
+ return thecrc;
+}
+
+uint32_t
+crc_byte(uint32_t thecrc, unsigned int byte_val)
+{
+ COMPUTE(thecrc, byte_val & 0xff);
+ return thecrc;
+}
diff --git a/usr.bin/cksum/crc_extern.h b/usr.bin/cksum/crc_extern.h
new file mode 100644
index 0000000..b0c1c33
--- /dev/null
+++ b/usr.bin/cksum/crc_extern.h
@@ -0,0 +1,38 @@
+/* $NetBSD: crc_extern.h,v 1.1 2006/09/04 20:01:10 dsl Exp $ */
+
+/*-
+ * Copyright (c) 1991, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * @(#)extern.h 8.1 (Berkeley) 6/6/93
+ */
+
+__BEGIN_DECLS
+int crc(int, u_int32_t *, off_t *);
+uint32_t crc_buf(uint32_t, const void *, size_t);
+uint32_t crc_byte(uint32_t, unsigned int);
+__END_DECLS
diff --git a/usr.bin/cksum/extern.h b/usr.bin/cksum/extern.h
new file mode 100644
index 0000000..8384110
--- /dev/null
+++ b/usr.bin/cksum/extern.h
@@ -0,0 +1,88 @@
+/* $NetBSD: extern.h,v 1.19 2006/09/04 20:01:10 dsl Exp $ */
+
+/*-
+ * Copyright (c) 1991, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * @(#)extern.h 8.1 (Berkeley) 6/6/93
+ */
+
+#if HAVE_NBTOOL_CONFIG_H
+#include "nbtool_config.h"
+#endif
+
+#include <sys/cdefs.h>
+#include "crc_extern.h"
+
+__BEGIN_DECLS
+void pcrc(char *, u_int32_t, off_t);
+void psum1(char *, u_int32_t, off_t);
+void psum2(char *, u_int32_t, off_t);
+int csum1(int, u_int32_t *, off_t *);
+int csum2(int, u_int32_t *, off_t *);
+int md5(int, u_int32_t *, u_int32_t *);
+
+void MD2String(const char *);
+void MD2TimeTrial(void);
+void MD2TestSuite(void);
+void MD2Filter(int);
+
+void MD4String(const char *);
+void MD4TimeTrial(void);
+void MD4TestSuite(void);
+void MD4Filter(int);
+
+void MD5String(const char *);
+void MD5TimeTrial(void);
+void MD5TestSuite(void);
+void MD5Filter(int);
+
+void SHA1String(const char *);
+void SHA1TimeTrial(void);
+void SHA1TestSuite(void);
+void SHA1Filter(int);
+
+void RMD160String(const char *);
+void RMD160TimeTrial(void);
+void RMD160TestSuite(void);
+void RMD160Filter(int);
+
+void SHA256_String(const char *);
+void SHA256_TimeTrial(void);
+void SHA256_TestSuite(void);
+void SHA256_Filter(int);
+
+void SHA384_String(const char *);
+void SHA384_TimeTrial(void);
+void SHA384_TestSuite(void);
+void SHA384_Filter(int);
+
+void SHA512_String(const char *);
+void SHA512_TimeTrial(void);
+void SHA512_TestSuite(void);
+void SHA512_Filter(int);
+__END_DECLS
diff --git a/usr.bin/cksum/md2.c b/usr.bin/cksum/md2.c
new file mode 100644
index 0000000..cc80b4a
--- /dev/null
+++ b/usr.bin/cksum/md2.c
@@ -0,0 +1,21 @@
+/* $NetBSD: md2.c,v 1.1 2001/03/20 18:46:26 atatat Exp $ */
+
+#include <md2.h> /* this hash type */
+#include <md5.h> /* the hash we're replacing */
+
+#define HASHTYPE "MD2"
+#define HASHLEN 32
+
+#define MD5Filter MD2Filter
+#define MD5String MD2String
+#define MD5TestSuite MD2TestSuite
+#define MD5TimeTrial MD2TimeTrial
+
+#define MD5Data MD2Data
+#define MD5Init MD2Init
+#define MD5Update MD2Update
+#define MD5End MD2End
+
+#define MD5_CTX MD2_CTX
+
+#include "md5.c"
diff --git a/usr.bin/cksum/md4.c b/usr.bin/cksum/md4.c
new file mode 100644
index 0000000..e27b416
--- /dev/null
+++ b/usr.bin/cksum/md4.c
@@ -0,0 +1,21 @@
+/* $NetBSD: md4.c,v 1.1 2001/03/20 18:46:26 atatat Exp $ */
+
+#include <md4.h> /* this hash type */
+#include <md5.h> /* the hash we're replacing */
+
+#define HASHTYPE "MD4"
+#define HASHLEN 32
+
+#define MD5Filter MD4Filter
+#define MD5String MD4String
+#define MD5TestSuite MD4TestSuite
+#define MD5TimeTrial MD4TimeTrial
+
+#define MD5Data MD4Data
+#define MD5Init MD4Init
+#define MD5Update MD4Update
+#define MD5End MD4End
+
+#define MD5_CTX MD4_CTX
+
+#include "md5.c"
diff --git a/usr.bin/cksum/md5.c b/usr.bin/cksum/md5.c
new file mode 100644
index 0000000..5547771
--- /dev/null
+++ b/usr.bin/cksum/md5.c
@@ -0,0 +1,153 @@
+/* $NetBSD: md5.c,v 1.10 2008/12/29 00:51:29 christos Exp $ */
+
+/*
+ * MDDRIVER.C - test driver for MD2, MD4 and MD5
+ */
+
+/*
+ * Copyright (C) 1990-2, RSA Data Security, Inc. Created 1990. All
+ * rights reserved.
+ *
+ * RSA Data Security, Inc. makes no representations concerning either
+ * the merchantability of this software or the suitability of this
+ * software for any particular purpose. It is provided "as is"
+ * without express or implied warranty of any kind.
+ *
+ * These notices must be retained in any copies of any part of this
+ * documentation and/or software.
+ */
+
+#if HAVE_NBTOOL_CONFIG_H
+#include "nbtool_config.h"
+#endif
+
+#include <sys/cdefs.h>
+#if defined(__RCSID) && !defined(lint)
+__RCSID("$NetBSD: md5.c,v 1.10 2008/12/29 00:51:29 christos Exp $");
+#endif /* not lint */
+
+#include <sys/types.h>
+
+#include <err.h>
+#include <md5.h>
+#include <stdio.h>
+#include <string.h>
+#include <time.h>
+
+void MD5Filter(int);
+void MD5String(const char *);
+void MD5TestSuite(void);
+void MD5TimeTrial(void);
+
+#ifndef HASHTYPE
+#define HASHTYPE "MD5"
+#endif
+
+#ifndef HASHLEN
+#define HASHLEN 32
+#endif
+
+/*
+ * Length of test block, number of test blocks.
+ */
+#define TEST_BLOCK_LEN 1000
+#define TEST_BLOCK_COUNT 1000
+
+/*
+ * Digests a string and prints the result.
+ */
+void
+MD5String(const char *string)
+{
+ unsigned int len = strlen(string);
+ char buf[HASHLEN + 1];
+
+ printf("%s (\"%s\") = %s\n", HASHTYPE, string,
+ MD5Data((const unsigned char *)string, len, buf));
+}
+
+/*
+ * Measures the time to digest TEST_BLOCK_COUNT TEST_BLOCK_LEN-byte blocks.
+ */
+void
+MD5TimeTrial(void)
+{
+ MD5_CTX context;
+ time_t endTime, startTime;
+ unsigned char block[TEST_BLOCK_LEN];
+ unsigned int i;
+ char *p, buf[HASHLEN + 1];
+
+ printf("%s time trial. Digesting %d %d-byte blocks ...", HASHTYPE,
+ TEST_BLOCK_LEN, TEST_BLOCK_COUNT);
+ fflush(stdout);
+
+ /* Initialize block */
+ for (i = 0; i < TEST_BLOCK_LEN; i++)
+ block[i] = (unsigned char) (i & 0xff);
+
+ /* Start timer */
+ time(&startTime);
+
+ /* Digest blocks */
+ MD5Init(&context);
+ for (i = 0; i < TEST_BLOCK_COUNT; i++)
+ MD5Update(&context, block, TEST_BLOCK_LEN);
+ p = MD5End(&context,buf);
+
+ /* Stop timer */
+ time(&endTime);
+
+ printf(" done\n");
+ printf("Digest = %s\n", p);
+ printf("Time = %ld seconds\n", (long) (endTime - startTime));
+
+ /*
+ * Be careful that endTime-startTime is not zero.
+ * (Bug fix from Ric * Anderson, ric@Artisoft.COM.)
+ */
+ printf("Speed = %lld bytes/second\n",
+ (long long) TEST_BLOCK_LEN * TEST_BLOCK_COUNT /
+ ((endTime - startTime) != 0 ? (endTime - startTime) : 1));
+}
+
+/*
+ * Digests a reference suite of strings and prints the results.
+ */
+void
+MD5TestSuite(void)
+{
+ printf("%s test suite:\n", HASHTYPE);
+
+ MD5String("");
+ MD5String("a");
+ MD5String("abc");
+ MD5String("message digest");
+ MD5String("abcdefghijklmnopqrstuvwxyz");
+ MD5String("abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq");
+ MD5String
+ ("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789");
+ MD5String
+ ("1234567890123456789012345678901234567890\
+1234567890123456789012345678901234567890");
+}
+
+/*
+ * Digests the standard input and prints the result.
+ */
+void
+MD5Filter(int pipe)
+{
+ MD5_CTX context;
+ size_t len;
+ unsigned char buffer[BUFSIZ];
+ char buf[HASHLEN + 1];
+
+ MD5Init(&context);
+ while ((len = fread(buffer, (size_t)1, (size_t)BUFSIZ, stdin)) > 0) {
+ if (pipe && (len != fwrite(buffer, (size_t)1, len, stdout)))
+ err(1, "stdout");
+ MD5Update(&context, buffer, (unsigned int)len);
+ }
+ printf("%s\n", MD5End(&context,buf));
+}
diff --git a/usr.bin/cksum/print.c b/usr.bin/cksum/print.c
new file mode 100644
index 0000000..03dbd5a
--- /dev/null
+++ b/usr.bin/cksum/print.c
@@ -0,0 +1,76 @@
+/* $NetBSD: print.c,v 1.11 2005/01/12 17:04:35 xtraeme Exp $ */
+
+/*-
+ * Copyright (c) 1991, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#if HAVE_NBTOOL_CONFIG_H
+#include "nbtool_config.h"
+#endif
+
+#include <sys/cdefs.h>
+#if defined(__RCSID) && !defined(lint)
+#if 0
+static char sccsid[] = "@(#)print.c 8.1 (Berkeley) 6/6/93";
+#else
+__RCSID("$NetBSD: print.c,v 1.11 2005/01/12 17:04:35 xtraeme Exp $");
+#endif
+#endif /* not lint */
+
+#include <sys/types.h>
+#include <stdio.h>
+#include "extern.h"
+
+void
+pcrc(char *fn, u_int32_t val, off_t len)
+{
+ (void)printf("%lu %lld", (unsigned long)val, (long long)len);
+ if (fn)
+ (void)printf(" %s", fn);
+ (void)printf("\n");
+}
+
+void
+psum1(char *fn, u_int32_t val, off_t len)
+{
+ (void)printf("%lu %lld", (unsigned long)val,
+ (long long)(len + 1023) / 1024);
+ if (fn)
+ (void)printf(" %s", fn);
+ (void)printf("\n");
+}
+
+void
+psum2(char *fn, u_int32_t val, off_t len)
+{
+ (void)printf("%lu %lld", (unsigned long)val,
+ (long long)(len + 511) / 512);
+ if (fn)
+ (void)printf(" %s", fn);
+ (void)printf("\n");
+}
diff --git a/usr.bin/cksum/rmd160.c b/usr.bin/cksum/rmd160.c
new file mode 100644
index 0000000..f04a58d
--- /dev/null
+++ b/usr.bin/cksum/rmd160.c
@@ -0,0 +1,25 @@
+/* $NetBSD: rmd160.c,v 1.3 2006/10/30 20:22:54 christos Exp $ */
+
+#if HAVE_NBTOOL_CONFIG_H
+#include "nbtool_config.h"
+#endif
+
+#include <rmd160.h> /* this hash type */
+#include <md5.h> /* the hash we're replacing */
+
+#define HASHTYPE "RMD160"
+#define HASHLEN 40
+
+#define MD5Filter RMD160Filter
+#define MD5String RMD160String
+#define MD5TestSuite RMD160TestSuite
+#define MD5TimeTrial RMD160TimeTrial
+
+#define MD5Data RMD160Data
+#define MD5Init RMD160Init
+#define MD5Update RMD160Update
+#define MD5End RMD160End
+
+#define MD5_CTX RMD160_CTX
+
+#include "md5.c"
diff --git a/usr.bin/cksum/sha1.c b/usr.bin/cksum/sha1.c
new file mode 100644
index 0000000..221a6e0
--- /dev/null
+++ b/usr.bin/cksum/sha1.c
@@ -0,0 +1,21 @@
+/* $NetBSD: sha1.c,v 1.1 2001/03/20 18:46:27 atatat Exp $ */
+
+#include <sha1.h> /* this hash type */
+#include <md5.h> /* the hash we're replacing */
+
+#define HASHTYPE "SHA1"
+#define HASHLEN 40
+
+#define MD5Filter SHA1Filter
+#define MD5String SHA1String
+#define MD5TestSuite SHA1TestSuite
+#define MD5TimeTrial SHA1TimeTrial
+
+#define MD5Data SHA1Data
+#define MD5Init SHA1Init
+#define MD5Update SHA1Update
+#define MD5End SHA1End
+
+#define MD5_CTX SHA1_CTX
+
+#include "md5.c"
diff --git a/usr.bin/cksum/sha256.c b/usr.bin/cksum/sha256.c
new file mode 100644
index 0000000..41f272e
--- /dev/null
+++ b/usr.bin/cksum/sha256.c
@@ -0,0 +1,25 @@
+/* $NetBSD: sha256.c,v 1.3 2006/10/30 20:22:54 christos Exp $ */
+
+#if HAVE_NBTOOL_CONFIG_H
+#include "nbtool_config.h"
+#endif
+
+#include <sha2.h> /* this hash type */
+#include <md5.h> /* the hash we're replacing */
+
+#define HASHTYPE "SHA256"
+#define HASHLEN 64
+
+#define MD5Filter SHA256_Filter
+#define MD5String SHA256_String
+#define MD5TestSuite SHA256_TestSuite
+#define MD5TimeTrial SHA256_TimeTrial
+
+#define MD5Data SHA256_Data
+#define MD5Init SHA256_Init
+#define MD5Update SHA256_Update
+#define MD5End SHA256_End
+
+#define MD5_CTX SHA256_CTX
+
+#include "md5.c"
diff --git a/usr.bin/cksum/sha384.c b/usr.bin/cksum/sha384.c
new file mode 100644
index 0000000..845988b
--- /dev/null
+++ b/usr.bin/cksum/sha384.c
@@ -0,0 +1,25 @@
+/* $NetBSD: sha384.c,v 1.3 2006/10/30 20:22:54 christos Exp $ */
+
+#if HAVE_NBTOOL_CONFIG_H
+#include "nbtool_config.h"
+#endif
+
+#include <sha2.h> /* this hash type */
+#include <md5.h> /* the hash we're replacing */
+
+#define HASHTYPE "SHA384"
+#define HASHLEN 96
+
+#define MD5Filter SHA384_Filter
+#define MD5String SHA384_String
+#define MD5TestSuite SHA384_TestSuite
+#define MD5TimeTrial SHA384_TimeTrial
+
+#define MD5Data SHA384_Data
+#define MD5Init SHA384_Init
+#define MD5Update SHA384_Update
+#define MD5End SHA384_End
+
+#define MD5_CTX SHA384_CTX
+
+#include "md5.c"
diff --git a/usr.bin/cksum/sha512.c b/usr.bin/cksum/sha512.c
new file mode 100644
index 0000000..69dae77
--- /dev/null
+++ b/usr.bin/cksum/sha512.c
@@ -0,0 +1,25 @@
+/* $NetBSD: sha512.c,v 1.3 2006/10/30 20:22:54 christos Exp $ */
+
+#if HAVE_NBTOOL_CONFIG_H
+#include "nbtool_config.h"
+#endif
+
+#include <sha2.h> /* this hash type */
+#include <md5.h> /* the hash we're replacing */
+
+#define HASHTYPE "SHA512"
+#define HASHLEN 128
+
+#define MD5Filter SHA512_Filter
+#define MD5String SHA512_String
+#define MD5TestSuite SHA512_TestSuite
+#define MD5TimeTrial SHA512_TimeTrial
+
+#define MD5Data SHA512_Data
+#define MD5Init SHA512_Init
+#define MD5Update SHA512_Update
+#define MD5End SHA512_End
+
+#define MD5_CTX SHA512_CTX
+
+#include "md5.c"
diff --git a/usr.bin/cksum/sum1.c b/usr.bin/cksum/sum1.c
new file mode 100644
index 0000000..22e466e
--- /dev/null
+++ b/usr.bin/cksum/sum1.c
@@ -0,0 +1,76 @@
+/* $NetBSD: sum1.c,v 1.13 2005/02/05 00:13:34 simonb Exp $ */
+
+/*-
+ * Copyright (c) 1991, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#if HAVE_NBTOOL_CONFIG_H
+#include "nbtool_config.h"
+#endif
+
+#include <sys/cdefs.h>
+#if defined(__RCSID) && !defined(lint)
+#if 0
+static char sccsid[] = "@(#)sum1.c 8.1 (Berkeley) 6/6/93";
+#else
+__RCSID("$NetBSD: sum1.c,v 1.13 2005/02/05 00:13:34 simonb Exp $");
+#endif
+#endif /* not lint */
+
+#include <sys/types.h>
+#include <unistd.h>
+
+#include "extern.h"
+
+int
+csum1(int fd, u_int32_t *cval, off_t *clen)
+{
+ off_t total;
+ int nr;
+ u_int thecrc;
+ u_char *p;
+ u_char buf[8192];
+
+ /*
+ * 16-bit checksum, rotating right before each addition;
+ * overflow is discarded.
+ */
+ thecrc = total = 0;
+ while ((nr = read(fd, buf, sizeof(buf))) > 0)
+ for (total += nr, p = buf; nr--; ++p) {
+ if (thecrc & 1)
+ thecrc |= 0x10000;
+ thecrc = ((thecrc >> 1) + *p) & 0xffff;
+ }
+ if (nr < 0)
+ return 1;
+
+ *cval = thecrc;
+ *clen = total;
+ return 0;
+}
diff --git a/usr.bin/cksum/sum2.c b/usr.bin/cksum/sum2.c
new file mode 100644
index 0000000..34b3e2e
--- /dev/null
+++ b/usr.bin/cksum/sum2.c
@@ -0,0 +1,80 @@
+/* $NetBSD: sum2.c,v 1.13 2005/02/05 00:13:34 simonb Exp $ */
+
+/*-
+ * Copyright (c) 1991, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#if HAVE_NBTOOL_CONFIG_H
+#include "nbtool_config.h"
+#endif
+
+#include <sys/cdefs.h>
+#if defined(__RCSID) && !defined(lint)
+#if 0
+static char sccsid[] = "@(#)sum2.c 8.1 (Berkeley) 6/6/93";
+#else
+__RCSID("$NetBSD: sum2.c,v 1.13 2005/02/05 00:13:34 simonb Exp $");
+#endif
+#endif /* not lint */
+
+#include <sys/types.h>
+#include <unistd.h>
+
+#include "extern.h"
+
+int
+csum2(int fd, u_int32_t *cval, off_t *clen)
+{
+ u_int32_t thecrc;
+ off_t total;
+ int nr;
+ u_char *p;
+ u_char buf[8192];
+
+ /*
+ * Draft 8 POSIX 1003.2:
+ *
+ * s = sum of all bytes
+ * r = s % 2^16 + (s % 2^32) / 2^16
+ * thecrc = (r % 2^16) + r / 2^16
+ */
+ thecrc = 0;
+ total = 0;
+ while ((nr = read(fd, buf, sizeof(buf))) > 0)
+ for (total += nr, p = buf; nr--; ++p)
+ thecrc += *p;
+ if (nr < 0)
+ return 1;
+
+ thecrc = (thecrc & 0xffff) + (thecrc >> 16);
+ thecrc = (thecrc & 0xffff) + (thecrc >> 16);
+
+ *cval = thecrc;
+ *clen = total;
+ return 0;
+}
diff --git a/usr.bin/cmp/cmp.1 b/usr.bin/cmp/cmp.1
new file mode 100644
index 0000000..e61f62c
--- /dev/null
+++ b/usr.bin/cmp/cmp.1
@@ -0,0 +1,122 @@
+.\" $NetBSD: cmp.1,v 1.13 2017/07/03 21:34:18 wiz Exp $
+.\"
+.\" Copyright (c) 1987, 1990, 1993
+.\" The Regents of the University of California. All rights reserved.
+.\"
+.\" This code is derived from software contributed to Berkeley by
+.\" the Institute of Electrical and Electronics Engineers, Inc.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\" notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must reproduce the above copyright
+.\" notice, this list of conditions and the following disclaimer in the
+.\" documentation and/or other materials provided with the distribution.
+.\" 3. Neither the name of the University nor the names of its contributors
+.\" may be used to endorse or promote products derived from this software
+.\" without specific prior written permission.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+.\" ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+.\" SUCH DAMAGE.
+.\"
+.\" @(#)cmp.1 8.1 (Berkeley) 6/6/93
+.\"
+.Dd December 13, 2016
+.Dt CMP 1
+.Os
+.Sh NAME
+.Nm cmp
+.Nd compare two files
+.Sh SYNOPSIS
+.Nm
+.Op Fl c
+.Op Fl l | Fl s
+.Ar file1 file2
+.Op Ar skip1 Op Ar skip2
+.Sh DESCRIPTION
+The cmp utility compares two files of any type and writes the results
+to the standard output.
+By default,
+.Nm
+is silent if the files are the same; if they differ, the byte
+and line number at which the first difference occurred is reported.
+.Pp
+Bytes and lines are numbered beginning with one.
+.Pp
+The following options are available:
+.Bl -tag -width flag
+.It Fl c
+Do the compare using
+.Xr getc 3
+rather than
+.Xr mmap 2 .
+Combined with the
+.Fl l
+flag, this can be helpful in locating an I/O error in the underlying
+device.
+.It Fl l
+Print the byte number (decimal) and the differing
+byte values (octal) for each difference.
+.It Fl s
+Print nothing for differing files; return exit
+status only.
+.El
+.Pp
+The optional arguments
+.Ar skip1
+and
+.Ar skip2
+are the byte offsets from the beginning of
+.Ar file1
+and
+.Ar file2 ,
+respectively, where the comparison will begin.
+The offset is decimal by default, but may be expressed as an hexadecimal
+or octal value by preceding it with a leading ``0x'' or ``0''.
+.Sh EXIT STATUS
+The
+.Nm
+utility exits with one of the following values:
+.Bl -tag -width 4n
+.It 0
+The files are identical.
+.It 1
+The files are different; this includes the case
+where one file is identical to the first part of
+the other.
+In the latter case, if the
+.Fl s
+option has not been specified,
+.Nm
+writes to standard output that EOF was reached in the shorter
+file (before any differences were found).
+.It >1
+An error occurred.
+.El
+.Sh SEE ALSO
+.Xr comm 1 ,
+.Xr diff 1 ,
+.Xr diff3 1 ,
+.Xr sdiff 1
+.Sh STANDARDS
+The
+.Nm
+utility is expected to be
+.St -p1003.2
+compatible.
+.Sh HISTORY
+The
+.Nm
+utility first appeared in
+.At v1 .
diff --git a/usr.bin/cmp/cmp.c b/usr.bin/cmp/cmp.c
new file mode 100644
index 0000000..4301731
--- /dev/null
+++ b/usr.bin/cmp/cmp.c
@@ -0,0 +1,168 @@
+/* $NetBSD: cmp.c,v 1.20 2016/10/30 19:33:49 christos Exp $ */
+
+/*
+ * Copyright (c) 1987, 1990, 1993, 1994
+ * The Regents of the University of California. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+#ifndef lint
+__COPYRIGHT("@(#) Copyright (c) 1987, 1990, 1993, 1994\
+ The Regents of the University of California. All rights reserved.");
+#endif /* not lint */
+
+#ifndef lint
+#if 0
+static char sccsid[] = "@(#)cmp.c 8.3 (Berkeley) 4/2/94";
+#else
+__RCSID("$NetBSD: cmp.c,v 1.20 2016/10/30 19:33:49 christos Exp $");
+#endif
+#endif /* not lint */
+
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#include <err.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <locale.h>
+
+#include "extern.h"
+
+int lflag, sflag;
+
+__dead static void usage(void);
+
+int
+main(int argc, char *argv[])
+{
+ struct stat sb1, sb2;
+ off_t skip1 = 0, skip2 = 0;
+ int ch, fd1, fd2, special;
+ const char *file1, *file2;
+
+ setlocale(LC_ALL, "");
+
+ special = 0;
+ while ((ch = getopt(argc, argv, "cls")) != -1)
+ switch (ch) {
+ case 'c':
+ special = 1; /* careful! Don't use mmap() */
+ break;
+ case 'l': /* print all differences */
+ lflag = 1;
+ break;
+ case 's': /* silent run */
+ sflag = 1;
+ break;
+ case '?':
+ default:
+ usage();
+ }
+ argv += optind;
+ argc -= optind;
+
+ if (lflag && sflag)
+ errx(ERR_EXIT, "only one of -l and -s may be specified");
+
+ if (argc < 2 || argc > 4)
+ usage();
+
+ /* Backward compatibility -- handle "-" meaning stdin. */
+ if (strcmp(file1 = argv[0], "-") == 0) {
+ special = 1;
+ fd1 = 0;
+ file1 = "stdin";
+ }
+ else if ((fd1 = open(file1, O_RDONLY, 0)) < 0) {
+ if (!sflag)
+ warn("%s", file1);
+ exit(ERR_EXIT);
+ }
+ if (strcmp(file2 = argv[1], "-") == 0) {
+ if (special)
+ errx(ERR_EXIT,
+ "standard input may only be specified once");
+ special = 1;
+ fd2 = 0;
+ file2 = "stdin";
+ }
+ else if ((fd2 = open(file2, O_RDONLY, 0)) < 0) {
+ if (!sflag)
+ warn("%s", file2);
+ exit(ERR_EXIT);
+ }
+
+ if (argc > 2) {
+ char *ep;
+
+ errno = 0;
+ skip1 = strtoq(argv[2], &ep, 0);
+ if (errno || ep == argv[2])
+ usage();
+
+ if (argc == 4) {
+ skip2 = strtoq(argv[3], &ep, 0);
+ if (errno || ep == argv[3])
+ usage();
+ }
+ }
+
+ if (!special) {
+ if (fstat(fd1, &sb1))
+ err(ERR_EXIT, "%s", file1);
+ if (!S_ISREG(sb1.st_mode))
+ special = 1;
+ else {
+ if (fstat(fd2, &sb2))
+ err(ERR_EXIT, "%s", file2);
+ if (!S_ISREG(sb2.st_mode))
+ special = 1;
+ }
+ }
+
+ if (special)
+ c_special(fd1, file1, skip1, fd2, file2, skip2);
+ else
+ c_regular(fd1, file1, skip1, sb1.st_size,
+ fd2, file2, skip2, sb2.st_size);
+ exit(0);
+}
+
+static void
+usage(void)
+{
+
+ (void)fprintf(stderr,
+ "Usage: %s [-c] [-l | -s] file1 file2 [skip1 [skip2]]\n",
+ getprogname());
+ exit(ERR_EXIT);
+}
diff --git a/usr.bin/cmp/extern.h b/usr.bin/cmp/extern.h
new file mode 100644
index 0000000..d8a72ed
--- /dev/null
+++ b/usr.bin/cmp/extern.h
@@ -0,0 +1,44 @@
+/* $NetBSD: extern.h,v 1.9 2011/08/29 14:14:11 joerg Exp $ */
+
+/*-
+ * Copyright (c) 1991, 1993, 1994
+ * The Regents of the University of California. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * @(#)extern.h 8.3 (Berkeley) 4/2/94
+ */
+
+#define OK_EXIT 0
+#define DIFF_EXIT 1
+#define ERR_EXIT 2 /* error exit code */
+
+void c_regular(int, const char *, off_t, off_t, int, const char *, off_t, off_t);
+void c_special(int, const char *, off_t, int, const char *, off_t);
+__dead void diffmsg(const char *, const char *, off_t, off_t);
+__dead void eofmsg(const char *, off_t, off_t);
+__dead void errmsg(const char *, off_t, off_t);
+
+extern int lflag, sflag;
diff --git a/usr.bin/cmp/misc.c b/usr.bin/cmp/misc.c
new file mode 100644
index 0000000..3195de9
--- /dev/null
+++ b/usr.bin/cmp/misc.c
@@ -0,0 +1,84 @@
+/* $NetBSD: misc.c,v 1.12 2009/04/11 12:16:12 lukem Exp $ */
+
+/*-
+ * Copyright (c) 1991, 1993, 1994
+ * The Regents of the University of California. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+#ifndef lint
+#if 0
+static char sccsid[] = "@(#)misc.c 8.3 (Berkeley) 4/2/94";
+#else
+__RCSID("$NetBSD: misc.c,v 1.12 2009/04/11 12:16:12 lukem Exp $");
+#endif
+#endif /* not lint */
+
+#include <sys/types.h>
+
+#include <err.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+#include "extern.h"
+
+void
+errmsg(const char *file, off_t byte, off_t line)
+{
+ if (lflag)
+ err(ERR_EXIT, "%s: char %lld, line %lld", file,
+ (long long)byte, (long long)line);
+ else
+ err(ERR_EXIT, "%s", file);
+}
+
+void
+eofmsg(const char *file, off_t byte, off_t line)
+{
+ if (!sflag) {
+ if (!lflag)
+ warnx("EOF on %s", file);
+ else {
+ if (line > 0)
+ warnx("EOF on %s: char %lld, line %lld",
+ file, (long long)byte, (long long)line);
+ else
+ warnx("EOF on %s: char %lld",
+ file, (long long)byte);
+ }
+ }
+ exit(DIFF_EXIT);
+}
+
+void
+diffmsg(const char *file1, const char *file2, off_t byte, off_t line)
+{
+ if (!sflag)
+ (void)printf("%s %s differ: char %lld, line %lld\n",
+ file1, file2, (long long)byte, (long long)line);
+ exit(DIFF_EXIT);
+}
diff --git a/usr.bin/cmp/regular.c b/usr.bin/cmp/regular.c
new file mode 100644
index 0000000..63c663f
--- /dev/null
+++ b/usr.bin/cmp/regular.c
@@ -0,0 +1,118 @@
+/* $NetBSD: regular.c,v 1.24 2013/11/20 17:19:14 kleink Exp $ */
+
+/*-
+ * Copyright (c) 1991, 1993, 1994
+ * The Regents of the University of California. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+#ifndef lint
+#if 0
+static char sccsid[] = "@(#)regular.c 8.3 (Berkeley) 4/2/94";
+#else
+__RCSID("$NetBSD: regular.c,v 1.24 2013/11/20 17:19:14 kleink Exp $");
+#endif
+#endif /* not lint */
+
+#include <sys/param.h>
+#include <sys/mman.h>
+#include <sys/stat.h>
+
+#include <err.h>
+#include <limits.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <stdio.h>
+
+#include "extern.h"
+
+void
+c_regular(int fd1, const char *file1, off_t skip1, off_t len1,
+ int fd2, const char *file2, off_t skip2, off_t len2)
+{
+ u_char ch, *p1, *p2;
+ off_t byte, length, line;
+ int dfound;
+ size_t blk_sz, blk_cnt;
+
+ if (sflag && len1 != len2)
+ exit(1);
+
+ if (skip1 > len1)
+ eofmsg(file1, len1 + 1, 0);
+ len1 -= skip1;
+ if (skip2 > len2)
+ eofmsg(file2, len2 + 1, 0);
+ len2 -= skip2;
+
+ byte = line = 1;
+ dfound = 0;
+ length = MIN(len1, len2);
+ for (blk_sz = 1024 * 1024; length != 0; length -= blk_sz) {
+ if ((uintmax_t)blk_sz > (uintmax_t)length)
+ blk_sz = length;
+ p1 = mmap(NULL, blk_sz, PROT_READ, MAP_FILE|MAP_SHARED,
+ fd1, skip1);
+ if (p1 == MAP_FAILED)
+ goto mmap_failed;
+
+ p2 = mmap(NULL, blk_sz, PROT_READ, MAP_FILE|MAP_SHARED,
+ fd2, skip2);
+ if (p2 == MAP_FAILED) {
+ munmap(p1, blk_sz);
+ goto mmap_failed;
+ }
+
+ blk_cnt = blk_sz;
+ for (; blk_cnt--; ++p1, ++p2, ++byte) {
+ if ((ch = *p1) != *p2) {
+ if (!lflag) {
+ diffmsg(file1, file2, byte, line);
+ /* NOTREACHED */
+ }
+ dfound = 1;
+ (void)printf("%6lld %3o %3o\n",
+ (long long)byte, ch, *p2);
+ }
+ if (ch == '\n')
+ ++line;
+ }
+ munmap(p1 - blk_sz, blk_sz);
+ munmap(p2 - blk_sz, blk_sz);
+ skip1 += blk_sz;
+ skip2 += blk_sz;
+ }
+
+ if (len1 != len2)
+ eofmsg(len1 > len2 ? file2 : file1, byte, line);
+ if (dfound)
+ exit(DIFF_EXIT);
+ return;
+
+mmap_failed:
+ c_special(fd1, file1, skip1, fd2, file2, skip2);
+}
diff --git a/usr.bin/cmp/special.c b/usr.bin/cmp/special.c
new file mode 100644
index 0000000..0b1afc5
--- /dev/null
+++ b/usr.bin/cmp/special.c
@@ -0,0 +1,111 @@
+/* $NetBSD: special.c,v 1.14 2011/11/28 10:10:10 wiz Exp $ */
+
+/*-
+ * Copyright (c) 1991, 1993, 1994
+ * The Regents of the University of California. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+#ifndef lint
+#if 0
+static char sccsid[] = "@(#)special.c 8.3 (Berkeley) 4/2/94";
+#else
+__RCSID("$NetBSD: special.c,v 1.14 2011/11/28 10:10:10 wiz Exp $");
+#endif
+#endif /* not lint */
+
+#include <sys/types.h>
+
+#include <err.h>
+#include <stdlib.h>
+#include <stdio.h>
+
+#include "extern.h"
+
+void
+c_special(int fd1, const char *file1, off_t skip1, int fd2, const char *file2, off_t skip2)
+{
+ int ch1, ch2;
+ off_t byte, line;
+ FILE *fp1, *fp2;
+ int dfound;
+
+ dfound = 0;
+ if ((fp1 = fdopen(fd1, "r")) == NULL)
+ err(ERR_EXIT, "%s", file1);
+ if ((fp2 = fdopen(fd2, "r")) == NULL)
+ err(ERR_EXIT, "%s", file2);
+
+ for (byte = line = 1; skip1--; byte++) {
+ ch1 = getc(fp1);
+ if (ch1 == EOF)
+ goto eof;
+ if (ch1 == '\n')
+ line++;
+ }
+ for (byte = line = 1; skip2--; byte++) {
+ ch2 = getc(fp2);
+ if (ch2 == EOF)
+ goto eof;
+ if (ch2 == '\n')
+ line++;
+ }
+ dfound = 0;
+ for (byte = line = 1;; ++byte) {
+ ch1 = getc(fp1);
+ ch2 = getc(fp2);
+ if (ch1 == EOF || ch2 == EOF)
+ break;
+ if (ch1 != ch2) {
+ if (lflag) {
+ dfound = 1;
+ (void)printf("%6lld %3o %3o\n", (long long)byte,
+ ch1, ch2);
+ } else
+ diffmsg(file1, file2, byte, line);
+ /* NOTREACHED */
+ }
+ if (ch1 == '\n')
+ ++line;
+ }
+
+ eof:
+ if (ferror(fp1))
+ errmsg(file1, byte, line);
+ if (ferror(fp2))
+ errmsg(file2, byte, line);
+ if (feof(fp1)) {
+ if (!feof(fp2))
+ eofmsg(file1, byte, line);
+ } else
+ if (feof(fp2))
+ eofmsg(file2, byte, line);
+ (void)fclose(fp1);
+ (void)fclose(fp2);
+ if (dfound)
+ exit(DIFF_EXIT);
+}
diff --git a/usr.bin/comm/comm.1 b/usr.bin/comm/comm.1
new file mode 100644
index 0000000..d304388
--- /dev/null
+++ b/usr.bin/comm/comm.1
@@ -0,0 +1,102 @@
+.\" $NetBSD: comm.1,v 1.12 2014/04/13 01:45:34 snj Exp $
+.\"
+.\" Copyright (c) 1989, 1990, 1993
+.\" The Regents of the University of California. All rights reserved.
+.\"
+.\" This code is derived from software contributed to Berkeley by
+.\" the Institute of Electrical and Electronics Engineers, Inc.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\" notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must reproduce the above copyright
+.\" notice, this list of conditions and the following disclaimer in the
+.\" documentation and/or other materials provided with the distribution.
+.\" 3. Neither the name of the University nor the names of its contributors
+.\" may be used to endorse or promote products derived from this software
+.\" without specific prior written permission.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+.\" ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+.\" SUCH DAMAGE.
+.\"
+.\" @(#)comm.1 8.1 (Berkeley) 6/6/93
+.\"
+.Dd June 6, 1993
+.Dt COMM 1
+.Os
+.Sh NAME
+.Nm comm
+.Nd select or reject lines common to two files
+.Sh SYNOPSIS
+.Nm
+.Op Fl 123f
+.Ar file1 file2
+.Sh DESCRIPTION
+The
+.Nm
+utility reads
+.Ar file1
+and
+.Ar file2 ,
+which should be
+sorted lexically, and produces three text
+columns as output: lines only in
+.Ar file1 ;
+lines only in
+.Ar file2 ;
+and lines in both files.
+.Pp
+The filename ``-'' means the standard input.
+.Pp
+The following options are available:
+.Bl -tag -width Ds
+.It Fl 1
+Suppress printing of column 1.
+.It Fl 2
+Suppress printing of column 2.
+.It Fl 3
+Suppress printing of column 3.
+.It Fl f
+Fold case in line comparisons.
+.El
+.Pp
+Each column will have a number of tab characters prepended to it
+equal to the number of lower numbered columns that are being printed.
+For example, if column number two is being suppressed, lines printed
+in column number one will not have any tabs preceding them, and lines
+printed in column number three will have one.
+.Pp
+.Nm
+assumes that the files are lexically sorted; all characters
+participate in line comparisons.
+.\" .Sh ENVIRONMENT
+.\" .Bl -tag -width indent
+.\" .It Ev LANG
+.\" .It Ev LC_ALL
+.\" .It Ev LC_CTYPE
+.\" .It Ev LC_COLLATE
+.\" .It Ev LC_MESSAGES
+.\" .El
+.Sh EXIT STATUS
+.Ex -std
+.Sh SEE ALSO
+.Xr cmp 1 ,
+.Xr diff 1 ,
+.Xr sort 1 ,
+.Xr uniq 1
+.Sh STANDARDS
+The
+.Nm
+utility conforms to
+.St -p1003.2-92 .
diff --git a/usr.bin/comm/comm.c b/usr.bin/comm/comm.c
new file mode 100644
index 0000000..c1416e2
--- /dev/null
+++ b/usr.bin/comm/comm.c
@@ -0,0 +1,213 @@
+/* $NetBSD: comm.c,v 1.20 2012/09/05 04:01:23 simonb Exp $ */
+
+/*
+ * Copyright (c) 1989, 1993, 1994
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Case Larsen.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+#ifndef lint
+__COPYRIGHT("@(#) Copyright (c) 1989, 1993, 1994\
+ The Regents of the University of California. All rights reserved.");
+#endif /* not lint */
+
+#ifndef lint
+#if 0
+static char sccsid[] = "@(#)comm.c 8.4 (Berkeley) 5/4/95";
+#endif
+__RCSID("$NetBSD: comm.c,v 1.20 2012/09/05 04:01:23 simonb Exp $");
+#endif /* not lint */
+
+#include <err.h>
+#include <limits.h>
+#include <locale.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#define MAXLINELEN (LINE_MAX + 1)
+
+static const char *tabs[] = { "", "\t", "\t\t" };
+
+static FILE *file(const char *);
+static void show(FILE *, const char *, char *);
+__dead static void usage(void);
+static char *getnextln(char *buf, FILE *);
+
+int
+main(int argc, char **argv)
+{
+ int comp, file1done, file2done, read1, read2;
+ int ch, flag1, flag2, flag3;
+ FILE *fp1, *fp2;
+ const char *col1, *col2, *col3, **p;
+ char line1[MAXLINELEN], line2[MAXLINELEN];
+ int (*compare)(const char*,const char*);
+
+ (void)setlocale(LC_ALL, "");
+
+ file1done = file2done = 0;
+ flag1 = flag2 = flag3 = 1;
+ compare = strcoll;
+ while ((ch = getopt(argc, argv, "123f")) != -1)
+ switch(ch) {
+ case '1':
+ flag1 = 0;
+ break;
+ case '2':
+ flag2 = 0;
+ break;
+ case '3':
+ flag3 = 0;
+ break;
+ case 'f':
+ compare = strcasecmp;
+ break;
+ case '?':
+ default:
+ usage();
+ }
+ argc -= optind;
+ argv += optind;
+
+ if (argc != 2)
+ usage();
+
+ fp1 = file(argv[0]);
+ fp2 = file(argv[1]);
+
+ /* for each column printed, add another tab offset */
+ p = tabs;
+ col1 = col2 = col3 = NULL;
+ if (flag1)
+ col1 = *p++;
+ if (flag2)
+ col2 = *p++;
+ if (flag3)
+ col3 = *p;
+
+ for (read1 = read2 = 1;;) {
+ /* read next line, check for EOF */
+ if (read1)
+ file1done = !getnextln(line1, fp1);
+ if (read2)
+ file2done = !getnextln(line2, fp2);
+
+ /* if one file done, display the rest of the other file */
+ if (file1done) {
+ if (!file2done && col2)
+ show(fp2, col2, line2);
+ break;
+ }
+ if (file2done) {
+ if (!file1done && col1)
+ show(fp1, col1, line1);
+ break;
+ }
+
+ /* lines are the same */
+ if (!(comp = compare(line1, line2))) {
+ read1 = read2 = 1;
+ if (col3)
+ if (printf("%s%s\n", col3, line1) < 0)
+ break;
+ continue;
+ }
+
+ /* lines are different */
+ if (comp < 0) {
+ read1 = 1;
+ read2 = 0;
+ if (col1)
+ if (printf("%s%s\n", col1, line1) < 0)
+ break;
+ } else {
+ read1 = 0;
+ read2 = 1;
+ if (col2)
+ if (printf("%s%s\n", col2, line2) < 0)
+ break;
+ }
+ }
+
+ if (ferror (stdout) || fclose (stdout) == EOF)
+ err(1, "stdout");
+
+ exit(0);
+}
+
+static void
+show(FILE *fp, const char *offset, char *buf)
+{
+ while (printf("%s%s\n", offset, buf) >= 0 && getnextln(buf, fp))
+ ;
+}
+
+static FILE *
+file(const char *name)
+{
+ FILE *fp;
+
+ if (!strcmp(name, "-"))
+ return (stdin);
+ if ((fp = fopen(name, "r")) == NULL)
+ err(1, "%s", name);
+ return (fp);
+}
+
+static void
+usage(void)
+{
+
+ (void)fprintf(stderr, "usage: comm [-123f] file1 file2\n");
+ exit(1);
+}
+
+static char *
+getnextln(char *buf, FILE *fp)
+{
+ size_t i = 0;
+ int c;
+
+ while ((c = getc(fp)) != '\n' && c != EOF) {
+ buf[i++] = c;
+
+ if (i >= MAXLINELEN)
+ i--; /* consumes extra characters till newline */
+ }
+
+ if (c == EOF && !i)
+ return NULL;
+
+ buf[i] = 0;
+ return buf;
+}
+
diff --git a/usr.bin/compress/compress.1 b/usr.bin/compress/compress.1
new file mode 100644
index 0000000..84bc47c
--- /dev/null
+++ b/usr.bin/compress/compress.1
@@ -0,0 +1,171 @@
+.\" $NetBSD: compress.1,v 1.16 2017/07/04 06:58:55 wiz Exp $
+.\"
+.\" Copyright (c) 1986, 1990, 1993
+.\" The Regents of the University of California. All rights reserved.
+.\"
+.\" This code is derived from software contributed to Berkeley by
+.\" James A. Woods, derived from original work by Spencer Thomas
+.\" and Joseph Orost.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\" notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must reproduce the above copyright
+.\" notice, this list of conditions and the following disclaimer in the
+.\" documentation and/or other materials provided with the distribution.
+.\" 3. Neither the name of the University nor the names of its contributors
+.\" may be used to endorse or promote products derived from this software
+.\" without specific prior written permission.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+.\" ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+.\" SUCH DAMAGE.
+.\"
+.\" @(#)compress.1 8.2 (Berkeley) 4/18/94
+.\"
+.Dd January 23, 2003
+.Dt COMPRESS 1
+.Os
+.Sh NAME
+.Nm compress ,
+.\".Nm uncompress ,
+.Nm uncompress
+.\".Nm zcat
+.Nd compress and expand data
+.Sh SYNOPSIS
+.Nm
+.Op Fl cdfv
+.Op Fl b Ar bits
+.Op Ar
+.Nm uncompress
+.Op Fl cdfv
+.Op Ar
+.\".Nm zcat
+.\".Op Ar
+.Sh DESCRIPTION
+.Nm
+reduces the size of the named files using adaptive Lempel-Ziv coding.
+Each
+.Ar file
+is renamed to the same name plus the extension
+.Dq .Z .
+As many of the modification time, access time, file flags, file mode,
+user ID, and group ID as allowed by permissions are retained in the
+new file.
+If compression would not reduce the size of a
+.Ar file ,
+the file is ignored.
+.Pp
+.Nm uncompress
+restores the compressed files to their original form, renaming the
+files by deleting the
+.Dq .Z
+extension.
+.\".Pp
+.\".Nm Zcat
+.\"is an alias for
+.\".Dq "uncompress -c" .
+.Pp
+If renaming the files would cause files to be overwritten and the standard
+input device is a terminal, the user is prompted (on the standard error
+output) for confirmation.
+If prompting is not possible or confirmation is not received, the files
+are not overwritten.
+.Pp
+If no files are specified, the standard input is compressed or uncompressed
+to the standard output.
+If either the input and output files are not regular files, the checks for
+reduction in size and file overwriting are not performed, the input file is
+not removed, and the attributes of the input file are not retained.
+.Pp
+The options are as follows:
+.Bl -tag -width Ds
+.It Fl b
+Specify the
+.Ar bits
+code limit (see below).
+.It Fl c
+Compressed or uncompressed output is written to the standard output.
+No files are modified.
+.It Fl d
+Force decompression.
+.It Fl f
+Force compression of
+.Ar file ,
+even if it is not actually reduced in size.
+Additionally, files are overwritten without prompting for confirmation.
+.It Fl v
+Print the percentage reduction of each file.
+.El
+.Pp
+.Nm
+uses a modified Lempel-Ziv algorithm.
+Common substrings in the file are first replaced by 9-bit codes 257 and up.
+When code 512 is reached, the algorithm switches to 10-bit codes and
+continues to use more bits until the
+limit specified by the
+.Fl b
+flag is reached (the default is 16).
+.Ar Bits
+must be between 9 and 16.
+.Pp
+After the
+.Ar bits
+limit is reached,
+.Nm
+periodically checks the compression ratio.
+If it is increasing,
+.Nm
+continues to use the existing code dictionary.
+However, if the compression ratio decreases,
+.Nm
+discards the table of substrings and rebuilds it from scratch.
+This allows the algorithm to adapt to the next "block" of the file.
+.Pp
+The
+.Fl b
+flag is omitted for
+.Ar uncompress
+since the
+.Ar bits
+parameter specified during compression
+is encoded within the output, along with
+a magic number to ensure that neither decompression of random data nor
+recompression of compressed data is attempted.
+.Pp
+The amount of compression obtained depends on the size of the
+input, the number of
+.Ar bits
+per code, and the distribution of common substrings.
+Typically, text such as source code or English is reduced by 50\-60%.
+Compression is generally much better than that achieved by Huffman
+coding (as used in the historical command pack), or adaptive Huffman
+coding (as used in the historical command compact), and takes less
+time to compute.
+.Sh EXIT STATUS
+.Ex -std compress
+.Sh SEE ALSO
+.Xr zcat 1
+.Rs
+.%A Welch, Terry A.
+.%D June, 1984
+.%T "A Technique for High Performance Data Compression"
+.%J "IEEE Computer"
+.%V 17:6
+.%P pp. 8-19
+.Re
+.Sh HISTORY
+The
+.Nm
+command appeared in
+.Bx 4.3 .
diff --git a/usr.bin/compress/compress.c b/usr.bin/compress/compress.c
new file mode 100644
index 0000000..93fdc76
--- /dev/null
+++ b/usr.bin/compress/compress.c
@@ -0,0 +1,459 @@
+/* $NetBSD: compress.c,v 1.26 2011/08/30 23:08:05 joerg Exp $ */
+
+/*-
+ * Copyright (c) 1992, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+#ifndef lint
+__COPYRIGHT("@(#) Copyright (c) 1992, 1993\
+ The Regents of the University of California. All rights reserved.");
+#endif /* not lint */
+
+#ifndef lint
+#if 0
+static char sccsid[] = "@(#)compress.c 8.2 (Berkeley) 1/7/94";
+#else
+__RCSID("$NetBSD: compress.c,v 1.26 2011/08/30 23:08:05 joerg Exp $");
+#endif
+#endif /* not lint */
+
+#include <sys/param.h>
+#include <sys/time.h>
+#include <sys/stat.h>
+
+#include <err.h>
+#include <errno.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+static void compress(const char *, const char *, int);
+static void cwarn(const char *, ...) __printflike(1, 2);
+static void cwarnx(const char *, ...) __printflike(1, 2);
+static void decompress(const char *, const char *, int);
+static int permission(const char *);
+static void setfile(const char *, struct stat *);
+__dead static void usage(int);
+
+extern FILE *zopen(const char *fname, const char *mode, int bits);
+
+static int eval, force, verbose;
+static int isstdout, isstdin;
+
+int
+main(int argc, char **argv)
+{
+ enum {COMPRESS, DECOMPRESS} style = COMPRESS;
+ size_t len;
+ int bits, cat, ch;
+ char *p, newname[MAXPATHLEN];
+
+ if ((p = strrchr(argv[0], '/')) == NULL)
+ p = argv[0];
+ else
+ ++p;
+ if (!strcmp(p, "uncompress"))
+ style = DECOMPRESS;
+ else if (!strcmp(p, "compress"))
+ style = COMPRESS;
+ else if (!strcmp(p, "zcat")) {
+ style = DECOMPRESS;
+ cat = 1;
+ }
+ else
+ errx(1, "unknown program name");
+
+ bits = cat = 0;
+ while ((ch = getopt(argc, argv, "b:cdfv")) != -1)
+ switch(ch) {
+ case 'b':
+ bits = strtol(optarg, &p, 10);
+ if (*p)
+ errx(1, "illegal bit count -- %s", optarg);
+ break;
+ case 'c':
+ cat = 1;
+ break;
+ case 'd': /* Backward compatible. */
+ style = DECOMPRESS;
+ break;
+ case 'f':
+ force = 1;
+ break;
+ case 'v':
+ verbose = 1;
+ break;
+ case '?':
+ default:
+ usage(style == COMPRESS);
+ }
+ argc -= optind;
+ argv += optind;
+
+ if (argc == 0) {
+ switch(style) {
+ case COMPRESS:
+ isstdout = 1;
+ isstdin = 1;
+ (void)compress("/dev/stdin", "/dev/stdout", bits);
+ break;
+ case DECOMPRESS:
+ isstdout = 1;
+ isstdin = 1;
+ (void)decompress("/dev/stdin", "/dev/stdout", bits);
+ break;
+ }
+ exit (eval);
+ }
+
+ if (cat == 1 && argc > 1)
+ errx(1, "the -c option permits only a single file argument");
+
+ for (; *argv; ++argv) {
+ isstdout = 0;
+ switch(style) {
+ case COMPRESS:
+ if (cat) {
+ isstdout = 1;
+ compress(*argv, "/dev/stdout", bits);
+ break;
+ }
+ if ((p = strrchr(*argv, '.')) != NULL &&
+ !strcmp(p, ".Z")) {
+ cwarnx("%s: name already has trailing .Z",
+ *argv);
+ break;
+ }
+ len = strlen(*argv);
+ if (len > sizeof(newname) - 3) {
+ cwarnx("%s: name too long", *argv);
+ break;
+ }
+ memmove(newname, *argv, len);
+ newname[len] = '.';
+ newname[len + 1] = 'Z';
+ newname[len + 2] = '\0';
+ compress(*argv, newname, bits);
+ break;
+ case DECOMPRESS:
+ len = strlen(*argv);
+ if ((p = strrchr(*argv, '.')) == NULL ||
+ strcmp(p, ".Z")) {
+ if (len > sizeof(newname) - 3) {
+ cwarnx("%s: name too long", *argv);
+ break;
+ }
+ memmove(newname, *argv, len);
+ newname[len] = '.';
+ newname[len + 1] = 'Z';
+ newname[len + 2] = '\0';
+ decompress(newname,
+ cat ? "/dev/stdout" : *argv, bits);
+ if (cat)
+ isstdout = 1;
+ } else {
+ if (len - 2 > sizeof(newname) - 1) {
+ cwarnx("%s: name too long", *argv);
+ break;
+ }
+ memmove(newname, *argv, len - 2);
+ newname[len - 2] = '\0';
+ decompress(*argv,
+ cat ? "/dev/stdout" : newname, bits);
+ if (cat)
+ isstdout = 1;
+ }
+ break;
+ }
+ }
+ exit (eval);
+}
+
+static void
+compress(const char *in, const char *out, int bits)
+{
+ size_t nr;
+ struct stat isb, sb;
+ const char *error = NULL;
+ FILE *ifp, *ofp;
+ int exists, isreg, oreg;
+ u_char buf[BUFSIZ];
+
+ if (!isstdout) {
+ exists = !stat(out, &sb);
+ if (!force && exists && S_ISREG(sb.st_mode) && !permission(out))
+ return;
+ oreg = !exists || S_ISREG(sb.st_mode);
+ } else
+ oreg = 0;
+
+ ifp = ofp = NULL;
+ if ((ifp = fopen(in, "r")) == NULL) {
+ cwarn("%s", in);
+ return;
+ }
+
+ if (!isstdin) {
+ if (stat(in, &isb)) { /* DON'T FSTAT! */
+ cwarn("%s", in);
+ goto err;
+ }
+ if (!S_ISREG(isb.st_mode))
+ isreg = 0;
+ else
+ isreg = 1;
+ } else
+ isreg = 0;
+
+ if ((ofp = zopen(out, "w", bits)) == NULL) {
+ cwarn("%s", out);
+ goto err;
+ }
+ oreg <<= 1;
+ while ((nr = fread(buf, 1, sizeof(buf), ifp)) != 0)
+ if (fwrite(buf, 1, nr, ofp) != nr) {
+ cwarn("%s", out);
+ goto err;
+ }
+
+ if (ferror(ifp))
+ error = in;
+ if (fclose(ifp))
+ if (error == NULL)
+ error = in;
+ if (fclose(ofp))
+ if (error == NULL)
+ error = out;
+ ifp = NULL;
+ ofp = NULL;
+ if (error) {
+ cwarn("%s", error);
+ goto err;
+ }
+
+ if (isreg && oreg) {
+ if (stat(out, &sb)) {
+ cwarn("%s", out);
+ goto err;
+ }
+
+ if (!force && sb.st_size >= isb.st_size) {
+ if (verbose)
+ (void)printf("%s: file would grow; left unmodified\n", in);
+ goto err;
+ }
+
+ setfile(out, &isb);
+
+ if (unlink(in))
+ cwarn("%s", in);
+
+ if (verbose) {
+ (void)printf("%s: ", out);
+ if (isb.st_size > sb.st_size)
+ (void)printf("%.0f%% compression\n",
+ ((double)sb.st_size / isb.st_size) * 100.0);
+ else
+ (void)printf("%.0f%% expansion\n",
+ ((double)isb.st_size / sb.st_size) * 100.0);
+ }
+ }
+ return;
+
+err: if (ofp)
+ (void)fclose(ofp);
+ if (oreg == 2)
+ (void)unlink(out);
+ if (ifp)
+ (void)fclose(ifp);
+}
+
+static void
+decompress(const char *in, const char *out, int bits)
+{
+ size_t nr;
+ struct stat sb;
+ FILE *ifp, *ofp;
+ int exists, isreg, oreg;
+ u_char buf[BUFSIZ];
+
+ if (!isstdout) {
+ exists = !stat(out, &sb);
+ if (!force && exists && S_ISREG(sb.st_mode) && !permission(out))
+ return;
+ oreg = !exists || S_ISREG(sb.st_mode);
+ } else
+ oreg = 0;
+
+ ifp = ofp = NULL;
+ if ((ofp = fopen(out, "w")) == NULL) {
+ cwarn("%s", out);
+ return;
+ }
+
+ if ((ifp = zopen(in, "r", bits)) == NULL) {
+ cwarn("%s", in);
+ goto err;
+ }
+ if (!isstdin) {
+ if (stat(in, &sb)) {
+ cwarn("%s", in);
+ goto err;
+ }
+ if (!S_ISREG(sb.st_mode))
+ isreg = 0;
+ else
+ isreg = 1;
+ } else
+ isreg = 0;
+
+ oreg <<= 1;
+ while ((nr = fread(buf, 1, sizeof(buf), ifp)) != 0)
+ if (fwrite(buf, 1, nr, ofp) != nr) {
+ cwarn("%s", out);
+ goto err;
+ }
+
+ if (ferror(ifp)) {
+ cwarn("%s", in);
+ goto err;
+ }
+ if (fclose(ifp)) {
+ ifp = NULL;
+ cwarn("%s", in);
+ goto err;
+ }
+ ifp = NULL;
+
+ if (fclose(ofp)) {
+ ofp = NULL;
+ cwarn("%s", out);
+ goto err;
+ }
+
+ if (isreg && oreg) {
+ setfile(out, &sb);
+
+ if (unlink(in))
+ cwarn("%s", in);
+ }
+ return;
+
+err: if (ofp)
+ (void)fclose(ofp);
+ if (oreg == 2)
+ (void)unlink(out);
+ if (ifp)
+ (void)fclose(ifp);
+}
+
+static void
+setfile(const char *name, struct stat *fs)
+{
+ static struct timeval tv[2];
+
+ fs->st_mode &= S_ISUID|S_ISGID|S_IRWXU|S_IRWXG|S_IRWXO;
+
+ TIMESPEC_TO_TIMEVAL(&tv[0], &fs->st_atimespec);
+ TIMESPEC_TO_TIMEVAL(&tv[1], &fs->st_mtimespec);
+ if (utimes(name, tv))
+ cwarn("utimes: %s", name);
+
+ /*
+ * Changing the ownership probably won't succeed, unless we're root
+ * or POSIX_CHOWN_RESTRICTED is not set. Set uid/gid before setting
+ * the mode; current BSD behavior is to remove all setuid bits on
+ * chown. If chown fails, lose setuid/setgid bits.
+ */
+ if (chown(name, fs->st_uid, fs->st_gid)) {
+ if (errno != EPERM)
+ cwarn("chown: %s", name);
+ fs->st_mode &= ~(S_ISUID|S_ISGID);
+ }
+ if (chmod(name, fs->st_mode))
+ cwarn("chown: %s", name);
+
+ /*
+ * Restore the file's flags. However, do this only if the original
+ * file had any flags set; this avoids a warning on file-systems that
+ * do not support flags.
+ */
+ if (fs->st_flags != 0 && chflags(name, fs->st_flags))
+ cwarn("chflags: %s", name);
+}
+
+static int
+permission(const char *fname)
+{
+ int ch, first;
+
+ if (!isatty(fileno(stderr)))
+ return (0);
+ (void)fprintf(stderr, "overwrite %s? ", fname);
+ first = ch = getchar();
+ while (ch != '\n' && ch != EOF)
+ ch = getchar();
+ return (first == 'y');
+}
+
+static void
+usage(int iscompress)
+{
+ if (iscompress)
+ (void)fprintf(stderr,
+ "usage: compress [-cdfv] [-b bits] [file ...]\n");
+ else
+ (void)fprintf(stderr,
+ "usage: uncompress [-cdfv] [-b bits] [file ...]\n");
+ exit(1);
+}
+
+static void
+cwarnx(const char *fmt, ...)
+{
+ va_list ap;
+
+ va_start(ap, fmt);
+ vwarnx(fmt, ap);
+ va_end(ap);
+ eval = 1;
+}
+
+static void
+cwarn(const char *fmt, ...)
+{
+ va_list ap;
+
+ va_start(ap, fmt);
+ vwarn(fmt, ap);
+ va_end(ap);
+ eval = 1;
+}
diff --git a/usr.bin/compress/doc/NOTES b/usr.bin/compress/doc/NOTES
new file mode 100644
index 0000000..26181bf
--- /dev/null
+++ b/usr.bin/compress/doc/NOTES
@@ -0,0 +1,139 @@
+From: James A. Woods <jaw@eos.arc.nasa.gov>
+
+>From vn Fri Dec 2 18:05:27 1988
+Subject: Re: Looking for C source for RSA
+Newsgroups: sci.crypt
+
+# Illegitimi noncarborundum
+
+Patents are a tar pit.
+
+A good case can be made that most are just a license to sue, and nothing
+is illegal until a patent is upheld in court.
+
+For example, if you receive netnews by means other than 'nntp',
+these very words are being modulated by 'compress',
+a variation on the patented Lempel-Ziv-Welch algorithm.
+
+Original Ziv-Lempel is patent number 4,464,650, and the more powerful
+LZW method is #4,558,302. Yet despite any similarities between 'compress'
+and LZW (the public-domain 'compress' code was designed and given to the
+world before the ink on the Welch patent was dry), no attorneys from Sperry
+(the assignee) have asked you to unplug your Usenet connection.
+
+Why? I can't speak for them, but it is possible the claims are too broad,
+or, just as bad, not broad enough. ('compress' does things not mentioned
+in the Welch patent.) Maybe they realize that they can commercialize
+LZW better by selling hardware implementations rather than by licensing
+software. Again, the LZW software delineated in the patent is *not*
+the same as that of 'compress'.
+
+At any rate, court-tested software patents are a different animal;
+corporate patents in a portfolio are usually traded like baseball cards
+to shut out small fry rather than actually be defended before
+non-technical juries. Perhaps RSA will undergo this test successfully,
+although the grant to "exclude others from making, using, or selling"
+the invention would then only apply to the U.S. (witness the
+Genentech patent of the TPA molecule in the U.S. but struck down
+in Great Britain as too broad.)
+
+The concept is still exotic for those who learned in school the rule of thumb
+that one may patent "apparatus" but not an "idea".
+Apparently this all changed in Diamond v. Diehr (1981) when the U. S. Supreme
+Court reversed itself.
+
+Scholars should consult the excellent article in the Washington and Lee
+Law Review (fall 1984, vol. 41, no. 4) by Anthony and Colwell for a
+comprehensive survey of an area which will remain murky for some time.
+
+Until the dust clears, how you approach ideas which are patented depends
+on how paranoid you are of a legal onslaught. Arbitrary? Yes. But
+the patent bar the CCPA (Court of Customs and Patent Appeals)
+thanks you for any uncertainty as they, at least, stand to gain
+from any trouble.
+
+=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
+From: James A. Woods <jaw@eos.arc.nasa.gov>
+Subject: Re: Looking for C source for RSA (actually 'compress' patents)
+
+ In article <2042@eos.UUCP> you write:
+ >The concept is still exotic for those who learned in school the rule of thumb
+ >that one may patent "apparatus" but not an "idea".
+
+A rule of thumb that has never been completely valid, as any chemical
+engineer can tell you. (Chemical processes were among the earliest patents,
+as I recall.)
+
+ ah yes -- i date myself when relaying out-of-date advice from elderly
+ attorneys who don't even specialize in patents. one other interesting
+ class of patents include the output of optical lens design programs,
+ which yield formulae which can then fairly directly can be molded
+ into glass. although there are restrictions on patenting equations,
+ the "embedded systems" seem to fly past the legal gauntlets.
+
+ anyway, i'm still learning about intellectual property law after
+ several conversations from a unisys (nee sperry) lawyer re 'compress'.
+
+ it's more complicated than this, but they're letting (oral
+ communication only) software versions of 'compress' slide
+ as far as licensing fees go. this includes 'arc', 'stuffit',
+ and other commercial wrappers for 'compress'. yet they are
+ signing up licensees for hardware chips. hewlett-packard
+ supposedly has an active vlsi project, and unisys has
+ board-level lzw-based tape controllers. (to build lzw into
+ a disk controller would be strange, as you'd have to build
+ in a filesystem too!)
+
+ it's byzantine
+ that unisys is in a tiff with hp regarding the patents,
+ after discovering some sort of "compress" button on some
+ hp terminal product. why? well, professor abraham lempel jumped
+ from being department chairman of computer science at technion in
+ israel to sperry (where he got the first patent), but then to work
+ at hewlett-packard on sabbatical. the second welch patent
+ is only weakly derivative of the first, so they want chip
+ licenses and hp relented. however, everyone agrees something
+ like the current unix implementation is the way to go with
+ software, so hp (and ucb) long ago asked spencer thomas and i to sign
+ off on copyright permission (although they didn't need to, it being pd).
+ lempel, hp, and unisys grumbles they can't make money off the
+ software since a good free implementation (not the best --
+ i have more ideas!) escaped via usenet. (lempel's own pascal
+ code was apparently horribly slow.)
+ i don't follow the ibm 'arc' legal bickering; my impression
+ is that the pc folks are making money off the archiver/wrapper
+ look/feel of the thing [if ms-dos can be said to have a look and feel].
+
+ now where is telebit with the compress firmware? in a limbo
+ netherworld, probably, with sperry still welcoming outfits
+ to sign patent licenses, a common tactic to bring other small fry
+ into the fold. the guy who crammed 12-bit compess into the modem
+ there left. also what is transpiring with 'compress' and sys 5 rel 4?
+ beats me, but if sperry got a hold of them on these issues,
+ at&t would likely re-implement another algorithm if they
+ thought 'compress' infringes. needful to say, i don't think
+ it does after the abovementioned legal conversation.
+ my own beliefs on whether algorithms should be patentable at all
+ change with the weather. if the courts finally nail down
+ patent protection for algorithms, academic publication in
+ textbooks will be somewhat at odds with the engineering world,
+ where the textbook codes will simply be a big tease to get
+ money into the patent holder coffers...
+
+ oh, if you implement lzw from the patent, you won't get
+ good rates because it doesn't mention adaptive table reset,
+ lack thereof being *the* serious deficiency of thomas' first version.
+
+ now i know that patent law generally protects against independent
+ re-invention (like the 'xor' hash function pleasantly mentioned
+ in the patent [but not the paper]).
+ but the upshot is that if anyone ever wanted to sue us,
+ we're partially covered with
+ independently-developed twists, plus the fact that some of us work
+ in a bureacratic morass (as contractor to a public agency in my case).
+
+ quite a mess, huh? i've wanted to tell someone this stuff
+ for a long time, for posterity if nothing else.
+
+james
+
diff --git a/usr.bin/compress/doc/README b/usr.bin/compress/doc/README
new file mode 100644
index 0000000..6803287
--- /dev/null
+++ b/usr.bin/compress/doc/README
@@ -0,0 +1,283 @@
+
+ @(#)README 8.1 (Berkeley) 6/9/93
+
+Compress version 4.0 improvements over 3.0:
+ o compress() speedup (10-50%) by changing division hash to xor
+ o decompress() speedup (5-10%)
+ o Memory requirements reduced (3-30%)
+ o Stack requirements reduced to less than 4kb
+ o Removed 'Big+Fast' compress code (FBITS) because of compress speedup
+ o Portability mods for Z8000 and PC/XT (but not zeus 3.2)
+ o Default to 'quiet' mode
+ o Unification of 'force' flags
+ o Manual page overhaul
+ o Portability enhancement for M_XENIX
+ o Removed text on #else and #endif
+ o Added "-V" switch to print version and options
+ o Added #defines for SIGNED_COMPARE_SLOW
+ o Added Makefile and "usermem" program
+ o Removed all floating point computations
+ o New programs: [deleted]
+
+The "usermem" script attempts to determine the maximum process size. Some
+editing of the script may be necessary (see the comments). [It should work
+fine on 4.3 bsd.] If you can't get it to work at all, just create file
+"USERMEM" containing the maximum process size in decimal.
+
+The following preprocessor symbols control the compilation of "compress.c":
+
+ o USERMEM Maximum process memory on the system
+ o SACREDMEM Amount to reserve for other proceses
+ o SIGNED_COMPARE_SLOW Unsigned compare instructions are faster
+ o NO_UCHAR Don't use "unsigned char" types
+ o BITS Overrules default set by USERMEM-SACREDMEM
+ o vax Generate inline assembler
+ o interdata Defines SIGNED_COMPARE_SLOW
+ o M_XENIX Makes arrays < 65536 bytes each
+ o pdp11 BITS=12, NO_UCHAR
+ o z8000 BITS=12
+ o pcxt BITS=12
+ o BSD4_2 Allow long filenames ( > 14 characters) &
+ Call setlinebuf(stderr)
+
+The difference "usermem-sacredmem" determines the maximum BITS that can be
+specified with the "-b" flag.
+
+memory: at least BITS
+------ -- ----- ----
+ 433,484 16
+ 229,600 15
+ 127,536 14
+ 73,464 13
+ 0 12
+
+The default is BITS=16.
+
+The maximum bits can be overrulled by specifying "-DBITS=bits" at
+compilation time.
+
+WARNING: files compressed on a large machine with more bits than allowed by
+a version of compress on a smaller machine cannot be decompressed! Use the
+"-b12" flag to generate a file on a large machine that can be uncompressed
+on a 16-bit machine.
+
+The output of compress 4.0 is fully compatible with that of compress 3.0.
+In other words, the output of compress 4.0 may be fed into uncompress 3.0 or
+the output of compress 3.0 may be fed into uncompress 4.0.
+
+The output of compress 4.0 not compatible with that of
+compress 2.0. However, compress 4.0 still accepts the output of
+compress 2.0. To generate output that is compatible with compress
+2.0, use the undocumented "-C" flag.
+
+ -from mod.sources, submitted by vax135!petsd!joe (Joe Orost), 8/1/85
+--------------------------------
+
+Enclosed is compress version 3.0 with the following changes:
+
+1. "Block" compression is performed. After the BITS run out, the
+ compression ratio is checked every so often. If it is decreasing,
+ the table is cleared and a new set of substrings are generated.
+
+ This makes the output of compress 3.0 not compatible with that of
+ compress 2.0. However, compress 3.0 still accepts the output of
+ compress 2.0. To generate output that is compatible with compress
+ 2.0, use the undocumented "-C" flag.
+
+2. A quiet "-q" flag has been added for use by the news system.
+
+3. The character chaining has been deleted and the program now uses
+ hashing. This improves the speed of the program, especially
+ during decompression. Other speed improvements have been made,
+ such as using putc() instead of fwrite().
+
+4. A large table is used on large machines when a relatively small
+ number of bits is specified. This saves much time when compressing
+ for a 16-bit machine on a 32-bit virtual machine. Note that the
+ speed improvement only occurs when the input file is > 30000
+ characters, and the -b BITS is less than or equal to the cutoff
+ described below.
+
+Most of these changes were made by James A. Woods (ames!jaw). Thank you
+James!
+
+To compile compress:
+
+ cc -O -DUSERMEM=usermem -o compress compress.c
+
+Where "usermem" is the amount of physical user memory available (in bytes).
+If any physical memory is to be reserved for other processes, put in
+"-DSACREDMEM sacredmem", where "sacredmem" is the amount to be reserved.
+
+The difference "usermem-sacredmem" determines the maximum BITS that can be
+specified, and the cutoff bits where the large+fast table is used.
+
+memory: at least BITS cutoff
+------ -- ----- ---- ------
+ 4,718,592 16 13
+ 2,621,440 16 12
+ 1,572,864 16 11
+ 1,048,576 16 10
+ 631,808 16 --
+ 329,728 15 --
+ 178,176 14 --
+ 99,328 13 --
+ 0 12 --
+
+The default memory size is 750,000 which gives a maximum BITS=16 and no
+large+fast table.
+
+The maximum bits can be overruled by specifying "-DBITS=bits" at
+compilation time.
+
+If your machine doesn't support unsigned characters, define "NO_UCHAR"
+when compiling.
+
+If your machine has "int" as 16-bits, define "SHORT_INT" when compiling.
+
+After compilation, move "compress" to a standard executable location, such
+as /usr/local. Then:
+ cd /usr/local
+ ln compress uncompress
+ ln compress zcat
+
+On machines that have a fixed stack size (such as Perkin-Elmer), set the
+stack to at least 12kb. ("setstack compress 12" on Perkin-Elmer).
+
+Next, install the manual (compress.l).
+ cp compress.l /usr/man/manl
+ cd /usr/man/manl
+ ln compress.l uncompress.l
+ ln compress.l zcat.l
+
+ - or -
+
+ cp compress.l /usr/man/man1/compress.1
+ cd /usr/man/man1
+ ln compress.1 uncompress.1
+ ln compress.1 zcat.1
+
+ regards,
+ petsd!joe
+
+Here is a note from the net:
+
+>From hplabs!pesnta!amd!turtlevax!ken Sat Jan 5 03:35:20 1985
+Path: ames!hplabs!pesnta!amd!turtlevax!ken
+From: ken@turtlevax.UUCP (Ken Turkowski)
+Newsgroups: net.sources
+Subject: Re: Compress release 3.0 : sample Makefile
+Organization: CADLINC, Inc. @ Menlo Park, CA
+
+In the compress 3.0 source recently posted to mod.sources, there is a
+#define variable which can be set for optimum performance on a machine
+with a large amount of memory. A program (usermem) to calculate the
+useable amount of physical user memory is enclosed, as well as a sample
+4.2bsd Vax Makefile for compress.
+
+Here is the README file from the previous version of compress (2.0):
+
+>Enclosed is compress.c version 2.0 with the following bugs fixed:
+>
+>1. The packed files produced by compress are different on different
+> machines and dependent on the vax sysgen option.
+> The bug was in the different byte/bit ordering on the
+> various machines. This has been fixed.
+>
+> This version is NOT compatible with the original vax posting
+> unless the '-DCOMPATIBLE' option is specified to the C
+> compiler. The original posting has a bug which I fixed,
+> causing incompatible files. I recommend you NOT to use this
+> option unless you already have a lot of packed files from
+> the original posting by thomas.
+>2. The exit status is not well defined (on some machines) causing the
+> scripts to fail.
+> The exit status is now 0,1 or 2 and is documented in
+> compress.l.
+>3. The function getopt() is not available in all C libraries.
+> The function getopt() is no longer referenced by the
+> program.
+>4. Error status is not being checked on the fwrite() and fflush() calls.
+> Fixed.
+>
+>The following enhancements have been made:
+>
+>1. Added facilities of "compact" into the compress program. "Pack",
+> "Unpack", and "Pcat" are no longer required (no longer supplied).
+>2. Installed work around for C compiler bug with "-O".
+>3. Added a magic number header (\037\235). Put the bits specified
+> in the file.
+>4. Added "-f" flag to force overwrite of output file.
+>5. Added "-c" flag and "zcat" program. 'ln compress zcat' after you
+> compile.
+>6. The 'uncompress' script has been deleted; simply
+> 'ln compress uncompress' after you compile and it will work.
+>7. Removed extra bit masking for machines that support unsigned
+> characters. If your machine doesn't support unsigned characters,
+> define "NO_UCHAR" when compiling.
+>
+>Compile "compress.c" with "-O -o compress" flags. Move "compress" to a
+>standard executable location, such as /usr/local. Then:
+> cd /usr/local
+> ln compress uncompress
+> ln compress zcat
+>
+>On machines that have a fixed stack size (such as Perkin-Elmer), set the
+>stack to at least 12kb. ("setstack compress 12" on Perkin-Elmer).
+>
+>Next, install the manual (compress.l).
+> cp compress.l /usr/man/manl - or -
+> cp compress.l /usr/man/man1/compress.1
+>
+>Here is the README that I sent with my first posting:
+>
+>>Enclosed is a modified version of compress.c, along with scripts to make it
+>>run identically to pack(1), unpack(1), an pcat(1). Here is what I
+>>(petsd!joe) and a colleague (petsd!peora!srd) did:
+>>
+>>1. Removed VAX dependencies.
+>>2. Changed the struct to separate arrays; saves mucho memory.
+>>3. Did comparisons in unsigned, where possible. (Faster on Perkin-Elmer.)
+>>4. Sorted the character next chain and changed the search to stop
+>>prematurely. This saves a lot on the execution time when compressing.
+>>
+>>This version is totally compatible with the original version. Even though
+>>lint(1) -p has no complaints about compress.c, it won't run on a 16-bit
+>>machine, due to the size of the arrays.
+>>
+>>Here is the README file from the original author:
+>>
+>>>Well, with all this discussion about file compression (for news batching
+>>>in particular) going around, I decided to implement the text compression
+>>>algorithm described in the June Computer magazine. The author claimed
+>>>blinding speed and good compression ratios. It's certainly faster than
+>>>compact (but, then, what wouldn't be), but it's also the same speed as
+>>>pack, and gets better compression than both of them. On 350K bytes of
+>>>unix-wizards, compact took about 8 minutes of CPU, pack took about 80
+>>>seconds, and compress (herein) also took 80 seconds. But, compact and
+>>>pack got about 30% compression, whereas compress got over 50%. So, I
+>>>decided I had something, and that others might be interested, too.
+>>>
+>>>As is probably true of compact and pack (although I haven't checked),
+>>>the byte order within a word is probably relevant here, but as long as
+>>>you stay on a single machine type, you should be ok. (Can anybody
+>>>elucidate on this?) There are a couple of asm's in the code (extv and
+>>>insv instructions), so anyone porting it to another machine will have to
+>>>deal with this anyway (and could probably make it compatible with Vax
+>>>byte order at the same time). Anyway, I've linted the code (both with
+>>>and without -p), so it should run elsewhere. Note the longs in the
+>>>code, you can take these out if you reduce BITS to <= 15.
+>>>
+>>>Have fun, and as always, if you make good enhancements, or bug fixes,
+>>>I'd like to see them.
+>>>
+>>>=Spencer (thomas@utah-20, {harpo,hplabs,arizona}!utah-cs!thomas)
+>>
+>> regards,
+>> joe
+>>
+>>--
+>>Full-Name: Joseph M. Orost
+>>UUCP: ..!{decvax,ucbvax,ihnp4}!vax135!petsd!joe
+>>US Mail: MS 313; Perkin-Elmer; 106 Apple St; Tinton Falls, NJ 07724
+>>Phone: (201) 870-5844
diff --git a/usr.bin/compress/doc/revision.log b/usr.bin/compress/doc/revision.log
new file mode 100644
index 0000000..16ca49d
--- /dev/null
+++ b/usr.bin/compress/doc/revision.log
@@ -0,0 +1,116 @@
+/*
+ * $Hdr: compress.c,v 4.0 85/07/30 12:50:00 joe Release $
+ * $Lg: compress.c,v $
+ * Revision 4.0 85/07/30 12:50:00 joe
+ * Removed ferror() calls in output routine on every output except first.
+ * Prepared for release to the world.
+ *
+ * Revision 3.6 85/07/04 01:22:21 joe
+ * Remove much wasted storage by overlaying hash table with the tables
+ * used by decompress: tab_suffix[1<<BITS], stack[8000]. Updated USERMEM
+ * computations. Fixed dump_tab() DEBUG routine.
+ *
+ * Revision 3.5 85/06/30 20:47:21 jaw
+ * Change hash function to use exclusive-or. Rip out hash cache. These
+ * speedups render the megamemory version defunct, for now. Make decoder
+ * stack global. Parts of the RCS trunks 2.7, 2.6, and 2.1 no longer apply.
+ *
+ * Revision 3.4 85/06/27 12:00:00 ken
+ * Get rid of all floating-point calculations by doing all compression ratio
+ * calculations in fixed point.
+ *
+ * Revision 3.3 85/06/24 21:53:24 joe
+ * Incorporate portability suggestion for M_XENIX. Got rid of text on #else
+ * and #endif lines. Cleaned up #ifdefs for vax and interdata.
+ *
+ * Revision 3.2 85/06/06 21:53:24 jaw
+ * Incorporate portability suggestions for Z8000, IBM PC/XT from mailing list.
+ * Default to "quiet" output (no compression statistics).
+ *
+ * Revision 3.1 85/05/12 18:56:13 jaw
+ * Integrate decompress() stack speedups (from early pointer mods by McKie).
+ * Repair multi-file USERMEM gaffe. Unify 'force' flags to mimic semantics
+ * of SVR2 'pack'. Streamline block-compress table clear logic. Increase
+ * output byte count by magic number size.
+ *
+ * Revision 3.0 84/11/27 11:50:00 petsd!joe
+ * Set HSIZE depending on BITS. Set BITS depending on USERMEM. Unrolled
+ * loops in clear routines. Added "-C" flag for 2.0 compatibility. Used
+ * unsigned compares on Perkin-Elmer. Fixed foreground check.
+ *
+ * Revision 2.7 84/11/16 19:35:39 ames!jaw
+ * Cache common hash codes based on input statistics; this improves
+ * performance for low-density raster images. Pass on #ifdef bundle
+ * from Turkowski.
+ *
+ * Revision 2.6 84/11/05 19:18:21 ames!jaw
+ * Vary size of hash tables to reduce time for small files.
+ * Tune PDP-11 hash function.
+ *
+ * Revision 2.5 84/10/30 20:15:14 ames!jaw
+ * Junk chaining; replace with the simpler (and, on the VAX, faster)
+ * double hashing, discussed within. Make block compression standard.
+ *
+ * Revision 2.4 84/10/16 11:11:11 ames!jaw
+ * Introduce adaptive reset for block compression, to boost the rate
+ * another several percent. (See mailing list notes.)
+ *
+ * Revision 2.3 84/09/22 22:00:00 petsd!joe
+ * Implemented "-B" block compress. Implemented REVERSE sorting of tab_next.
+ * Bug fix for last bits. Changed fwrite to putchar loop everywhere.
+ *
+ * Revision 2.2 84/09/18 14:12:21 ames!jaw
+ * Fold in news changes, small machine typedef from thomas,
+ * #ifdef interdata from joe.
+ *
+ * Revision 2.1 84/09/10 12:34:56 ames!jaw
+ * Configured fast table lookup for 32-bit machines.
+ * This cuts user time in half for b <= FBITS, and is useful for news batching
+ * from VAX to PDP sites. Also sped up decompress() [fwrite->putc] and
+ * added signal catcher [plus beef in writeerr()] to delete effluvia.
+ *
+ * Revision 2.0 84/08/28 22:00:00 petsd!joe
+ * Add check for foreground before prompting user. Insert maxbits into
+ * compressed file. Force file being uncompressed to end with ".Z".
+ * Added "-c" flag and "zcat". Prepared for release.
+ *
+ * Revision 1.10 84/08/24 18:28:00 turtlevax!ken
+ * Will only compress regular files (no directories), added a magic number
+ * header (plus an undocumented -n flag to handle old files without headers),
+ * added -f flag to force overwriting of possibly existing destination file,
+ * otherwise the user is prompted for a response. Will tack on a .Z to a
+ * filename if it doesn't have one when decompressing. Will only replace
+ * file if it was compressed.
+ *
+ * Revision 1.9 84/08/16 17:28:00 turtlevax!ken
+ * Removed scanargs(), getopt(), added .Z extension and unlimited number of
+ * filenames to compress. Flags may be clustered (-Ddvb12) or separated
+ * (-D -d -v -b 12), or combination thereof. Modes and other status is
+ * copied with copystat(). -O bug for 4.2 seems to have disappeared with
+ * 1.8.
+ *
+ * Revision 1.8 84/08/09 23:15:00 joe
+ * Made it compatible with vax version, installed jim's fixes/enhancements
+ *
+ * Revision 1.6 84/08/01 22:08:00 joe
+ * Sped up algorithm significantly by sorting the compress chain.
+ *
+ * Revision 1.5 84/07/13 13:11:00 srd
+ * Added C version of vax asm routines. Changed structure to arrays to
+ * save much memory. Do unsigned compares where possible (faster on
+ * Perkin-Elmer)
+ *
+ * Revision 1.4 84/07/05 03:11:11 thomas
+ * Clean up the code a little and lint it. (Lint complains about all
+ * the regs used in the asm, but I'm not going to "fix" this.)
+ *
+ * Revision 1.3 84/07/05 02:06:54 thomas
+ * Minor fixes.
+ *
+ * Revision 1.2 84/07/05 00:27:27 thomas
+ * Add variable bit length output.
+ *
+ */
+
+static char rcs_ident[] =
+ "$Hdr: compress.c,v 4.0 85/07/30 12:50:00 joe Release $";
diff --git a/usr.bin/compress/zopen.3 b/usr.bin/compress/zopen.3
new file mode 100644
index 0000000..32be9c4
--- /dev/null
+++ b/usr.bin/compress/zopen.3
@@ -0,0 +1,138 @@
+.\" $NetBSD: zopen.3,v 1.7 2003/08/07 11:13:29 agc Exp $
+.\"
+.\" Copyright (c) 1992, 1993
+.\" The Regents of the University of California. All rights reserved.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\" notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must reproduce the above copyright
+.\" notice, this list of conditions and the following disclaimer in the
+.\" documentation and/or other materials provided with the distribution.
+.\" 3. Neither the name of the University nor the names of its contributors
+.\" may be used to endorse or promote products derived from this software
+.\" without specific prior written permission.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+.\" ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+.\" SUCH DAMAGE.
+.\"
+.\" @(#)zopen.3 8.1 (Berkeley) 6/9/93
+.\"
+.Dd June 9, 1993
+.Dt ZOPEN 3
+.Os
+.Sh NAME
+.Nm zopen
+.Nd compressed stream open function
+.Sh SYNOPSIS
+.In stdio.h
+.Ft FILE *
+.Fn zopen "const char *path" "const char *mode" "int bits"
+.Sh DESCRIPTION
+The
+.Fn zopen
+function
+opens the compressed file whose name is the string pointed to by
+.Fa path
+and associates a stream with it.
+.Pp
+The argument
+.Fa mode
+points to one of the following one-character strings:
+.Bl -tag -width indent
+.It Dq Li r
+Open compressed file for reading.
+The stream is positioned at the beginning of the file.
+.It Dq Li w
+Truncate file to zero length or create compressed file for writing.
+The stream is positioned at the beginning of the file.
+.El
+.Pp
+Any created files will have mode
+.Pf \\*q Dv S_IRUSR
+\&|
+.Dv S_IWUSR
+\&|
+.Dv S_IRGRP
+\&|
+.Dv S_IWGRP
+\&|
+.Dv S_IROTH
+\&|
+.Dv S_IWOTH Ns \\*q
+.Pq Li 0666 ,
+as modified by the process'
+umask value (see
+.Xr umask 2 ) .
+.Pp
+Files may only be read or written.
+Seek operations are not allowed.
+.Pp
+The
+.Fa bits
+argument, if non-zero, is set to the bits code limit.
+If zero, the default is 16.
+See
+.Fn compress 1
+for more information.
+.Sh RETURN VALUES
+Upon successful completion
+.Fn zopen
+returns a
+.Tn FILE
+pointer.
+Otherwise,
+.Dv NULL
+is returned and the global variable
+.Va errno
+is set to indicate the error.
+.Sh ERRORS
+.Bl -tag -width [EINVAL]
+.It Bq Er EINVAL
+The
+.Fa mode
+or
+.Fa bits
+arguments specified to
+.Fn zopen
+were invalid.
+.It Bq Er EFTYPE
+The compressed file starts with an invalid header, or the compressed
+file is compressed with more bits than can be handled.
+.El
+.Pp
+The
+.Fn zopen
+function may also fail and set
+.Va errno
+for any of the errors specified for the routines
+.Xr fopen 3
+or
+.Xr funopen 3 .
+.Sh SEE ALSO
+.Xr compress 1 ,
+.Xr fopen 3 ,
+.Xr funopen 3
+.Sh HISTORY
+The
+.Nm zopen
+function
+first appeared in
+.Bx 4.4 .
+.Sh BUGS
+The
+.Fn zopen
+function
+may not be portable to systems other than
+.Bx .
diff --git a/usr.bin/compress/zopen.c b/usr.bin/compress/zopen.c
new file mode 100644
index 0000000..0d3f178
--- /dev/null
+++ b/usr.bin/compress/zopen.c
@@ -0,0 +1,699 @@
+/* $NetBSD: zopen.c,v 1.15 2011/08/16 13:55:01 joerg Exp $ */
+
+/*-
+ * Copyright (c) 1985, 1986, 1992, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Diomidis Spinellis and James A. Woods, derived from original
+ * work by Spencer Thomas and Joseph Orost.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#if defined(LIBC_SCCS) && !defined(lint)
+#if 0
+static char sccsid[] = "@(#)zopen.c 8.1 (Berkeley) 6/27/93";
+#else
+static char rcsid[] = "$NetBSD: zopen.c,v 1.15 2011/08/16 13:55:01 joerg Exp $";
+#endif
+#endif /* LIBC_SCCS and not lint */
+
+/*-
+ * fcompress.c - File compression ala IEEE Computer, June 1984.
+ *
+ * Compress authors:
+ * Spencer W. Thomas (decvax!utah-cs!thomas)
+ * Jim McKie (decvax!mcvax!jim)
+ * Steve Davies (decvax!vax135!petsd!peora!srd)
+ * Ken Turkowski (decvax!decwrl!turtlevax!ken)
+ * James A. Woods (decvax!ihnp4!ames!jaw)
+ * Joe Orost (decvax!vax135!petsd!joe)
+ *
+ * Cleaned up and converted to library returning I/O streams by
+ * Diomidis Spinellis <dds@doc.ic.ac.uk>.
+ *
+ * zopen(filename, mode, bits)
+ * Returns a FILE * that can be used for read or write. The modes
+ * supported are only "r" and "w". Seeking is not allowed. On
+ * reading the file is decompressed, on writing it is compressed.
+ * The output is compatible with compress(1) with 16 bit tables.
+ * Any file produced by compress(1) can be read.
+ */
+
+#include <sys/param.h>
+#include <sys/stat.h>
+
+#include <errno.h>
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#define BITS 16 /* Default bits. */
+#define HSIZE 69001 /* 95% occupancy */
+
+/* A code_int must be able to hold 2**BITS values of type int, and also -1. */
+typedef long code_int;
+typedef long count_int;
+
+typedef u_char char_type;
+static char_type magic_header[] =
+ {'\037', '\235'}; /* 1F 9D */
+
+#define BIT_MASK 0x1f /* Defines for third byte of header. */
+#define BLOCK_MASK 0x80
+
+/*
+ * Masks 0x40 and 0x20 are free. I think 0x20 should mean that there is
+ * a fourth header byte (for expansion).
+ */
+#define INIT_BITS 9 /* Initial number of bits/code. */
+
+#define MAXCODE(n_bits) ((1 << (n_bits)) - 1)
+
+struct s_zstate {
+ FILE *zs_fp; /* File stream for I/O */
+ char zs_mode; /* r or w */
+ enum {
+ S_START, S_MIDDLE, S_EOF
+ } zs_state; /* State of computation */
+ int zs_n_bits; /* Number of bits/code. */
+ int zs_maxbits; /* User settable max # bits/code. */
+ code_int zs_maxcode; /* Maximum code, given n_bits. */
+ code_int zs_maxmaxcode; /* Should NEVER generate this code. */
+ count_int zs_htab [HSIZE];
+ u_short zs_codetab [HSIZE];
+ code_int zs_hsize; /* For dynamic table sizing. */
+ code_int zs_free_ent; /* First unused entry. */
+ /*
+ * Block compression parameters -- after all codes are used up,
+ * and compression rate changes, start over.
+ */
+ int zs_block_compress;
+ int zs_clear_flg;
+ long zs_ratio;
+ count_int zs_checkpoint;
+ int zs_offset;
+ long zs_in_count; /* Length of input. */
+ long zs_bytes_out; /* Length of compressed output. */
+ long zs_out_count; /* # of codes output (for debugging). */
+ char_type zs_buf[BITS];
+ union {
+ struct {
+ long zs_fcode;
+ code_int zs_ent;
+ code_int zs_hsize_reg;
+ int zs_hshift;
+ } w; /* Write paramenters */
+ struct {
+ char_type *zs_stackp;
+ int zs_finchar;
+ code_int zs_code, zs_oldcode, zs_incode;
+ int zs_roffset, zs_size;
+ char_type zs_gbuf[BITS];
+ } r; /* Read parameters */
+ } u;
+};
+
+/* Definitions to retain old variable names */
+#define fp zs->zs_fp
+#define zmode zs->zs_mode
+#define state zs->zs_state
+#define n_bits zs->zs_n_bits
+#define maxbits zs->zs_maxbits
+#define maxcode zs->zs_maxcode
+#define maxmaxcode zs->zs_maxmaxcode
+#define htab zs->zs_htab
+#define codetab zs->zs_codetab
+#define hsize zs->zs_hsize
+#define free_ent zs->zs_free_ent
+#define block_compress zs->zs_block_compress
+#define clear_flg zs->zs_clear_flg
+#define ratio zs->zs_ratio
+#define checkpoint zs->zs_checkpoint
+#define offset zs->zs_offset
+#define in_count zs->zs_in_count
+#define bytes_out zs->zs_bytes_out
+#define out_count zs->zs_out_count
+#define buf zs->zs_buf
+#define fcode zs->u.w.zs_fcode
+#define hsize_reg zs->u.w.zs_hsize_reg
+#define ent zs->u.w.zs_ent
+#define hshift zs->u.w.zs_hshift
+#define stackp zs->u.r.zs_stackp
+#define finchar zs->u.r.zs_finchar
+#define code zs->u.r.zs_code
+#define oldcode zs->u.r.zs_oldcode
+#define incode zs->u.r.zs_incode
+#define roffset zs->u.r.zs_roffset
+#define size zs->u.r.zs_size
+#define gbuf zs->u.r.zs_gbuf
+
+/*
+ * To save much memory, we overlay the table used by compress() with those
+ * used by decompress(). The tab_prefix table is the same size and type as
+ * the codetab. The tab_suffix table needs 2**BITS characters. We get this
+ * from the beginning of htab. The output stack uses the rest of htab, and
+ * contains characters. There is plenty of room for any possible stack
+ * (stack used to be 8000 characters).
+ */
+
+#define htabof(i) htab[i]
+#define codetabof(i) codetab[i]
+
+#define tab_prefixof(i) codetabof(i)
+#define tab_suffixof(i) ((char_type *)(htab))[i]
+#define de_stack ((char_type *)&tab_suffixof(1 << BITS))
+
+#define CHECK_GAP 10000 /* Ratio check interval. */
+
+/*
+ * the next two codes should not be changed lightly, as they must not
+ * lie within the contiguous general code space.
+ */
+#define FIRST 257 /* First free entry. */
+#define CLEAR 256 /* Table clear output code. */
+
+static int cl_block(struct s_zstate *);
+static code_int getcode(struct s_zstate *);
+static int output(struct s_zstate *, code_int);
+static int zclose(void *);
+FILE *zopen(const char *, const char *, int);
+static int zread(void *, char *, int);
+static int zwrite(void *, const char *, int);
+
+/*-
+ * Algorithm from "A Technique for High Performance Data Compression",
+ * Terry A. Welch, IEEE Computer Vol 17, No 6 (June 1984), pp 8-19.
+ *
+ * Algorithm:
+ * Modified Lempel-Ziv method (LZW). Basically finds common
+ * substrings and replaces them with a variable size code. This is
+ * deterministic, and can be done on the fly. Thus, the decompression
+ * procedure needs no input table, but tracks the way the table was built.
+ */
+
+/*-
+ * compress write
+ *
+ * Algorithm: use open addressing double hashing (no chaining) on the
+ * prefix code / next character combination. We do a variant of Knuth's
+ * algorithm D (vol. 3, sec. 6.4) along with G. Knott's relatively-prime
+ * secondary probe. Here, the modular division first probe is gives way
+ * to a faster exclusive-or manipulation. Also do block compression with
+ * an adaptive reset, whereby the code table is cleared when the compression
+ * ratio decreases, but after the table fills. The variable-length output
+ * codes are re-sized at this point, and a special CLEAR code is generated
+ * for the decompressor. Late addition: construct the table according to
+ * file size for noticeable speed improvement on small files. Please direct
+ * questions about this implementation to ames!jaw.
+ */
+static int
+zwrite(void *cookie, const char *wbp, int num)
+{
+ code_int i;
+ int c, disp;
+ struct s_zstate *zs;
+ const u_char *bp;
+ u_char tmp;
+ int count;
+
+ if (num == 0)
+ return (0);
+
+ zs = cookie;
+ count = num;
+ bp = (const u_char *)wbp;
+ if (state == S_MIDDLE)
+ goto middle;
+ state = S_MIDDLE;
+
+ maxmaxcode = 1L << maxbits;
+ if (fwrite(magic_header,
+ sizeof(char), sizeof(magic_header), fp) != sizeof(magic_header))
+ return (-1);
+ tmp = (u_char)(maxbits | block_compress);
+ if (fwrite(&tmp, sizeof(char), sizeof(tmp), fp) != sizeof(tmp))
+ return (-1);
+
+ offset = 0;
+ bytes_out = 3; /* Includes 3-byte header mojo. */
+ out_count = 0;
+ clear_flg = 0;
+ ratio = 0;
+ in_count = 1;
+ checkpoint = CHECK_GAP;
+ maxcode = MAXCODE(n_bits = INIT_BITS);
+ free_ent = ((block_compress) ? FIRST : 256);
+
+ ent = *bp++;
+ --count;
+
+ hshift = 0;
+ for (fcode = (long)hsize; fcode < 65536L; fcode *= 2L)
+ hshift++;
+ hshift = 8 - hshift; /* Set hash code range bound. */
+
+ hsize_reg = hsize;
+ memset(htab, 0xff, hsize_reg * sizeof(count_int));
+
+middle: while (count--) {
+ c = *bp++;
+ in_count++;
+ fcode = (long)(((long)c << maxbits) + ent);
+ i = ((c << hshift) ^ ent); /* Xor hashing. */
+
+ if (htabof(i) == fcode) {
+ ent = codetabof(i);
+ continue;
+ } else if ((long)htabof(i) < 0) /* Empty slot. */
+ goto nomatch;
+ disp = hsize_reg - i; /* Secondary hash (after G. Knott). */
+ if (i == 0)
+ disp = 1;
+probe: if ((i -= disp) < 0)
+ i += hsize_reg;
+
+ if (htabof(i) == fcode) {
+ ent = codetabof(i);
+ continue;
+ }
+ if ((long)htabof(i) >= 0)
+ goto probe;
+nomatch: if (output(zs, (code_int) ent) == -1)
+ return (-1);
+ out_count++;
+ ent = c;
+ if (free_ent < maxmaxcode) {
+ codetabof(i) = free_ent++; /* code -> hashtable */
+ htabof(i) = fcode;
+ } else if ((count_int)in_count >=
+ checkpoint && block_compress) {
+ if (cl_block(zs) == -1)
+ return (-1);
+ }
+ }
+ return (num);
+}
+
+static int
+zclose(void *cookie)
+{
+ struct s_zstate *zs;
+ int rval;
+
+ zs = cookie;
+ if (zmode == 'w') { /* Put out the final code. */
+ if (output(zs, (code_int) ent) == -1) {
+ (void)fclose(fp);
+ free(zs);
+ return (-1);
+ }
+ out_count++;
+ if (output(zs, (code_int) - 1) == -1) {
+ (void)fclose(fp);
+ free(zs);
+ return (-1);
+ }
+ }
+ rval = fclose(fp) == EOF ? -1 : 0;
+ free(zs);
+ return (rval);
+}
+
+/*-
+ * Output the given code.
+ * Inputs:
+ * code: A n_bits-bit integer. If == -1, then EOF. This assumes
+ * that n_bits =< (long)wordsize - 1.
+ * Outputs:
+ * Outputs code to the file.
+ * Assumptions:
+ * Chars are 8 bits long.
+ * Algorithm:
+ * Maintain a BITS character long buffer (so that 8 codes will
+ * fit in it exactly). Use the VAX insv instruction to insert each
+ * code in turn. When the buffer fills up empty it and start over.
+ */
+
+static char_type lmask[9] =
+ {0xff, 0xfe, 0xfc, 0xf8, 0xf0, 0xe0, 0xc0, 0x80, 0x00};
+static char_type rmask[9] =
+ {0x00, 0x01, 0x03, 0x07, 0x0f, 0x1f, 0x3f, 0x7f, 0xff};
+
+static int
+output(struct s_zstate *zs, code_int ocode)
+{
+ int bits, r_off;
+ char_type *bp;
+
+ r_off = offset;
+ bits = n_bits;
+ bp = buf;
+ if (ocode >= 0) {
+ /* Get to the first byte. */
+ bp += (r_off >> 3);
+ r_off &= 7;
+ /*
+ * Since ocode is always >= 8 bits, only need to mask the first
+ * hunk on the left.
+ */
+ *bp = (*bp & rmask[r_off]) | ((ocode << r_off) & lmask[r_off]);
+ bp++;
+ bits -= (8 - r_off);
+ ocode >>= 8 - r_off;
+ /* Get any 8 bit parts in the middle (<=1 for up to 16 bits). */
+ if (bits >= 8) {
+ *bp++ = ocode;
+ ocode >>= 8;
+ bits -= 8;
+ }
+ /* Last bits. */
+ if (bits)
+ *bp = ocode;
+ offset += n_bits;
+ if (offset == (n_bits << 3)) {
+ bp = buf;
+ bits = n_bits;
+ bytes_out += bits;
+ if (fwrite(bp, sizeof(char), bits, fp) != (size_t)bits)
+ return (-1);
+ bp += bits;
+ bits = 0;
+ offset = 0;
+ }
+ /*
+ * If the next entry is going to be too big for the ocode size,
+ * then increase it, if possible.
+ */
+ if (free_ent > maxcode || (clear_flg > 0)) {
+ /*
+ * Write the whole buffer, because the input side won't
+ * discover the size increase until after it has read it.
+ */
+ if (offset > 0) {
+ if (fwrite(buf, 1, n_bits, fp) != (size_t)n_bits)
+ return (-1);
+ bytes_out += n_bits;
+ }
+ offset = 0;
+
+ if (clear_flg) {
+ maxcode = MAXCODE(n_bits = INIT_BITS);
+ clear_flg = 0;
+ } else {
+ n_bits++;
+ if (n_bits == maxbits)
+ maxcode = maxmaxcode;
+ else
+ maxcode = MAXCODE(n_bits);
+ }
+ }
+ } else {
+ /* At EOF, write the rest of the buffer. */
+ if (offset > 0) {
+ offset = (offset + 7) / 8;
+ if (fwrite(buf, 1, offset, fp) != (size_t)offset)
+ return (-1);
+ bytes_out += offset;
+ }
+ offset = 0;
+ }
+ return (0);
+}
+
+/*
+ * Decompress read. This routine adapts to the codes in the file building
+ * the "string" table on-the-fly; requiring no table to be stored in the
+ * compressed file. The tables used herein are shared with those of the
+ * compress() routine. See the definitions above.
+ */
+static int
+zread(void *cookie, char *rbp, int num)
+{
+ u_int count;
+ struct s_zstate *zs;
+ u_char *bp, header[3];
+
+ if (num == 0)
+ return (0);
+
+ zs = cookie;
+ count = num;
+ bp = (u_char *)rbp;
+ switch (state) {
+ case S_START:
+ state = S_MIDDLE;
+ break;
+ case S_MIDDLE:
+ goto middle;
+ case S_EOF:
+ goto eof;
+ }
+
+ /* Check the magic number */
+ if (fread(header,
+ sizeof(char), sizeof(header), fp) != sizeof(header) ||
+ memcmp(header, magic_header, sizeof(magic_header)) != 0) {
+ errno = EFTYPE;
+ return (-1);
+ }
+ maxbits = header[2]; /* Set -b from file. */
+ block_compress = maxbits & BLOCK_MASK;
+ maxbits &= BIT_MASK;
+ maxmaxcode = 1L << maxbits;
+ if (maxbits > BITS || maxbits < 12) {
+ errno = EFTYPE;
+ return (-1);
+ }
+ /* As above, initialize the first 256 entries in the table. */
+ maxcode = MAXCODE(n_bits = INIT_BITS);
+ for (code = 255; code >= 0; code--) {
+ tab_prefixof(code) = 0;
+ tab_suffixof(code) = (char_type) code;
+ }
+ free_ent = block_compress ? FIRST : 256;
+ oldcode = -1;
+ stackp = de_stack;
+
+ while ((code = getcode(zs)) > -1) {
+
+ if ((code == CLEAR) && block_compress) {
+ for (code = 255; code >= 0; code--)
+ tab_prefixof(code) = 0;
+ clear_flg = 1;
+ free_ent = FIRST;
+ oldcode = -1;
+ continue;
+ }
+ incode = code;
+
+ /* Special case for kWkWk string. */
+ if (code >= free_ent) {
+ if (code > free_ent || oldcode == -1) {
+ /* Bad stream. */
+ errno = EINVAL;
+ return (-1);
+ }
+ *stackp++ = finchar;
+ code = oldcode;
+ }
+ /*
+ * The above condition ensures that code < free_ent.
+ * The construction of tab_prefixof in turn guarantees that
+ * each iteration decreases code and therefore stack usage is
+ * bound by 1 << BITS - 256.
+ */
+
+ /* Generate output characters in reverse order. */
+ while (code >= 256) {
+ *stackp++ = tab_suffixof(code);
+ code = tab_prefixof(code);
+ }
+ *stackp++ = finchar = tab_suffixof(code);
+
+ /* And put them out in forward order. */
+middle: do {
+ if (count-- == 0)
+ return (num);
+ *bp++ = *--stackp;
+ } while (stackp > de_stack);
+
+ /* Generate the new entry. */
+ if ((code = free_ent) < maxmaxcode && oldcode != -1) {
+ tab_prefixof(code) = (u_short) oldcode;
+ tab_suffixof(code) = finchar;
+ free_ent = code + 1;
+ }
+
+ /* Remember previous code. */
+ oldcode = incode;
+ }
+ state = S_EOF;
+eof: return (num - count);
+}
+
+/*-
+ * Read one code from the standard input. If EOF, return -1.
+ * Inputs:
+ * stdin
+ * Outputs:
+ * code or -1 is returned.
+ */
+static code_int
+getcode(struct s_zstate *zs)
+{
+ code_int gcode;
+ int r_off, bits;
+ char_type *bp;
+
+ bp = gbuf;
+ if (clear_flg > 0 || roffset >= size || free_ent > maxcode) {
+ /*
+ * If the next entry will be too big for the current gcode
+ * size, then we must increase the size. This implies reading
+ * a new buffer full, too.
+ */
+ if (free_ent > maxcode) {
+ n_bits++;
+ if (n_bits == maxbits) /* Won't get any bigger now. */
+ maxcode = maxmaxcode;
+ else
+ maxcode = MAXCODE(n_bits);
+ }
+ if (clear_flg > 0) {
+ maxcode = MAXCODE(n_bits = INIT_BITS);
+ clear_flg = 0;
+ }
+ size = fread(gbuf, 1, n_bits, fp);
+ if (size <= 0) /* End of file. */
+ return (-1);
+ roffset = 0;
+ /* Round size down to integral number of codes. */
+ size = (size << 3) - (n_bits - 1);
+ }
+ r_off = roffset;
+ bits = n_bits;
+
+ /* Get to the first byte. */
+ bp += (r_off >> 3);
+ r_off &= 7;
+
+ /* Get first part (low order bits). */
+ gcode = (*bp++ >> r_off);
+ bits -= (8 - r_off);
+ r_off = 8 - r_off; /* Now, roffset into gcode word. */
+
+ /* Get any 8 bit parts in the middle (<=1 for up to 16 bits). */
+ if (bits >= 8) {
+ gcode |= *bp++ << r_off;
+ r_off += 8;
+ bits -= 8;
+ }
+
+ /* High order bits. */
+ gcode |= (*bp & rmask[bits]) << r_off;
+ roffset += n_bits;
+
+ return (gcode);
+}
+
+static int
+cl_block(struct s_zstate *zs) /* Table clear for block compress. */
+{
+ long rat;
+
+ checkpoint = in_count + CHECK_GAP;
+
+ if (in_count > 0x007fffff) { /* Shift will overflow. */
+ rat = bytes_out >> 8;
+ if (rat == 0) /* Don't divide by zero. */
+ rat = 0x7fffffff;
+ else
+ rat = in_count / rat;
+ } else
+ rat = (in_count << 8) / bytes_out; /* 8 fractional bits. */
+ if (rat > ratio)
+ ratio = rat;
+ else {
+ ratio = 0;
+ memset(htab, 0xff, hsize * sizeof(count_int));
+ free_ent = FIRST;
+ clear_flg = 1;
+ if (output(zs, (code_int) CLEAR) == -1)
+ return (-1);
+ }
+ return (0);
+}
+
+FILE *
+zopen(const char *fname, const char *mode, int bits)
+{
+ struct s_zstate *zs;
+
+ if ((mode[0] != 'r' && mode[0] != 'w') || mode[1] != '\0' ||
+ bits < 0 || bits > BITS) {
+ errno = EINVAL;
+ return (NULL);
+ }
+
+ if ((zs = calloc(1, sizeof(struct s_zstate))) == NULL)
+ return (NULL);
+
+ maxbits = bits ? bits : BITS; /* User settable max # bits/code. */
+ maxmaxcode = 1 << maxbits; /* Should NEVER generate this code. */
+ hsize = HSIZE; /* For dynamic table sizing. */
+ free_ent = 0; /* First unused entry. */
+ block_compress = BLOCK_MASK;
+ clear_flg = 0;
+ ratio = 0;
+ checkpoint = CHECK_GAP;
+ in_count = 1; /* Length of input. */
+ out_count = 0; /* # of codes output (for debugging). */
+ state = S_START;
+ roffset = 0;
+ size = 0;
+
+ /*
+ * Layering compress on top of stdio in order to provide buffering,
+ * and ensure that reads and write work with the data specified.
+ */
+ if ((fp = fopen(fname, mode)) == NULL) {
+ free(zs);
+ return (NULL);
+ }
+ switch (*mode) {
+ case 'r':
+ zmode = 'r';
+ return (funopen(zs, zread, NULL, NULL, zclose));
+ case 'w':
+ zmode = 'w';
+ return (funopen(zs, NULL, zwrite, NULL, zclose));
+ }
+ /* NOTREACHED */
+ return (NULL);
+}
diff --git a/usr.bin/csplit/csplit.1 b/usr.bin/csplit/csplit.1
new file mode 100644
index 0000000..93f96a6
--- /dev/null
+++ b/usr.bin/csplit/csplit.1
@@ -0,0 +1,163 @@
+.\" $NetBSD: csplit.1,v 1.5 2014/02/04 19:48:48 wiz Exp $
+.\"
+.\" Copyright (c) 2002 Tim J. Robbins.
+.\" All rights reserved.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\" notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must reproduce the above copyright
+.\" notice, this list of conditions and the following disclaimer in the
+.\" documentation and/or other materials provided with the distribution.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+.\" ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+.\" SUCH DAMAGE.
+.\"
+.\" $FreeBSD: src/usr.bin/csplit/csplit.1,v 1.11 2005/01/25 22:29:51 tjr Exp $
+.\"
+.Dd February 4, 2014
+.Dt CSPLIT 1
+.Os
+.Sh NAME
+.Nm csplit
+.Nd split files based on context
+.Sh SYNOPSIS
+.Nm
+.Op Fl ks
+.Op Fl f Ar prefix
+.Op Fl n Ar number
+.Ar file args ...
+.Sh DESCRIPTION
+The
+.Nm
+utility splits
+.Ar file
+into pieces using the patterns
+.Ar args .
+If
+.Ar file
+is
+a dash
+.Pq Sq - ,
+.Nm
+reads from standard input.
+.Pp
+Files are created with a prefix of
+.Dq xx
+and two decimal digits.
+The size of each file is written to standard output
+as it is created.
+If an error occurs whilst files are being created,
+or a
+.Dv HUP ,
+.Dv INT ,
+or
+.Dv TERM
+signal is received,
+all files previously written are removed.
+.Pp
+The options are as follows:
+.Bl -tag -offset indent -width 10n
+.It Fl f Ar prefix
+Create file names beginning with
+.Ar prefix ,
+instead of
+.Dq xx .
+.It Fl k
+Do not remove previously created files if an error occurs or a
+.Dv HUP ,
+.Dv INT ,
+or
+.Dv TERM
+signal is received.
+.It Fl n Ar number
+Create file names beginning with
+.Ar number
+of decimal digits after the prefix,
+instead of 2.
+.It Fl s
+Do not write the size of each output file to standard output as it is
+created.
+.El
+.Pp
+The
+.Ar args
+operands may be a combination of the following patterns:
+.Bl -tag -offset indent -width 10n
+.It / Ns Ar regexp Ns / Ns Oo Oo Cm + Ns | Ns Cm - Oc Ns Ar offset Oc
+Create a file containing the input from the current line to (but not including)
+the next line matching the given basic regular expression.
+An optional
+.Ar offset
+from the line that matched may be specified.
+.It % Ns Ar regexp Ns % Ns Oo Oo Cm + Ns | Ns Cm - Oc Ns Ar offset Oc
+Same as above but a file is not created for the output.
+.It Ar line_no
+Create containing the input from the current line to (but not including)
+the specified line number.
+.It Brq Ar num
+Repeat the previous pattern the specified number of times.
+If it follows a line number pattern, a new file will be created for each
+.Ar line_no
+lines,
+.Ar num
+times.
+The first line of the file is line number 1 for historic reasons.
+.El
+.Pp
+After all the patterns have been processed, the remaining input data
+(if there is any) will be written to a new file.
+.Pp
+Requesting to split at a line before the current line number or past the
+end of the file will result in an error.
+.Sh ENVIRONMENT
+The
+.Ev LANG , LC_ALL , LC_COLLATE ,
+and
+.Ev LC_CTYPE
+environment variables affect the execution of
+.Nm
+as described in
+.Xr environ 7 .
+.Sh EXIT STATUS
+.Ex -std
+.Sh EXAMPLES
+Split the
+.Xr mdoc 7
+file
+.Pa foo.1
+into one file for each section (up to 21 plus one for the rest, if any):
+.Pp
+.Dl "$ csplit -k foo.1 '%^\e.Sh%' '/^\e.Sh/' '{20}'"
+.Pp
+Split standard input after the first 99 lines and every 100 lines thereafter:
+.Pp
+.Dl "$ csplit -k - 100 '{19}'"
+.Sh SEE ALSO
+.Xr sed 1 ,
+.Xr split 1 ,
+.Xr re_format 7
+.Sh STANDARDS
+The
+.Nm
+utility conforms to
+.St -p1003.1-2004 .
+.Sh HISTORY
+A
+.Nm
+command appeared in PWB UNIX.
+.Sh BUGS
+Input lines are limited to
+.Dv LINE_MAX
+(2048) bytes in length.
diff --git a/usr.bin/csplit/csplit.c b/usr.bin/csplit/csplit.c
new file mode 100644
index 0000000..9308ed8
--- /dev/null
+++ b/usr.bin/csplit/csplit.c
@@ -0,0 +1,479 @@
+/* $NetBSD: csplit.c,v 1.7 2017/07/30 23:02:53 cheusov Exp $ */
+/* $FreeBSD: src/usr.bin/csplit/csplit.c,v 1.9 2004/03/22 11:15:03 tjr Exp$ */
+
+/*-
+ * Copyright (c) 2002 Tim J. Robbins.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+/*
+ * csplit -- split files based on context
+ *
+ * This utility splits its input into numbered output files by line number
+ * or by a regular expression. Regular expression matches have an optional
+ * offset with them, allowing the split to occur a specified number of
+ * lines before or after the match.
+ *
+ * To handle negative offsets, we stop reading when the match occurs and
+ * store the offset that the file should have been split at, then use
+ * this output file as input until all the "overflowed" lines have been read.
+ * The file is then closed and truncated to the correct length.
+ *
+ * We assume that the output files can be seeked upon (ie. they cannot be
+ * symlinks to named pipes or character devices), but make no such
+ * assumption about the input.
+ */
+
+#include <sys/cdefs.h>
+#ifndef lint
+__RCSID("$NetBSD: csplit.c,v 1.7 2017/07/30 23:02:53 cheusov Exp $");
+#endif
+
+#include <sys/types.h>
+
+#include <ctype.h>
+#include <err.h>
+#include <errno.h>
+#include <limits.h>
+#include <locale.h>
+#include <regex.h>
+#include <signal.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <util.h>
+
+static void cleanup(void);
+static void do_lineno(const char *);
+static void do_rexp(const char *);
+static char *get_line(void);
+static void handlesig(int);
+static FILE *newfile(void);
+static void toomuch(FILE *, long);
+static void usage(void) __dead;
+
+/*
+ * Command line options
+ */
+static const char *prefix; /* File name prefix */
+static long sufflen; /* Number of decimal digits for suffix */
+static int sflag; /* Suppress output of file names */
+static int kflag; /* Keep output if error occurs */
+
+/*
+ * Other miscellaneous globals (XXX too many)
+ */
+static long lineno; /* Current line number in input file */
+static long reps; /* Number of repetitions for this pattern */
+static long nfiles; /* Number of files output so far */
+static long maxfiles; /* Maximum number of files we can create */
+static char currfile[PATH_MAX]; /* Current output file */
+static const char *infn; /* Name of the input file */
+static FILE *infile; /* Input file handle */
+static FILE *overfile; /* Overflow file for toomuch() */
+static off_t truncofs; /* Offset this file should be truncated at */
+static int doclean; /* Should cleanup() remove output? */
+
+int
+main(int argc, char *argv[])
+{
+ struct sigaction sa;
+ long i;
+ int ch;
+ const char *expr;
+ char *ep, *p;
+ FILE *ofp;
+
+ (void)setlocale(LC_ALL, "");
+
+ kflag = sflag = 0;
+ prefix = "xx";
+ sufflen = 2;
+ while ((ch = getopt(argc, argv, "ksf:n:")) > 0) {
+ switch (ch) {
+ case 'f':
+ prefix = optarg;
+ break;
+ case 'k':
+ kflag = 1;
+ break;
+ case 'n':
+ errno = 0;
+ sufflen = strtol(optarg, &ep, 10);
+ if (sufflen <= 0 || *ep != '\0' || errno != 0)
+ errx(1, "%s: bad suffix length", optarg);
+ break;
+ case 's':
+ sflag = 1;
+ break;
+ default:
+ usage();
+ /*NOTREACHED*/
+ }
+ }
+
+ if (sufflen + strlen(prefix) >= PATH_MAX)
+ errx(1, "name too long");
+
+ argc -= optind;
+ argv += optind;
+
+ if ((infn = *argv++) == NULL)
+ usage();
+ if (strcmp(infn, "-") == 0) {
+ infile = stdin;
+ infn = "stdin";
+ } else if ((infile = fopen(infn, "r")) == NULL)
+ err(1, "%s", infn);
+
+ if (!kflag) {
+ doclean = 1;
+ (void)atexit(cleanup);
+ sa.sa_flags = 0;
+ sa.sa_handler = handlesig;
+ (void)sigemptyset(&sa.sa_mask);
+ (void)sigaddset(&sa.sa_mask, SIGHUP);
+ (void)sigaddset(&sa.sa_mask, SIGINT);
+ (void)sigaddset(&sa.sa_mask, SIGTERM);
+ (void)sigaction(SIGHUP, &sa, NULL);
+ (void)sigaction(SIGINT, &sa, NULL);
+ (void)sigaction(SIGTERM, &sa, NULL);
+ }
+
+ lineno = 0;
+ nfiles = 0;
+ truncofs = 0;
+ overfile = NULL;
+
+ /* Ensure 10^sufflen < LONG_MAX. */
+ for (maxfiles = 1, i = 0; i < sufflen; i++) {
+ if (maxfiles > LONG_MAX / 10)
+ errx(1, "%ld: suffix too long (limit %ld)",
+ sufflen, i);
+ maxfiles *= 10;
+ }
+
+ /* Create files based on supplied patterns. */
+ while (nfiles < maxfiles - 1 && (expr = *argv++) != NULL) {
+ /* Look ahead & see if this pattern has any repetitions. */
+ if (*argv != NULL && **argv == '{') {
+ errno = 0;
+ reps = strtol(*argv + 1, &ep, 10);
+ if (reps < 0 || *ep != '}' || errno != 0)
+ errx(1, "%s: bad repetition count", *argv + 1);
+ argv++;
+ } else
+ reps = 0;
+
+ if (*expr == '/' || *expr == '%') {
+ do
+ do_rexp(expr);
+ while (reps-- != 0 && nfiles < maxfiles - 1);
+ } else if (isdigit((unsigned char)*expr))
+ do_lineno(expr);
+ else
+ errx(1, "%s: unrecognised pattern", expr);
+ }
+
+ /* Copy the rest into a new file. */
+ if (!feof(infile)) {
+ ofp = newfile();
+ while ((p = get_line()) != NULL && fputs(p, ofp) != EOF)
+ ;
+ if (!sflag)
+ (void)printf("%jd\n", (intmax_t)ftello(ofp));
+ if (fclose(ofp) != 0)
+ err(1, "%s", currfile);
+ }
+
+ toomuch(NULL, 0L);
+ doclean = 0;
+
+ return (0);
+}
+
+static void
+usage(void)
+{
+
+ (void)fprintf(stderr,
+"Usage: %s [-ks] [-f prefix] [-n number] file args ...\n", getprogname());
+ exit(1);
+}
+
+__dead static void
+handlesig(int sig)
+{
+ char msg[BUFSIZ];
+ size_t len;
+
+ len = snprintf(msg, sizeof(msg), "%s: Caught %s, cleaning up\n",
+ getprogname(), strsignal(sig));
+ if (len < sizeof(msg))
+ (void)write(STDERR_FILENO, msg, len);
+ cleanup();
+ (void)raise_default_signal(sig);
+ _exit(2);
+}
+
+/* Create a new output file. */
+static FILE *
+newfile(void)
+{
+ FILE *fp;
+
+ if ((size_t)snprintf(currfile, sizeof(currfile), "%s%0*ld", prefix,
+ (int)sufflen, nfiles) >= sizeof(currfile))
+ errx(1, "%s: %s", currfile, strerror(ENAMETOOLONG));
+ if ((fp = fopen(currfile, "w+")) == NULL)
+ err(1, "%s", currfile);
+ nfiles++;
+
+ return (fp);
+}
+
+/* Remove partial output, called before exiting. */
+static void
+cleanup(void)
+{
+ char fnbuf[PATH_MAX];
+ long i;
+
+ if (!doclean)
+ return;
+
+ /*
+ * NOTE: One cannot portably assume to be able to call snprintf()
+ * from inside a signal handler. It does, however, appear to be safe
+ * to do on FreeBSD and NetBSD. The solution to this problem is worse
+ * than the problem itself.
+ */
+
+ for (i = 0; i < nfiles; i++) {
+ (void)snprintf(fnbuf, sizeof(fnbuf), "%s%0*ld", prefix,
+ (int)sufflen, i);
+ (void)unlink(fnbuf);
+ }
+}
+
+/* Read a line from the input into a static buffer. */
+static char *
+get_line(void)
+{
+ static char lbuf[LINE_MAX];
+ FILE *src;
+
+ src = overfile != NULL ? overfile : infile;
+
+again: if (fgets(lbuf, sizeof(lbuf), src) == NULL) {
+ if (src == overfile) {
+ src = infile;
+ goto again;
+ }
+ return (NULL);
+ }
+ if (ferror(src))
+ err(1, "%s", infn);
+ lineno++;
+
+ return (lbuf);
+}
+
+/* Conceptually rewind the input (as obtained by get_line()) back `n' lines. */
+static void
+toomuch(FILE *ofp, long n)
+{
+ char buf[BUFSIZ];
+ size_t i, nread;
+
+ if (overfile != NULL) {
+ /*
+ * Truncate the previous file we overflowed into back to
+ * the correct length, close it.
+ */
+ if (fflush(overfile) != 0)
+ err(1, "overflow");
+ if (ftruncate(fileno(overfile), truncofs) != 0)
+ err(1, "overflow");
+ if (fclose(overfile) != 0)
+ err(1, "overflow");
+ overfile = NULL;
+ }
+
+ if (n == 0)
+ /* Just tidying up */
+ return;
+
+ lineno -= n;
+
+ /*
+ * Wind the overflow file backwards to `n' lines before the
+ * current one.
+ */
+ do {
+ if (ftello(ofp) < (off_t)sizeof(buf))
+ rewind(ofp);
+ else
+ (void)fseeko(ofp, -(off_t)sizeof(buf), SEEK_CUR);
+ if (ferror(ofp))
+ errx(1, "%s: can't seek", currfile);
+ if ((nread = fread(buf, 1, sizeof(buf), ofp)) == 0)
+ errx(1, "can't read overflowed output");
+ if (fseeko(ofp, -(off_t)nread, SEEK_CUR) != 0)
+ err(1, "%s", currfile);
+ for (i = 1; i <= nread; i++)
+ if (buf[nread - i] == '\n' && n-- == 0)
+ break;
+ if (ftello(ofp) == 0)
+ break;
+ } while (n > 0);
+ if (fseeko(ofp, (off_t)nread - i + 1, SEEK_CUR) != 0)
+ err(1, "%s", currfile);
+
+ /*
+ * get_line() will read from here. Next call will truncate to
+ * truncofs in this file.
+ */
+ overfile = ofp;
+ truncofs = ftello(overfile);
+}
+
+/* Handle splits for /regexp/ and %regexp% patterns. */
+static void
+do_rexp(const char *expr)
+{
+ regex_t cre;
+ intmax_t nwritten;
+ long ofs;
+ int first;
+ char *ecopy, *ep, *p, *pofs, *re;
+ FILE *ofp;
+
+ if ((ecopy = strdup(expr)) == NULL)
+ err(1, "strdup");
+
+ re = ecopy + 1;
+ if ((pofs = strrchr(ecopy, *expr)) == NULL || pofs[-1] == '\\')
+ errx(1, "%s: missing trailing %c", expr, *expr);
+ *pofs++ = '\0';
+
+ if (*pofs != '\0') {
+ errno = 0;
+ ofs = strtol(pofs, &ep, 10);
+ if (*ep != '\0' || errno != 0)
+ errx(1, "%s: bad offset", pofs);
+ } else
+ ofs = 0;
+
+ if (regcomp(&cre, re, REG_BASIC|REG_NOSUB) != 0)
+ errx(1, "%s: bad regular expression", re);
+
+ if (*expr == '/')
+ /* /regexp/: Save results to a file. */
+ ofp = newfile();
+ else {
+ /* %regexp%: Make a temporary file for overflow. */
+ if ((ofp = tmpfile()) == NULL)
+ err(1, "tmpfile");
+ }
+
+ /* Read and output lines until we get a match. */
+ first = 1;
+ while ((p = get_line()) != NULL) {
+ if (fputs(p, ofp) == EOF)
+ break;
+ if (!first && regexec(&cre, p, 0, NULL, 0) == 0)
+ break;
+ first = 0;
+ }
+
+ if (p == NULL)
+ errx(1, "%s: no match", re);
+
+ if (ofs <= 0) {
+ /*
+ * Negative (or zero) offset: throw back any lines we should
+ * not have read yet.
+ */
+ if (p != NULL) {
+ toomuch(ofp, -ofs + 1);
+ nwritten = (intmax_t)truncofs;
+ } else
+ nwritten = (intmax_t)ftello(ofp);
+ } else {
+ /*
+ * Positive offset: copy the requested number of lines
+ * after the match.
+ */
+ while (--ofs > 0 && (p = get_line()) != NULL)
+ if (fputs(p, ofp) == EOF)
+ break;
+ toomuch(NULL, 0L);
+ nwritten = (intmax_t)ftello(ofp);
+ if (fclose(ofp) != 0)
+ err(1, "%s", currfile);
+ }
+
+ if (!sflag && *expr == '/')
+ (void)printf("%jd\n", nwritten);
+
+ regfree(&cre);
+ free(ecopy);
+}
+
+/* Handle splits based on line number. */
+static void
+do_lineno(const char *expr)
+{
+ long lastline, tgtline;
+ char *ep, *p;
+ FILE *ofp;
+
+ errno = 0;
+ tgtline = strtol(expr, &ep, 10);
+ if (tgtline <= 0 || errno != 0 || *ep != '\0')
+ errx(1, "%s: bad line number", expr);
+ lastline = tgtline;
+ if (lastline <= lineno)
+ errx(1, "%s: can't go backwards", expr);
+
+ while (nfiles < maxfiles - 1) {
+ ofp = newfile();
+ while (lineno + 1 != lastline) {
+ if ((p = get_line()) == NULL)
+ errx(1, "%ld: out of range", lastline);
+ if (fputs(p, ofp) == EOF)
+ break;
+ }
+ if (!sflag)
+ (void)printf("%jd\n", (intmax_t)ftello(ofp));
+ if (fclose(ofp) != 0)
+ err(1, "%s", currfile);
+ if (reps-- == 0)
+ break;
+ lastline += tgtline;
+ }
+}
diff --git a/usr.bin/ctags/C.c b/usr.bin/ctags/C.c
new file mode 100644
index 0000000..9d32b22
--- /dev/null
+++ b/usr.bin/ctags/C.c
@@ -0,0 +1,573 @@
+/* $NetBSD: C.c,v 1.19 2009/07/13 19:05:40 roy Exp $ */
+
+/*
+ * Copyright (c) 1987, 1993, 1994
+ * The Regents of the University of California. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#if HAVE_NBTOOL_CONFIG_H
+#include "nbtool_config.h"
+#endif
+
+#include <sys/cdefs.h>
+#if defined(__RCSID) && !defined(lint)
+#if 0
+static char sccsid[] = "@(#)C.c 8.4 (Berkeley) 4/2/94";
+#else
+__RCSID("$NetBSD: C.c,v 1.19 2009/07/13 19:05:40 roy Exp $");
+#endif
+#endif /* not lint */
+
+#include <limits.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <string.h>
+
+#include "ctags.h"
+
+static int func_entry(void);
+static void hash_entry(void);
+static void skip_string(int);
+static int str_entry(int);
+
+/*
+ * c_entries --
+ * read .c and .h files and call appropriate routines
+ */
+void
+c_entries(void)
+{
+ int c; /* current character */
+ int level; /* brace level */
+ int token; /* if reading a token */
+ int t_def; /* if reading a typedef */
+ int t_level; /* typedef's brace level */
+ char *sp; /* buffer pointer */
+ char tok[MAXTOKEN]; /* token buffer */
+
+ lineftell = ftell(inf);
+ sp = tok; token = t_def = NO; t_level = -1; level = 0; lineno = 1;
+ while (GETC(!=, EOF)) {
+ switch (c) {
+ /*
+ * Here's where it DOESN'T handle: {
+ * foo(a)
+ * {
+ * #ifdef notdef
+ * }
+ * #endif
+ * if (a)
+ * puts("hello, world");
+ * }
+ */
+ case '{':
+ ++level;
+ goto endtok;
+ case '}':
+ /*
+ * if level goes below zero, try and fix
+ * it, even though we've already messed up
+ */
+ if (--level < 0)
+ level = 0;
+ goto endtok;
+
+ case '\n':
+ SETLINE;
+ /*
+ * the above 3 cases are similar in that they
+ * are special characters that also end tokens.
+ */
+ endtok: if (sp > tok) {
+ *sp = EOS;
+ token = YES;
+ sp = tok;
+ }
+ else
+ token = NO;
+ continue;
+
+ /*
+ * We ignore quoted strings and character constants
+ * completely.
+ */
+ case '"':
+ case '\'':
+ (void)skip_string(c);
+ break;
+
+ /*
+ * comments can be fun; note the state is unchanged after
+ * return, in case we found:
+ * "foo() XX comment XX { int bar; }"
+ */
+ case '/':
+ if (GETC(==, '*')) {
+ skip_comment(c);
+ continue;
+ } else if (c == '/') {
+ skip_comment(c);
+ continue;
+ }
+ (void)ungetc(c, inf);
+ c = '/';
+ goto storec;
+
+ /* hash marks flag #define's. */
+ case '#':
+ if (sp == tok) {
+ hash_entry();
+ break;
+ }
+ goto storec;
+
+ /*
+ * if we have a current token, parenthesis on
+ * level zero indicates a function.
+ */
+ case '(':
+ do c = getc(inf);
+ while (c != EOF && iswhite(c));
+ if (c == '*')
+ break;
+ if (c != EOF)
+ ungetc(c, inf);
+ if (!level && token) {
+ int curline;
+
+ if (sp != tok)
+ *sp = EOS;
+ /*
+ * grab the line immediately, we may
+ * already be wrong, for example,
+ * foo\n
+ * (arg1,
+ */
+ get_line();
+ curline = lineno;
+ if (func_entry()) {
+ ++level;
+ pfnote(tok, curline);
+ }
+ break;
+ }
+ goto storec;
+
+ /*
+ * semi-colons indicate the end of a typedef; if we find a
+ * typedef we search for the next semi-colon of the same
+ * level as the typedef. Ignoring "structs", they are
+ * tricky, since you can find:
+ *
+ * "typedef long time_t;"
+ * "typedef unsigned int u_int;"
+ * "typedef unsigned int u_int [10];"
+ *
+ * If looking at a typedef, we save a copy of the last token
+ * found. Then, when we find the ';' we take the current
+ * token if it starts with a valid token name, else we take
+ * the one we saved. There's probably some reasonable
+ * alternative to this...
+ */
+ case ';':
+ if (t_def && level == t_level) {
+ t_def = NO;
+ get_line();
+ if (sp != tok)
+ *sp = EOS;
+ pfnote(tok, lineno);
+ break;
+ }
+ goto storec;
+
+ /*
+ * store characters until one that can't be part of a token
+ * comes along; check the current token against certain
+ * reserved words.
+ */
+ default:
+ storec: if (c == EOF)
+ break;
+ if (!intoken(c)) {
+ if (sp == tok)
+ break;
+ *sp = EOS;
+ if (tflag) {
+ /* no typedefs inside typedefs */
+ if (!t_def &&
+ !memcmp(tok, "typedef",8)) {
+ t_def = YES;
+ t_level = level;
+ break;
+ }
+ /* catch "typedef struct" */
+ if ((!t_def || t_level <= level)
+ && (!memcmp(tok, "struct", 7)
+ || !memcmp(tok, "union", 6)
+ || !memcmp(tok, "enum", 5))) {
+ /*
+ * get line immediately;
+ * may change before '{'
+ */
+ get_line();
+ if (str_entry(c))
+ ++level;
+ break;
+ /* } */
+ }
+ }
+ sp = tok;
+ }
+ else if (sp != tok || begtoken(c)) {
+ if (sp < tok + sizeof tok)
+ *sp++ = c;
+ token = YES;
+ }
+ continue;
+ }
+
+ sp = tok;
+ token = NO;
+ }
+}
+
+/*
+ * func_entry --
+ * handle a function reference
+ */
+static int
+func_entry(void)
+{
+ int c; /* current character */
+ int level = 0; /* for matching '()' */
+ static char attribute[] = "__attribute__";
+ char maybe_attribute[sizeof attribute + 1],
+ *anext;
+
+ /*
+ * Find the end of the assumed function declaration.
+ * Note that ANSI C functions can have type definitions so keep
+ * track of the parentheses nesting level.
+ */
+ while (GETC(!=, EOF)) {
+ switch (c) {
+ case '\'':
+ case '"':
+ /* skip strings and character constants */
+ skip_string(c);
+ break;
+ case '/':
+ /* skip comments */
+ if (GETC(==, '*'))
+ skip_comment(c);
+ else if (c == '/')
+ skip_comment(c);
+ break;
+ case '(':
+ level++;
+ break;
+ case ')':
+ if (level == 0)
+ goto fnd;
+ level--;
+ break;
+ case '\n':
+ SETLINE;
+ }
+ }
+ return (NO);
+fnd:
+ /*
+ * we assume that the character after a function's right paren
+ * is a token character if it's a function and a non-token
+ * character if it's a declaration. Comments don't count...
+ */
+ for (anext = maybe_attribute;;) {
+ while (GETC(!=, EOF) && iswhite(c))
+ if (c == '\n')
+ SETLINE;
+ if (c == EOF)
+ return NO;
+ /*
+ * Recognize the gnu __attribute__ extension, which would
+ * otherwise make the heuristic test DTWT
+ */
+ if (anext == maybe_attribute) {
+ if (intoken(c)) {
+ *anext++ = c;
+ continue;
+ }
+ } else {
+ if (intoken(c)) {
+ if (anext - maybe_attribute
+ < (ptrdiff_t)(sizeof attribute - 1))
+ *anext++ = c;
+ else break;
+ continue;
+ } else {
+ *anext++ = '\0';
+ if (strcmp(maybe_attribute, attribute) == 0) {
+ (void)ungetc(c, inf);
+ return NO;
+ }
+ break;
+ }
+ }
+ if (intoken(c) || c == '{')
+ break;
+ if (c == '/' && GETC(==, '*'))
+ skip_comment(c);
+ else if (c == '/')
+ skip_comment(c);
+ else { /* don't ever "read" '/' */
+ (void)ungetc(c, inf);
+ return (NO);
+ }
+ }
+ if (c != '{')
+ (void)skip_key('{');
+ return (YES);
+}
+
+/*
+ * hash_entry --
+ * handle a line starting with a '#'
+ */
+static void
+hash_entry(void)
+{
+ int c; /* character read */
+ int curline; /* line started on */
+ char *sp; /* buffer pointer */
+ char tok[MAXTOKEN]; /* storage buffer */
+
+ curline = lineno;
+ do if (GETC(==, EOF))
+ return;
+ while(c != '\n' && iswhite(c));
+ ungetc(c, inf);
+ for (sp = tok;;) { /* get next token */
+ if (GETC(==, EOF))
+ return;
+ if (iswhite(c))
+ break;
+ if (sp < tok + sizeof tok)
+ *sp++ = c;
+ }
+ if(sp >= tok + sizeof tok)
+ --sp;
+ *sp = EOS;
+ if (memcmp(tok, "define", 6)) /* only interested in #define's */
+ goto skip;
+ for (;;) { /* this doesn't handle "#define \n" */
+ if (GETC(==, EOF))
+ return;
+ if (!iswhite(c))
+ break;
+ }
+ for (sp = tok;;) { /* get next token */
+ if(sp < tok + sizeof tok)
+ *sp++ = c;
+ if (GETC(==, EOF))
+ return;
+ /*
+ * this is where it DOESN'T handle
+ * "#define \n"
+ */
+ if (!intoken(c))
+ break;
+ }
+ if(sp >= tok + sizeof tok)
+ --sp;
+ *sp = EOS;
+ if (dflag || c == '(') { /* only want macros */
+ get_line();
+ pfnote(tok, curline);
+ }
+skip: if (c == '\n') { /* get rid of rest of define */
+ SETLINE
+ if (*(sp - 1) != '\\')
+ return;
+ }
+ (void)skip_key('\n');
+}
+
+/*
+ * str_entry --
+ * handle a struct, union or enum entry
+ */
+static int
+str_entry(int c /* current character */)
+{
+ int curline; /* line started on */
+ char *sp; /* buffer pointer */
+ char tok[LINE_MAX]; /* storage buffer */
+
+ curline = lineno;
+ while (iswhite(c))
+ if (GETC(==, EOF))
+ return (NO);
+ if (c == '{') /* it was "struct {" */
+ return (YES);
+ for (sp = tok;;) { /* get next token */
+ *sp++ = c;
+ if (GETC(==, EOF))
+ return (NO);
+ if (!intoken(c))
+ break;
+ }
+ switch (c) {
+ case '{': /* it was "struct foo{" */
+ --sp;
+ break;
+ case '\n': /* it was "struct foo\n" */
+ SETLINE;
+ /*FALLTHROUGH*/
+ default: /* probably "struct foo " */
+ while (GETC(!=, EOF))
+ if (!iswhite(c))
+ break;
+ if (c != '{') {
+ (void)ungetc(c, inf);
+ return (NO);
+ }
+ }
+ *sp = EOS;
+ pfnote(tok, curline);
+ return (YES);
+}
+
+/*
+ * skip_comment --
+ * skip over comment
+ */
+void
+skip_comment(int commenttype)
+{
+ int c; /* character read */
+ int star; /* '*' flag */
+
+ for (star = 0; GETC(!=, EOF);)
+ switch(c) {
+ /* comments don't nest, nor can they be escaped. */
+ case '*':
+ star = YES;
+ break;
+ case '/':
+ if (commenttype == '*' && star)
+ return;
+ break;
+ case '\n':
+ if (commenttype == '/') {
+ /*
+ * we don't really parse C, so sometimes it
+ * is necessary to see the newline
+ */
+ ungetc(c, inf);
+ return;
+ }
+ SETLINE;
+ /*FALLTHROUGH*/
+ default:
+ star = NO;
+ break;
+ }
+}
+
+/*
+ * skip_string --
+ * skip to the end of a string or character constant.
+ */
+void
+skip_string(int key)
+{
+ int c,
+ skip;
+
+ for (skip = NO; GETC(!=, EOF); )
+ switch (c) {
+ case '\\': /* a backslash escapes anything */
+ skip = !skip; /* we toggle in case it's "\\" */
+ break;
+ case '\n':
+ SETLINE;
+ /*FALLTHROUGH*/
+ default:
+ if (c == key && !skip)
+ return;
+ skip = NO;
+ }
+}
+
+/*
+ * skip_key --
+ * skip to next char "key"
+ */
+int
+skip_key(int key)
+{
+ int c,
+ skip,
+ retval;
+
+ for (skip = retval = NO; GETC(!=, EOF);)
+ switch(c) {
+ case '\\': /* a backslash escapes anything */
+ skip = !skip; /* we toggle in case it's "\\" */
+ break;
+ case ';': /* special case for yacc; if one */
+ case '|': /* of these chars occurs, we may */
+ retval = YES; /* have moved out of the rule */
+ break; /* not used by C */
+ case '\'':
+ case '"':
+ /* skip strings and character constants */
+ skip_string(c);
+ break;
+ case '/':
+ /* skip comments */
+ if (GETC(==, '*')) {
+ skip_comment(c);
+ break;
+ } else if (c == '/') {
+ skip_comment(c);
+ break;
+ }
+ (void)ungetc(c, inf);
+ c = '/';
+ goto norm;
+ case '\n':
+ SETLINE;
+ /*FALLTHROUGH*/
+ default:
+ norm:
+ if (c == key && !skip)
+ return (retval);
+ skip = NO;
+ }
+ return (retval);
+}
diff --git a/usr.bin/ctags/ctags.1 b/usr.bin/ctags/ctags.1
new file mode 100644
index 0000000..1dd72e8
--- /dev/null
+++ b/usr.bin/ctags/ctags.1
@@ -0,0 +1,225 @@
+.\" $NetBSD: ctags.1,v 1.18 2017/07/03 21:34:19 wiz Exp $
+.\"
+.\" Copyright (c) 1987, 1990, 1993
+.\" The Regents of the University of California. All rights reserved.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\" notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must reproduce the above copyright
+.\" notice, this list of conditions and the following disclaimer in the
+.\" documentation and/or other materials provided with the distribution.
+.\" 3. Neither the name of the University nor the names of its contributors
+.\" may be used to endorse or promote products derived from this software
+.\" without specific prior written permission.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+.\" ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+.\" SUCH DAMAGE.
+.\"
+.\" @(#)ctags.1 8.1 (Berkeley) 6/6/93
+.\"
+.Dd June 6, 1993
+.Dt CTAGS 1
+.Os
+.Sh NAME
+.Nm ctags
+.Nd create a tags file
+.Sh SYNOPSIS
+.Nm
+.Op Fl BFadtuwvx
+.Op Fl f Ar tagsfile
+.Ar name ...
+.Sh DESCRIPTION
+.Nm
+makes a tags file for
+.Xr ex 1
+from the specified C,
+Pascal, Fortran,
+.Tn YACC ,
+lex, and lisp sources.
+A tags file gives the locations of specified objects in a group of files.
+Each line of the tags file contains the object name, the file in which it
+is defined, and a search pattern for the object definition, separated by
+white-space.
+Using the
+.Ar tags
+file,
+.Xr ex 1
+can quickly locate these object definitions.
+Depending upon the options provided to
+.Nm ,
+objects will consist of subroutines, typedefs, defines, structs,
+enums and unions.
+.Bl -tag -width Ds
+.It Fl B
+use backward searching patterns
+.Pq Li ?...? .
+.It Fl F
+use forward searching patterns
+.Pq Li /.../
+(the default).
+.It Fl a
+append to
+.Ar tags
+file.
+.It Fl d
+create tags for
+.Li #defines
+that don't take arguments;
+.Li #defines
+that take arguments are tagged automatically.
+.It Fl f
+Places the tag descriptions in a file called
+.Ar tagsfile .
+The default behaviour is to place them in a file called
+.Ar tags .
+.It Fl t
+create tags for typedefs, structs, unions, and enums.
+.It Fl u
+update the specified files in the
+.Ar tags
+file, that is, all
+references to them are deleted, and the new values are appended to the
+file.
+(Beware: this option is implemented in a way which is rather
+slow; it is usually faster to simply rebuild the
+.Ar tags
+file.)
+.It Fl v
+An index of the form expected by
+.Xr vgrind 1
+is produced on the standard output.
+This listing
+contains the object name, file name, and page number (assuming 64
+line pages).
+Since the output will be sorted into lexicographic order,
+it may be desired to run the output through
+.Xr sort 1 .
+Sample use:
+.Bd -literal -offset indent
+ctags \-v files \&| sort \-f > index
+vgrind \-x index
+.Ed
+.It Fl w
+suppress warning diagnostics.
+.It Fl x
+.Nm
+produces a list of object
+names, the line number and file name on which each is defined, as well
+as the text of that line and prints this on the standard output.
+This
+is a simple index which can be printed out as an off-line readable
+function index.
+.El
+.Pp
+Files whose names end in
+.Sq \&.c
+or
+.Sq \&.h
+are assumed to be C
+source files and are searched for C style routine and macro definitions.
+Files whose names end in
+.Sq \&.y
+are assumed to be
+.Tn YACC
+source files.
+Files whose names end in
+.Sq \&.l
+are assumed to be lisp files if their
+first non-blank character is
+.Sq \&; ,
+.Sq \&( ,
+or
+.Sq \&[ ,
+otherwise, they are
+treated as lex files.
+Other files are first examined to see if they
+contain any Pascal or Fortran routine definitions, and, if not, are
+searched for C style definitions.
+.Pp
+The tag
+.Li main
+is treated specially in C programs.
+The tag formed
+is created by prepending
+.Ar M
+to the name of the file, with the
+trailing
+.Sq \&.c
+and any leading pathname components removed.
+This
+makes use of
+.Nm
+practical in directories with more than one
+program.
+.Pp
+Yacc and lex files each have a special tag.
+.Ar Yyparse
+is the start
+of the second section of the yacc file, and
+.Ar yylex
+is the start of
+the second section of the lex file.
+.Sh FILES
+.Bl -tag -width tags -compact
+.It Pa tags
+default output tags file
+.El
+.Sh EXIT STATUS
+.Ex -std
+Duplicate objects are not considered errors.
+.Sh SEE ALSO
+.Xr ex 1 ,
+.Xr vi 1
+.Sh HISTORY
+The
+.Nm
+command appeared in
+.Bx 3.0 .
+.Sh BUGS
+Recognition of
+.Em functions ,
+.Em subroutines
+and
+.Em procedures
+for
+.Tn FORTRAN
+and Pascal is done in a very simpleminded way.
+No attempt
+is made to deal with block structure; if you have two Pascal procedures
+in different blocks with the same name you lose.
+.Nm
+doesn't
+understand about Pascal types.
+.Pp
+The method of deciding whether to look for C, Pascal or
+.Tn FORTRAN
+functions is a hack.
+.Pp
+.Nm
+relies on the input being well formed, and any syntactical
+errors will completely confuse it.
+It also finds some legal syntax
+confusing; for example, since it doesn't understand
+.Li #ifdef Ns 's
+(incidentally, that's a feature, not a bug), any code with unbalanced
+braces inside
+.Li #ifdef Ns 's
+will cause it to become somewhat disoriented.
+In a similar fashion, multiple line changes within a definition will
+cause it to enter the last line of the object, rather than the first, as
+the searching pattern.
+The last line of multiple line
+.Li typedef Ns 's
+will similarly be noted.
diff --git a/usr.bin/ctags/ctags.c b/usr.bin/ctags/ctags.c
new file mode 100644
index 0000000..5054b1b
--- /dev/null
+++ b/usr.bin/ctags/ctags.c
@@ -0,0 +1,275 @@
+/* $NetBSD: ctags.c,v 1.12 2008/07/21 14:19:22 lukem Exp $ */
+
+/*
+ * Copyright (c) 1987, 1993, 1994, 1995
+ * The Regents of the University of California. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#if HAVE_NBTOOL_CONFIG_H
+#include "nbtool_config.h"
+#endif
+
+#include <sys/cdefs.h>
+#if defined(__COPYRIGHT) && !defined(lint)
+__COPYRIGHT("@(#) Copyright (c) 1987, 1993, 1994, 1995\
+ The Regents of the University of California. All rights reserved.");
+#endif /* not lint */
+
+#if defined(__RCSID) && !defined(lint)
+#if 0
+static char sccsid[] = "@(#)ctags.c 8.4 (Berkeley) 2/7/95";
+#endif
+__RCSID("$NetBSD: ctags.c,v 1.12 2008/07/21 14:19:22 lukem Exp $");
+#endif /* not lint */
+
+#include <err.h>
+#include <limits.h>
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+#include "ctags.h"
+
+/*
+ * ctags: create a tags file
+ */
+
+NODE *head; /* head of the sorted binary tree */
+
+ /* boolean "func" (see init()) */
+bool _wht[256], _etk[256], _itk[256], _btk[256], _gd[256];
+
+FILE *inf; /* ioptr for current input file */
+FILE *outf; /* ioptr for tags file */
+
+long lineftell; /* ftell after getc( inf ) == '\n' */
+
+int lineno; /* line number of current line */
+int dflag; /* -d: non-macro defines */
+int tflag; /* -t: create tags for typedefs */
+int vflag; /* -v: vgrind style index output */
+int wflag; /* -w: suppress warnings */
+int xflag; /* -x: cxref style output */
+
+char *curfile; /* current input file name */
+char searchar = '/'; /* use /.../ searches by default */
+char lbuf[LINE_MAX];
+
+void init(void);
+void find_entries(char *);
+
+int
+main(int argc, char **argv)
+{
+ static const char *outfile = "tags"; /* output file */
+ int aflag; /* -a: append to tags */
+ int uflag; /* -u: update tags */
+ int exit_val; /* exit value */
+ int step; /* step through args */
+ int ch; /* getopts char */
+ char cmd[100]; /* too ugly to explain */
+
+ aflag = uflag = NO;
+ while ((ch = getopt(argc, argv, "BFadf:tuwvx")) != -1)
+ switch(ch) {
+ case 'B':
+ searchar = '?';
+ break;
+ case 'F':
+ searchar = '/';
+ break;
+ case 'a':
+ aflag++;
+ break;
+ case 'd':
+ dflag++;
+ break;
+ case 'f':
+ outfile = optarg;
+ break;
+ case 't':
+ tflag++;
+ break;
+ case 'u':
+ uflag++;
+ break;
+ case 'w':
+ wflag++;
+ break;
+ case 'v':
+ vflag++;
+ case 'x':
+ xflag++;
+ break;
+ case '?':
+ default:
+ goto usage;
+ }
+ argv += optind;
+ argc -= optind;
+ if (!argc) {
+usage: (void)fprintf(stderr,
+ "usage: ctags [-BFadtuwvx] [-f tagsfile] file ...\n");
+ exit(1);
+ }
+
+ init();
+
+ for (exit_val = step = 0; step < argc; ++step)
+ if (!(inf = fopen(argv[step], "r"))) {
+ warn("%s", argv[step]);
+ exit_val = 1;
+ }
+ else {
+ curfile = argv[step];
+ find_entries(argv[step]);
+ (void)fclose(inf);
+ }
+
+ if (head) {
+ if (xflag)
+ put_entries(head);
+ else {
+ if (uflag) {
+ for (step = 0; step < argc; step++) {
+ (void)snprintf(cmd, sizeof(cmd),
+ "mv %s OTAGS; fgrep -v '\t%s\t' OTAGS >%s; rm OTAGS",
+ outfile, argv[step],
+ outfile);
+ system(cmd);
+ }
+ ++aflag;
+ }
+ if (!(outf = fopen(outfile, aflag ? "a" : "w")))
+ err(exit_val, "%s", outfile);
+ put_entries(head);
+ (void)fclose(outf);
+ if (uflag) {
+ (void)snprintf(cmd, sizeof(cmd),
+ "sort -o %s %s", outfile, outfile);
+ system(cmd);
+ }
+ }
+ }
+ exit(exit_val);
+}
+
+/*
+ * init --
+ * this routine sets up the boolean psuedo-functions which work by
+ * setting boolean flags dependent upon the corresponding character.
+ * Every char which is NOT in that string is false with respect to
+ * the pseudo-function. Therefore, all of the array "_wht" is NO
+ * by default and then the elements subscripted by the chars in
+ * CWHITE are set to YES. Thus, "_wht" of a char is YES if it is in
+ * the string CWHITE, else NO.
+ */
+void
+init(void)
+{
+ int i;
+ unsigned const char *sp;
+
+ for (i = 0; i < 256; i++) {
+ _wht[i] = _etk[i] = _itk[i] = _btk[i] = NO;
+ _gd[i] = YES;
+ }
+#define CWHITE " \f\t\n"
+ for (sp = CWHITE; *sp; sp++) /* white space chars */
+ _wht[*sp] = YES;
+#define CTOKEN " \t\n\"'#()[]{}=-+%*/&|^~!<>;,.:?"
+ for (sp = CTOKEN; *sp; sp++) /* token ending chars */
+ _etk[*sp] = YES;
+#define CINTOK "ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz0123456789"
+ for (sp = CINTOK; *sp; sp++) /* valid in-token chars */
+ _itk[*sp] = YES;
+#define CBEGIN "ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz"
+ for (sp = CBEGIN; *sp; sp++) /* token starting chars */
+ _btk[*sp] = YES;
+#define CNOTGD ",;"
+ for (sp = CNOTGD; *sp; sp++) /* invalid after-function chars */
+ _gd[*sp] = NO;
+}
+
+/*
+ * find_entries --
+ * this routine opens the specified file and calls the function
+ * which searches the file.
+ */
+void
+find_entries(char *file)
+{
+ char *cp;
+
+ lineno = 0; /* should be 1 ?? KB */
+ if ((cp = strrchr(file, '.')) != NULL) {
+ if (cp[1] == 'l' && !cp[2]) {
+ int c;
+
+ for (;;) {
+ if (GETC(==, EOF))
+ return;
+ if (!iswhite(c)) {
+ rewind(inf);
+ break;
+ }
+ }
+#define LISPCHR ";(["
+/* lisp */ if (strchr(LISPCHR, c)) {
+ l_entries();
+ return;
+ }
+/* lex */ else {
+ /*
+ * we search all 3 parts of a lex file
+ * for C references. This may be wrong.
+ */
+ toss_yysec();
+ (void)strlcpy(lbuf, "%%$", sizeof(lbuf));
+ pfnote("yylex", lineno);
+ rewind(inf);
+ }
+ }
+/* yacc */ else if (cp[1] == 'y' && !cp[2]) {
+ /*
+ * we search only the 3rd part of a yacc file
+ * for C references. This may be wrong.
+ */
+ toss_yysec();
+ (void)strlcpy(lbuf, "%%$", sizeof(lbuf));
+ pfnote("yyparse", lineno);
+ y_entries();
+ }
+/* fortran */ else if ((cp[1] != 'c' && cp[1] != 'h') && !cp[2]) {
+ if (PF_funcs())
+ return;
+ rewind(inf);
+ }
+ }
+/* C */ c_entries();
+}
diff --git a/usr.bin/ctags/ctags.h b/usr.bin/ctags/ctags.h
new file mode 100644
index 0000000..6aa2aa1
--- /dev/null
+++ b/usr.bin/ctags/ctags.h
@@ -0,0 +1,92 @@
+/* $NetBSD: ctags.h,v 1.9 2009/07/13 19:05:40 roy Exp $ */
+
+/*
+ * Copyright (c) 1987, 1993, 1994
+ * The Regents of the University of California. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * @(#)ctags.h 8.3 (Berkeley) 4/2/94
+ */
+
+#if HAVE_NBTOOL_CONFIG_H
+#include "nbtool_config.h"
+#endif
+
+#define bool char
+
+#define YES 1
+#define NO 0
+#define EOS '\0'
+
+#define ENDLINE 50 /* max length of pattern */
+#define MAXTOKEN 250 /* max size of single token */
+
+#define SETLINE {++lineno;lineftell = ftell(inf);}
+#define GETC(op,exp) ((c = getc(inf)) op (int)exp)
+
+#define iswhite(arg) (_wht[(unsigned)arg]) /* T if char is white */
+#define begtoken(arg) (_btk[(unsigned)arg]) /* T if char can start token */
+#define intoken(arg) (_itk[(unsigned)arg]) /* T if char can be in token */
+#define endtoken(arg) (_etk[(unsigned)arg]) /* T if char ends tokens */
+#define isgood(arg) (_gd[(unsigned)arg]) /* T if char can be after ')' */
+
+typedef struct nd_st { /* sorting structure */
+ struct nd_st *left,
+ *right; /* left and right sons */
+ char *entry, /* function or type name */
+ *file, /* file name */
+ *pat; /* search pattern */
+ int lno; /* for -x option */
+ bool been_warned; /* set if noticed dup */
+} NODE;
+
+extern char *curfile; /* current input file name */
+extern NODE *head; /* head of the sorted binary tree */
+extern FILE *inf; /* ioptr for current input file */
+extern FILE *outf; /* ioptr for current output file */
+extern long lineftell; /* ftell after getc( inf ) == '\n' */
+extern int lineno; /* line number of current line */
+extern int dflag; /* -d: non-macro defines */
+extern int tflag; /* -t: create tags for typedefs */
+extern int vflag; /* -v: vgrind style index output */
+extern int wflag; /* -w: suppress warnings */
+extern int xflag; /* -x: cxref style output */
+extern bool _wht[], _etk[], _itk[], _btk[], _gd[];
+extern char lbuf[LINE_MAX];
+extern char *lbp;
+extern char searchar; /* ex search character */
+
+extern int cicmp(const char *);
+extern void get_line(void);
+extern void pfnote(const char *, int);
+extern int skip_key(int);
+extern void put_entries(NODE *);
+extern void toss_yysec(void);
+extern void l_entries(void);
+extern void y_entries(void);
+extern int PF_funcs(void);
+extern void c_entries(void);
+extern void skip_comment(int);
diff --git a/usr.bin/ctags/fortran.c b/usr.bin/ctags/fortran.c
new file mode 100644
index 0000000..2a9cefd
--- /dev/null
+++ b/usr.bin/ctags/fortran.c
@@ -0,0 +1,174 @@
+/* $NetBSD: fortran.c,v 1.11 2009/07/13 19:05:40 roy Exp $ */
+
+/*
+ * Copyright (c) 1987, 1993, 1994
+ * The Regents of the University of California. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#if HAVE_NBTOOL_CONFIG_H
+#include "nbtool_config.h"
+#endif
+
+#include <sys/cdefs.h>
+#if defined(__RCSID) && !defined(lint)
+#if 0
+static char sccsid[] = "@(#)fortran.c 8.3 (Berkeley) 4/2/94";
+#else
+__RCSID("$NetBSD: fortran.c,v 1.11 2009/07/13 19:05:40 roy Exp $");
+#endif
+#endif /* not lint */
+
+#include <ctype.h>
+#include <limits.h>
+#include <stdio.h>
+#include <string.h>
+
+#include "ctags.h"
+
+static void takeprec(void);
+
+char *lbp; /* line buffer pointer */
+
+int
+PF_funcs(void)
+{
+ bool pfcnt; /* pascal/fortran functions found */
+ char *cp;
+ char tok[MAXTOKEN];
+
+ for (pfcnt = NO;;) {
+ lineftell = ftell(inf);
+ if (!fgets(lbuf, sizeof(lbuf), inf))
+ return (pfcnt);
+ ++lineno;
+ lbp = lbuf;
+ if (*lbp == '%') /* Ratfor escape to fortran */
+ ++lbp;
+ for (; isspace((unsigned char)*lbp); ++lbp)
+ continue;
+ if (!*lbp)
+ continue;
+ switch (*lbp | ' ') { /* convert to lower-case */
+ case 'c':
+ if (cicmp("complex") || cicmp("character"))
+ takeprec();
+ break;
+ case 'd':
+ if (cicmp("double")) {
+ for (; isspace((unsigned char)*lbp); ++lbp)
+ continue;
+ if (!*lbp)
+ continue;
+ if (cicmp("precision"))
+ break;
+ continue;
+ }
+ break;
+ case 'i':
+ if (cicmp("integer"))
+ takeprec();
+ break;
+ case 'l':
+ if (cicmp("logical"))
+ takeprec();
+ break;
+ case 'r':
+ if (cicmp("real"))
+ takeprec();
+ break;
+ }
+ for (; isspace((unsigned char)*lbp); ++lbp)
+ continue;
+ if (!*lbp)
+ continue;
+ switch (*lbp | ' ') {
+ case 'f':
+ if (cicmp("function"))
+ break;
+ continue;
+ case 'p':
+ if (cicmp("program") || cicmp("procedure"))
+ break;
+ continue;
+ case 's':
+ if (cicmp("subroutine"))
+ break;
+ default:
+ continue;
+ }
+ for (; isspace((unsigned char)*lbp); ++lbp)
+ continue;
+ if (!*lbp)
+ continue;
+ for (cp = lbp + 1; *cp && intoken(*cp); ++cp)
+ continue;
+ if ((cp = lbp + 1) != NULL)
+ continue;
+ *cp = EOS;
+ (void)strlcpy(tok, lbp, sizeof(tok));
+ get_line(); /* process line for ex(1) */
+ pfnote(tok, lineno);
+ pfcnt = YES;
+ }
+ /*NOTREACHED*/
+}
+
+/*
+ * cicmp --
+ * do case-independent strcmp
+ */
+int
+cicmp(const char *cp)
+{
+ int len;
+ char *bp;
+
+ for (len = 0, bp = lbp; *cp && (*cp &~ ' ') == (*bp++ &~ ' ');
+ ++cp, ++len)
+ continue;
+ if (!*cp) {
+ lbp += len;
+ return (YES);
+ }
+ return (NO);
+}
+
+static void
+takeprec(void)
+{
+ for (; isspace((unsigned char)*lbp); ++lbp)
+ continue;
+ if (*lbp == '*') {
+ for (++lbp; isspace((unsigned char)*lbp); ++lbp)
+ continue;
+ if (!isdigit((unsigned char)*lbp))
+ --lbp; /* force failure */
+ else
+ while (isdigit((unsigned char)*++lbp))
+ continue;
+ }
+}
diff --git a/usr.bin/ctags/lisp.c b/usr.bin/ctags/lisp.c
new file mode 100644
index 0000000..d143c6c
--- /dev/null
+++ b/usr.bin/ctags/lisp.c
@@ -0,0 +1,112 @@
+/* $NetBSD: lisp.c,v 1.11 2009/07/13 19:05:40 roy Exp $ */
+
+/*
+ * Copyright (c) 1987, 1993, 1994
+ * The Regents of the University of California. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#if HAVE_NBTOOL_CONFIG_H
+#include "nbtool_config.h"
+#endif
+
+#include <sys/cdefs.h>
+#if defined(__RCSID) && !defined(lint)
+#if 0
+static char sccsid[] = "@(#)lisp.c 8.3 (Berkeley) 4/2/94";
+#else
+__RCSID("$NetBSD: lisp.c,v 1.11 2009/07/13 19:05:40 roy Exp $");
+#endif
+#endif /* not lint */
+
+#include <ctype.h>
+#include <limits.h>
+#include <stdio.h>
+#include <string.h>
+
+#include "ctags.h"
+
+/*
+ * lisp tag functions
+ * just look for (def or (DEF
+ */
+void
+l_entries(void)
+{
+ int special;
+ char *cp;
+ char savedc;
+ char tok[MAXTOKEN];
+
+ for (;;) {
+ lineftell = ftell(inf);
+ if (!fgets(lbuf, sizeof(lbuf), inf))
+ return;
+ ++lineno;
+ lbp = lbuf;
+ if (!cicmp("(def"))
+ continue;
+ special = NO;
+ switch(*lbp | ' ') {
+ case 'm':
+ if (cicmp("method"))
+ special = YES;
+ break;
+ case 'w':
+ if (cicmp("wrapper") || cicmp("whopper"))
+ special = YES;
+ }
+ for (; !isspace((unsigned char)*lbp); ++lbp)
+ continue;
+ for (; isspace((unsigned char)*lbp); ++lbp)
+ continue;
+ for (cp = lbp; *cp && *cp != '\n'; ++cp)
+ continue;
+ *cp = EOS;
+ if (special) {
+ if (!(cp = strchr(lbp, ')')))
+ continue;
+ for (; cp >= lbp && *cp != ':'; --cp)
+ continue;
+ if (cp < lbp)
+ continue;
+ lbp = cp;
+ for (; *cp && *cp != ')' && *cp != ' '; ++cp)
+ continue;
+ }
+ else
+ for (cp = lbp + 1;
+ *cp && *cp != '(' && *cp != ' '; ++cp)
+ continue;
+ savedc = *cp;
+ *cp = EOS;
+ (void)strlcpy(tok, lbp, sizeof(tok));
+ *cp = savedc;
+ get_line();
+ pfnote(tok, lineno);
+ }
+ /*NOTREACHED*/
+}
diff --git a/usr.bin/ctags/print.c b/usr.bin/ctags/print.c
new file mode 100644
index 0000000..86fcd3a
--- /dev/null
+++ b/usr.bin/ctags/print.c
@@ -0,0 +1,121 @@
+/* $NetBSD: print.c,v 1.10 2009/07/13 19:05:40 roy Exp $ */
+
+/*
+ * Copyright (c) 1987, 1993, 1994
+ * The Regents of the University of California. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#if HAVE_NBTOOL_CONFIG_H
+#include "nbtool_config.h"
+#endif
+
+#include <sys/cdefs.h>
+#if defined(__RCSID) && !defined(lint)
+#if 0
+static char sccsid[] = "@(#)print.c 8.3 (Berkeley) 4/2/94";
+#else
+__RCSID("$NetBSD: print.c,v 1.10 2009/07/13 19:05:40 roy Exp $");
+#endif
+#endif /* not lint */
+
+#include <limits.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "ctags.h"
+
+/*
+ * get_line --
+ * get the line the token of interest occurred on,
+ * prepare it for printing.
+ */
+void
+get_line(void)
+{
+ long saveftell;
+ int c;
+ int cnt;
+ char *cp;
+
+ saveftell = ftell(inf);
+ (void)fseek(inf, lineftell, SEEK_SET);
+ if (xflag)
+ for (cp = lbuf; GETC(!=, '\n'); *cp++ = c)
+ continue;
+ /*
+ * do all processing here, so we don't step through the
+ * line more than once; means you don't call this routine
+ * unless you're sure you've got a keeper.
+ */
+ else for (cnt = 0, cp = lbuf; GETC(!=, EOF) && cnt < ENDLINE; ++cnt) {
+ if (c == '\\') { /* backslashes */
+ if (cnt > ENDLINE - 2)
+ break;
+ *cp++ = '\\'; *cp++ = '\\';
+ ++cnt;
+ }
+ else if (c == (int)searchar) { /* search character */
+ if (cnt > ENDLINE - 2)
+ break;
+ *cp++ = '\\'; *cp++ = c;
+ ++cnt;
+ }
+ else if (c == '\n') { /* end of keep */
+ *cp++ = '$'; /* can find whole line */
+ break;
+ }
+ else
+ *cp++ = c;
+ }
+ *cp = EOS;
+ (void)fseek(inf, saveftell, SEEK_SET);
+}
+
+/*
+ * put_entries --
+ * write out the tags
+ */
+void
+put_entries(NODE *node)
+{
+
+ if (node->left)
+ put_entries(node->left);
+ if (vflag)
+ printf("%s %s %d\n",
+ node->entry, node->file, (node->lno + 63) / 64);
+ else if (xflag)
+ printf("%-16s%4d %-16s %s\n",
+ node->entry, node->lno, node->file, node->pat);
+ else
+ fprintf(outf, "%s\t%s\t%c^%s%c\n",
+ node->entry, node->file, searchar, node->pat, searchar);
+ if (node->right)
+ put_entries(node->right);
+}
diff --git a/usr.bin/ctags/test/ctags.test b/usr.bin/ctags/test/ctags.test
new file mode 100644
index 0000000..658a073
--- /dev/null
+++ b/usr.bin/ctags/test/ctags.test
@@ -0,0 +1,69 @@
+/* $NetBSD: ctags.test,v 1.2 1995/03/26 20:14:14 glass Exp $ */
+
+int bar = (1 + 5);
+
+FOO("here is a #define test: ) {");
+char sysent[20];
+int nsysent = sizeof (sysent) / sizeof (sysent[0]);
+/*
+ * now is the time for a comment.
+ * four lines in length...
+ */struct struct_xtra{int list;};r4(x,y){};typedef struct{int bar;}struct_xxe;
+#define FOO BAR
+struct struct_three {
+ int list;
+};
+#define SINGLE
+int BAD();
+enum color {red, green, gold, brown};
+char qq[] = " quote(one,two) {int bar;} ";
+typedef struct {
+ int bar;
+ struct struct_two {
+ int foo;
+ union union_3 {
+ struct struct_three entry;
+ char size[25];
+ };
+ struct last {
+ struct struct_three xentry;
+ char list[34];
+ };
+ };
+} struct_one;
+#define TWOLINE ((MAXLIST + FUTURE + 15) \
+ / (time_to_live ? 3 : 4))
+#if (defined(BAR))
+int bar;
+#endif
+#define MULTIPLE {\
+ multiple(one,two); \
+ lineno++; \
+ callroute(one,two); \
+}
+#if defined(BAR)
+int bar;
+#endif
+union union_one {
+ struct struct_three s3;
+ char foo[25];
+};
+#define XYZ(A,B) (A + B / 2) * (3 - 26 + l_lineno)
+routine1(one,two) /* comments here are fun... */
+ struct {
+ int entry;
+ char bar[34];
+ } *one;
+ char two[10];
+{
+typedef unsigned char u_char;
+ register struct buf *bp;
+ five(one,two);
+}
+ routine2 (one,two) { puts("hello\n"); }
+ routine3
+(one,
+two) { puts("world\n"); }
+routine4(int one, char (*two)(void)) /* test ANSI arguments */
+{
+}
diff --git a/usr.bin/ctags/tree.c b/usr.bin/ctags/tree.c
new file mode 100644
index 0000000..93f4a41
--- /dev/null
+++ b/usr.bin/ctags/tree.c
@@ -0,0 +1,145 @@
+/* $NetBSD: tree.c,v 1.12 2006/04/05 19:38:47 dsl Exp $ */
+
+/*
+ * Copyright (c) 1987, 1993, 1994
+ * The Regents of the University of California. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#if HAVE_NBTOOL_CONFIG_H
+#include "nbtool_config.h"
+#endif
+
+#include <sys/cdefs.h>
+#if defined(__RCSID) && !defined(lint)
+#if 0
+static char sccsid[] = "@(#)tree.c 8.3 (Berkeley) 4/2/94";
+#else
+__RCSID("$NetBSD: tree.c,v 1.12 2006/04/05 19:38:47 dsl Exp $");
+#endif
+#endif /* not lint */
+
+#include <err.h>
+#include <limits.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "ctags.h"
+
+static void add_node(NODE *, NODE *);
+static void free_tree(NODE *);
+
+/*
+ * pfnote --
+ * enter a new node in the tree
+ */
+void
+pfnote(const char *name, int ln)
+{
+ NODE *np;
+ char *fp;
+ char nbuf[MAXTOKEN];
+
+ /*NOSTRICT*/
+ if (!(np = (NODE *)malloc(sizeof(NODE)))) {
+ warnx("too many entries to sort");
+ put_entries(head);
+ free_tree(head);
+ /*NOSTRICT*/
+ if (!(head = np = (NODE *)malloc(sizeof(NODE))))
+ err(1, "out of space");
+ }
+ if (!xflag && !strcmp(name, "main")) {
+ if (!(fp = strrchr(curfile, '/')))
+ fp = curfile;
+ else
+ ++fp;
+ (void)snprintf(nbuf, sizeof(nbuf), "M%s", fp);
+ fp = strrchr(nbuf, '.');
+ if (fp && !fp[2])
+ *fp = EOS;
+ name = nbuf;
+ }
+ if (!(np->entry = strdup(name)))
+ err(1, "strdup");
+ np->file = curfile;
+ np->lno = ln;
+ np->left = np->right = 0;
+ if (!(np->pat = strdup(lbuf)))
+ err(1, "strdup");
+ if (!head)
+ head = np;
+ else
+ add_node(np, head);
+}
+
+static void
+add_node(NODE *node, NODE *cur_node)
+{
+ int dif;
+
+ dif = strcmp(node->entry, cur_node->entry);
+ if (!dif) {
+ if (node->file == cur_node->file) {
+ if (!wflag)
+ fprintf(stderr, "Duplicate entry in file %s, line %d: %s\nSecond entry ignored\n", node->file, lineno, node->entry);
+ return;
+ }
+ if (!cur_node->been_warned)
+ if (!wflag)
+ fprintf(stderr, "Duplicate entry in files %s and %s: %s (Warning only)\n", node->file, cur_node->file, node->entry);
+ cur_node->been_warned = YES;
+ }
+ else if (dif < 0) {
+ if (cur_node->left)
+ add_node(node, cur_node->left);
+ else
+ cur_node->left = node;
+ } else {
+ if (cur_node->right)
+ add_node(node, cur_node->right);
+ else
+ cur_node->right = node;
+ }
+}
+
+static void
+free_tree(NODE *node)
+{
+ NODE *nnode;
+
+ for (; node != NULL; node = nnode) {
+ nnode = node->left;
+ if (node->right) {
+ if (nnode == NULL)
+ nnode = node->right;
+ else
+ free_tree(node->right);
+ }
+ free(node);
+ }
+}
diff --git a/usr.bin/ctags/yacc.c b/usr.bin/ctags/yacc.c
new file mode 100644
index 0000000..d17fa99
--- /dev/null
+++ b/usr.bin/ctags/yacc.c
@@ -0,0 +1,160 @@
+/* $NetBSD: yacc.c,v 1.12 2009/07/13 19:05:40 roy Exp $ */
+
+/*
+ * Copyright (c) 1987, 1993, 1994
+ * The Regents of the University of California. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#if HAVE_NBTOOL_CONFIG_H
+#include "nbtool_config.h"
+#endif
+
+#include <sys/cdefs.h>
+#if defined(__RCSID) && !defined(lint)
+#if 0
+static char sccsid[] = "@(#)yacc.c 8.3 (Berkeley) 4/2/94";
+#else
+__RCSID("$NetBSD: yacc.c,v 1.12 2009/07/13 19:05:40 roy Exp $");
+#endif
+#endif /* not lint */
+
+#include <ctype.h>
+#include <limits.h>
+#include <stdio.h>
+#include <string.h>
+
+#include "ctags.h"
+
+/*
+ * y_entries:
+ * find the yacc tags and put them in.
+ */
+void
+y_entries(void)
+{
+ int c;
+ char *sp;
+ bool in_rule;
+ char tok[MAXTOKEN];
+
+ in_rule = NO;
+
+ while (GETC(!=, EOF))
+ switch (c) {
+ case '\n':
+ SETLINE;
+ /* FALLTHROUGH */
+ case ' ':
+ case '\f':
+ case '\r':
+ case '\t':
+ break;
+ case '{':
+ if (skip_key('}'))
+ in_rule = NO;
+ break;
+ case '\'':
+ case '"':
+ if (skip_key(c))
+ in_rule = NO;
+ break;
+ case '%':
+ if (GETC(==, '%'))
+ return;
+ (void)ungetc(c, inf);
+ break;
+ case '/':
+ if (GETC(==, '*'))
+ skip_comment('*');
+ else
+ (void)ungetc(c, inf);
+ break;
+ case '|':
+ case ';':
+ in_rule = NO;
+ break;
+ default:
+ if (in_rule || (!isalpha(c) && c != '.' && c != '_'))
+ break;
+ sp = tok;
+ *sp++ = c;
+ while (GETC(!=, EOF) && (intoken(c) || c == '.'))
+ *sp++ = c;
+ *sp = EOS;
+ get_line(); /* may change before ':' */
+ if (c == EOF)
+ return;
+ while (iswhite(c)) {
+ if (c == '\n')
+ SETLINE;
+ if (GETC(==, EOF))
+ return;
+ }
+ if (c == ':') {
+ pfnote(tok, lineno);
+ in_rule = YES;
+ }
+ else
+ (void)ungetc(c, inf);
+ }
+}
+
+/*
+ * toss_yysec --
+ * throw away lines up to the next "\n%%\n"
+ */
+void
+toss_yysec(void)
+{
+ int c; /* read character */
+ int state;
+
+ /*
+ * state == 0 : waiting
+ * state == 1 : received a newline
+ * state == 2 : received first %
+ * state == 3 : received second %
+ */
+ lineftell = ftell(inf);
+ for (state = 0; GETC(!=, EOF);)
+ switch (c) {
+ case '\n':
+ ++lineno;
+ lineftell = ftell(inf);
+ if (state == 3) /* done! */
+ return;
+ state = 1; /* start over */
+ break;
+ case '%':
+ if (state) /* if 1 or 2 */
+ ++state; /* goto 3 */
+ break;
+ default:
+ state = 0; /* reset */
+ break;
+ }
+}
diff --git a/usr.bin/cut/cut.1 b/usr.bin/cut/cut.1
new file mode 100644
index 0000000..a3dbf55
--- /dev/null
+++ b/usr.bin/cut/cut.1
@@ -0,0 +1,126 @@
+.\" $NetBSD: cut.1,v 1.18 2012/06/20 17:53:19 wiz Exp $
+.\"
+.\" Copyright (c) 1989, 1990, 1993
+.\" The Regents of the University of California. All rights reserved.
+.\"
+.\" This code is derived from software contributed to Berkeley by
+.\" the Institute of Electrical and Electronics Engineers, Inc.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\" notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must reproduce the above copyright
+.\" notice, this list of conditions and the following disclaimer in the
+.\" documentation and/or other materials provided with the distribution.
+.\" 3. Neither the name of the University nor the names of its contributors
+.\" may be used to endorse or promote products derived from this software
+.\" without specific prior written permission.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+.\" ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+.\" SUCH DAMAGE.
+.\"
+.\" @(#)cut.1 8.1 (Berkeley) 6/6/93
+.\"
+.Dd June 12, 2012
+.Dt CUT 1
+.Os
+.Sh NAME
+.Nm cut
+.Nd select portions of each line of a file
+.Sh SYNOPSIS
+.Nm
+.Fl b Ar list
+.Op Fl n
+.Op Ar
+.Nm
+.Fl c Ar list
+.Op Ar
+.Nm
+.Fl f Ar list
+.Op Fl d Ar string
+.Op Fl s
+.Op Ar
+.Sh DESCRIPTION
+The
+.Nm
+utility selects portions of each line (as specified by
+.Ar list )
+from each
+.Ar file
+and writes them to the
+standard output.
+If the
+.Ar file
+argument is a single dash
+.Pq Sq -
+or no
+.Ar file
+arguments were specified, lines are read from the standard input.
+The items specified by
+.Ar list
+can be in terms of column position or in terms of fields delimited
+by a special character.
+Column and field numbering start from 1.
+.Pp
+.Ar list
+is a comma or whitespace separated set of increasing numbers and/or
+number ranges.
+Number ranges consist of a number, a dash
+.Pq Li \- ,
+and a second number
+and select the columns or fields from the first number to the second,
+inclusive.
+Numbers or number ranges may be preceded by a dash, which selects all
+columns or fields from 1 to the first number.
+Numbers or number ranges may be followed by a dash, which selects all
+columns or fields from the last number to the end of the line.
+Numbers and number ranges may be repeated, overlapping, and in any order.
+It is not an error to select columns or fields not present in the
+input line.
+.Pp
+The options are as follows:
+.Bl -tag -width Fl
+.It Fl b Ar list
+.Ar list
+specifies byte positions.
+.It Fl c Ar list
+.Ar list
+specifies character positions.
+.It Fl d Ar string
+Use the first character of
+.Ar string
+as the field delimiter character.
+The default is the
+.Aq TAB
+character.
+.It Fl f Ar list
+.Ar list
+specifies fields, separated by the field delimiter character.
+The selected fields are output,
+separated by the field delimiter character.
+.It Fl n
+Do not split multi-byte characters.
+.It Fl s
+Suppress lines with no field delimiter characters.
+Unless specified, lines with no delimiters are passed through unmodified.
+.El
+.Sh EXIT STATUS
+.Ex -std
+.Sh SEE ALSO
+.Xr paste 1
+.Sh STANDARDS
+The
+.Nm
+utility conforms to
+.St -p1003.2-92 .
diff --git a/usr.bin/cut/cut.c b/usr.bin/cut/cut.c
new file mode 100644
index 0000000..d84b46e
--- /dev/null
+++ b/usr.bin/cut/cut.c
@@ -0,0 +1,306 @@
+/* $NetBSD: cut.c,v 1.29 2014/02/03 20:22:19 wiz Exp $ */
+
+/*
+ * Copyright (c) 1989, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Adam S. Moskowitz of Menlo Consulting and Marciano Pitargue.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+#ifndef lint
+__COPYRIGHT("@(#) Copyright (c) 1989, 1993\
+ The Regents of the University of California. All rights reserved.");
+#endif /* not lint */
+
+#ifndef lint
+#if 0
+static char sccsid[] = "@(#)cut.c 8.3 (Berkeley) 5/4/95";
+#endif
+__RCSID("$NetBSD: cut.c,v 1.29 2014/02/03 20:22:19 wiz Exp $");
+#endif /* not lint */
+
+#include <ctype.h>
+#include <err.h>
+#include <errno.h>
+#include <limits.h>
+#include <locale.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <util.h>
+#include <wchar.h>
+#include <sys/param.h>
+
+static int bflag;
+static int cflag;
+static char dchar;
+static int dflag;
+static int fflag;
+static int sflag;
+
+static void b_cut(FILE *, const char *);
+static void c_cut(FILE *, const char *);
+static void f_cut(FILE *, const char *);
+static void get_list(char *);
+static void usage(void) __dead;
+
+int
+main(int argc, char *argv[])
+{
+ FILE *fp;
+ void (*fcn)(FILE *, const char *);
+ int ch, rval;
+
+ fcn = NULL;
+ (void)setlocale(LC_ALL, "");
+
+ dchar = '\t'; /* default delimiter is \t */
+
+ /* Since we don't support multi-byte characters, the -c and -b
+ options are equivalent, and the -n option is meaningless. */
+ while ((ch = getopt(argc, argv, "b:c:d:f:sn")) != -1)
+ switch(ch) {
+ case 'b':
+ fcn = b_cut;
+ get_list(optarg);
+ bflag = 1;
+ break;
+ case 'c':
+ fcn = c_cut;
+ get_list(optarg);
+ cflag = 1;
+ break;
+ case 'd':
+ dchar = *optarg;
+ dflag = 1;
+ break;
+ case 'f':
+ get_list(optarg);
+ fcn = f_cut;
+ fflag = 1;
+ break;
+ case 's':
+ sflag = 1;
+ break;
+ case 'n':
+ break;
+ case '?':
+ default:
+ usage();
+ }
+ argc -= optind;
+ argv += optind;
+
+ if (fflag) {
+ if (cflag || bflag)
+ usage();
+ } else if ((!cflag && !bflag) || dflag || sflag)
+ usage();
+ else if (bflag && cflag)
+ usage();
+
+ rval = 0;
+ if (*argv)
+ for (; *argv; ++argv) {
+ if (strcmp(*argv, "-") == 0)
+ fcn(stdin, "stdin");
+ else {
+ if ((fp = fopen(*argv, "r"))) {
+ fcn(fp, *argv);
+ (void)fclose(fp);
+ } else {
+ rval = 1;
+ warn("%s", *argv);
+ }
+ }
+ }
+ else
+ fcn(stdin, "stdin");
+ return(rval);
+}
+
+static size_t autostart, autostop, maxval;
+
+static char *positions = NULL;
+static size_t numpositions = 0;
+#define ALLOC_CHUNK _POSIX2_LINE_MAX /* malloc granularity */
+
+static void
+get_list(char *list)
+{
+ size_t setautostart, start, stop;
+ char *pos;
+ char *p;
+
+ if (positions == NULL) {
+ numpositions = ALLOC_CHUNK;
+ positions = ecalloc(numpositions, sizeof(*positions));
+ }
+
+ /*
+ * set a byte in the positions array to indicate if a field or
+ * column is to be selected; use +1, it's 1-based, not 0-based.
+ * This parser is less restrictive than the Draft 9 POSIX spec.
+ * POSIX doesn't allow lists that aren't in increasing order or
+ * overlapping lists. We also handle "-3-5" although there's no
+ * real reason to.
+ */
+ for (; (p = strtok(list, ", \t")) != NULL; list = NULL) {
+ setautostart = start = stop = 0;
+ if (*p == '-') {
+ ++p;
+ setautostart = 1;
+ }
+ if (isdigit((unsigned char)*p)) {
+ start = stop = strtol(p, &p, 10);
+ if (setautostart && start > autostart)
+ autostart = start;
+ }
+ if (*p == '-') {
+ if (isdigit((unsigned char)p[1]))
+ stop = strtol(p + 1, &p, 10);
+ if (*p == '-') {
+ ++p;
+ if (!autostop || autostop > stop)
+ autostop = stop;
+ }
+ }
+ if (*p)
+ errx(1, "[-bcf] list: illegal list value");
+ if (!stop || !start)
+ errx(1, "[-bcf] list: values may not include zero");
+ if (stop + 1 > numpositions) {
+ size_t newsize;
+ newsize = roundup(stop + 1, ALLOC_CHUNK);
+ positions = erealloc(positions, newsize);
+ (void)memset(positions + numpositions, 0,
+ newsize - numpositions);
+ numpositions = newsize;
+ }
+ if (maxval < stop)
+ maxval = stop;
+ for (pos = positions + start; start++ <= stop; pos++)
+ *pos = 1;
+ }
+
+ /* overlapping ranges */
+ if (autostop && maxval > autostop)
+ maxval = autostop;
+
+ /* set autostart */
+ if (autostart)
+ (void)memset(positions + 1, '1', autostart);
+}
+
+static void
+/*ARGSUSED*/
+f_cut(FILE *fp, const char *fname __unused)
+{
+ int ch, field, isdelim;
+ char *pos, *p, sep;
+ int output;
+ size_t len;
+ char *lbuf, *tbuf;
+
+ for (sep = dchar, tbuf = NULL; (lbuf = fgetln(fp, &len)) != NULL;) {
+ output = 0;
+ if (lbuf[len - 1] != '\n') {
+ /* no newline at the end of the last line so add one */
+ if ((tbuf = (char *)malloc(len + 1)) == NULL)
+ err(1, NULL);
+ (void)memcpy(tbuf, lbuf, len);
+ tbuf[len++] = '\n';
+ lbuf = tbuf;
+ }
+ for (isdelim = 0, p = lbuf;; ++p) {
+ ch = *p;
+ /* this should work if newline is delimiter */
+ if (ch == sep)
+ isdelim = 1;
+ if (ch == '\n') {
+ if (!isdelim && !sflag)
+ (void)fwrite(lbuf, len, 1, stdout);
+ break;
+ }
+ }
+ if (!isdelim)
+ continue;
+
+ pos = positions + 1;
+ for (field = maxval, p = lbuf; field; --field, ++pos) {
+ if (*pos) {
+ if (output++)
+ (void)putchar(sep);
+ while ((ch = *p++) != '\n' && ch != sep)
+ (void)putchar(ch);
+ } else {
+ while ((ch = *p++) != '\n' && ch != sep)
+ continue;
+ }
+ if (ch == '\n')
+ break;
+ }
+ if (ch != '\n') {
+ if (autostop) {
+ if (output)
+ (void)putchar(sep);
+ for (; (ch = *p) != '\n'; ++p)
+ (void)putchar(ch);
+ } else
+ for (; (ch = *p) != '\n'; ++p);
+ }
+ (void)putchar('\n');
+ if (tbuf) {
+ free(tbuf);
+ tbuf = NULL;
+ }
+ }
+ if (tbuf)
+ free(tbuf);
+}
+
+static void
+usage(void)
+{
+ (void)fprintf(stderr, "usage:\tcut -b list [-n] [file ...]\n"
+ "\tcut -c list [file ...]\n"
+ "\tcut -f list [-d string] [-s] [file ...]\n");
+ exit(1);
+}
+
+/* make b_put(): */
+#define CUT_BYTE 1
+#include "x_cut.c"
+#undef CUT_BYTE
+
+/* make c_put(): */
+#define CUT_BYTE 0
+#include "x_cut.c"
+#undef CUT_BYTE
diff --git a/usr.bin/cut/x_cut.c b/usr.bin/cut/x_cut.c
new file mode 100644
index 0000000..006b5af
--- /dev/null
+++ b/usr.bin/cut/x_cut.c
@@ -0,0 +1,95 @@
+/* $NetBSD: x_cut.c,v 1.2 2007/07/02 18:41:04 christos Exp $ */
+
+/*
+ * Copyright (c) 1989, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Adam S. Moskowitz of Menlo Consulting and Marciano Pitargue.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+/*
+ * This file is #include'd twice from cut.c, to generate both
+ * single- and multibyte versions of the same code.
+ *
+ * In cut.c #define:
+ * CUT_BYTE=0 to define b_cut (singlebyte), and
+ * CUT_BYTE=1 to define c_cut (multibyte).
+ *
+ */
+
+#if (CUT_BYTE == 1)
+# define CUT_FN b_cut
+# define CUT_CH_T int
+# define CUT_GETC getc
+# define CUT_EOF EOF
+# define CUT_PUTCHAR putchar
+#else
+# define CUT_FN c_cut
+# define CUT_CH_T wint_t
+# define CUT_GETC getwc
+# define CUT_EOF WEOF
+# define CUT_PUTCHAR putwchar
+#endif
+
+
+/* ARGSUSED */
+void
+CUT_FN(FILE *fp, const char *fname __unused)
+{
+ CUT_CH_T ch;
+ int col;
+ char *pos;
+
+ ch = 0;
+ for (;;) {
+ pos = positions + 1;
+ for (col = maxval; col; --col) {
+ if ((ch = CUT_GETC(fp)) == EOF)
+ return;
+ if (ch == '\n')
+ break;
+ if (*pos++)
+ (void)CUT_PUTCHAR(ch);
+ }
+ if (ch != '\n') {
+ if (autostop)
+ while ((ch = CUT_GETC(fp)) != CUT_EOF && ch != '\n')
+ (void)CUT_PUTCHAR(ch);
+ else
+ while ((ch = CUT_GETC(fp)) != CUT_EOF && ch != '\n');
+ }
+ (void)CUT_PUTCHAR('\n');
+ }
+}
+
+#undef CUT_FN
+#undef CUT_CH_T
+#undef CUT_GETC
+#undef CUT_EOF
+#undef CUT_PUTCHAR
+
diff --git a/usr.bin/dirname/dirname.c b/usr.bin/dirname/dirname.c
new file mode 100644
index 0000000..bc9d0ae
--- /dev/null
+++ b/usr.bin/dirname/dirname.c
@@ -0,0 +1,86 @@
+/* $NetBSD: dirname.c,v 1.12 2011/09/16 15:39:25 joerg Exp $ */
+
+/*-
+ * Copyright (c) 1991, 1993, 1994
+ * The Regents of the University of California. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+#ifndef lint
+__COPYRIGHT("@(#) Copyright (c) 1991, 1993, 1994\
+ The Regents of the University of California. All rights reserved.");
+#endif /* not lint */
+
+#ifndef lint
+#if 0
+static char sccsid[] = "@(#)dirname.c 8.4 (Berkeley) 5/4/95";
+#endif
+__RCSID("$NetBSD: dirname.c,v 1.12 2011/09/16 15:39:25 joerg Exp $");
+#endif /* not lint */
+
+#include <err.h>
+#include <libgen.h>
+#include <locale.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+__dead static void usage(void);
+
+int
+main(int argc, char **argv)
+{
+ char *p;
+ int ch;
+
+ setlocale(LC_ALL, "");
+
+ while ((ch = getopt(argc, argv, "")) != -1)
+ switch (ch) {
+ case '?':
+ default:
+ usage();
+ }
+ argc -= optind;
+ argv += optind;
+
+ if (argc != 1)
+ usage();
+
+ if ((p = dirname(*argv)) == NULL)
+ err(1, "%s", *argv);
+ (void)printf("%s\n", p);
+ exit(0);
+}
+
+static void
+usage(void)
+{
+
+ (void)fprintf(stderr, "usage: dirname path\n");
+ exit(1);
+}
diff --git a/usr.bin/du/du.1 b/usr.bin/du/du.1
new file mode 100644
index 0000000..14bb436
--- /dev/null
+++ b/usr.bin/du/du.1
@@ -0,0 +1,168 @@
+.\" $NetBSD: du.1,v 1.23 2012/05/14 21:14:14 wiz Exp $
+.\"
+.\" Copyright (c) 1990, 1993
+.\" The Regents of the University of California. All rights reserved.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\" notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must reproduce the above copyright
+.\" notice, this list of conditions and the following disclaimer in the
+.\" documentation and/or other materials provided with the distribution.
+.\" 3. Neither the name of the University nor the names of its contributors
+.\" may be used to endorse or promote products derived from this software
+.\" without specific prior written permission.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+.\" ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+.\" SUCH DAMAGE.
+.\"
+.\" @(#)du.1 8.2 (Berkeley) 4/1/94
+.\"
+.Dd May 14, 2012
+.Dt DU 1
+.Os
+.Sh NAME
+.Nm du
+.Nd display disk usage statistics
+.Sh SYNOPSIS
+.Nm
+.Op Fl H | Fl L | Fl P
+.Op Fl a | Fl d Ar depth | Fl s
+.Op Fl cghikmnrx
+.Op Ar file ...
+.Sh DESCRIPTION
+The
+.Nm
+utility displays the file system usage for each file argument
+and for each directory in the file hierarchy rooted in each directory
+argument.
+If no file is specified, the block usage of the hierarchy rooted in
+the current directory is displayed.
+.Pp
+The options are as follows:
+.Bl -tag -width Ds
+.It Fl H
+Symbolic links on the command line are followed.
+(Symbolic links encountered in the tree traversal are not followed.)
+.It Fl L
+All symbolic links are followed.
+.It Fl P
+No symbolic links are followed.
+.It Fl a
+Display an entry for each file in the file hierarchy.
+.It Fl c
+Display the grand total after all the arguments have been processed.
+.It Fl d
+Display an entry files and directories
+.Ar depth
+directories deep.
+.It Fl g
+If the
+.Fl g
+flag is specified, the number displayed is the number of gigabyte
+(1024*1024*1024 bytes) blocks.
+.It Fl h
+If the
+.Fl h
+flag is specified, the numbers will be displayed in "human-readable"
+format.
+Use unit suffixes: B (Byte), K (Kilobyte), M (Megabyte), G (Gigabyte),
+T (Terabyte) and P (Petabyte).
+.It Fl i
+Output inode usage instead of blocks.
+All "human-readable" options are ignored.
+.It Fl k
+By default,
+.Nm
+displays the number of blocks as returned by the
+.Xr stat 2
+system call, i.e. 512-byte blocks.
+If the
+.Fl k
+flag is specified, the number displayed is the number of kilobyte
+(1024 bytes) blocks.
+Partial numbers of blocks are rounded up.
+.It Fl m
+If the
+.Fl m
+flag is specified, the number displayed is the number of megabyte
+(1024*1024 bytes) blocks.
+.It Fl n
+Ignore files and directories with user
+.Qq nodump
+flag
+.Pq Dv UF_NODUMP
+set.
+.It Fl r
+Generate warning messages about directories that cannot be read.
+This is the default behaviour.
+.It Fl s
+Display only the grand total for the specified files.
+.It Fl x
+Filesystem mount points are not traversed.
+.El
+.Pp
+.Nm
+counts the storage used by symbolic links and not the files they
+reference unless the
+.Fl H
+or
+.Fl L
+option is specified.
+If either the
+.Fl H
+or
+.Fl L
+options are specified, storage used by any symbolic links which are
+followed is not counted or displayed.
+The
+.Fl H ,
+.Fl L
+and
+.Fl P
+options override each other and the command's actions are determined
+by the last one specified.
+.Pp
+Files having multiple hard links are counted (and displayed) a single
+time per
+.Nm
+execution.
+.Sh ENVIRONMENT
+.Bl -tag -width BLOCKSIZE
+.It Ev BLOCKSIZE
+If the environment variable
+.Ev BLOCKSIZE
+is set, and the
+.Fl g ,
+.Fl h ,
+.Fl k ,
+and
+.Fl m
+options are not specified, the block counts will be displayed in units of that
+size block.
+.El
+.Sh EXIT STATUS
+.Ex -std
+.Sh SEE ALSO
+.Xr df 1 ,
+.Xr chflags 2 ,
+.Xr fts 3 ,
+.Xr getbsize 3 ,
+.Xr symlink 7 ,
+.Xr quot 8
+.Sh HISTORY
+A
+.Nm
+command appeared in
+.At v6 .
diff --git a/usr.bin/du/du.c b/usr.bin/du/du.c
new file mode 100644
index 0000000..5b13232
--- /dev/null
+++ b/usr.bin/du/du.c
@@ -0,0 +1,362 @@
+/* $NetBSD: du.c,v 1.36 2012/03/11 11:23:20 shattered Exp $ */
+
+/*
+ * Copyright (c) 1989, 1993, 1994
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Chris Newcomb.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+#ifndef lint
+__COPYRIGHT("@(#) Copyright (c) 1989, 1993, 1994\
+ The Regents of the University of California. All rights reserved.");
+#endif /* not lint */
+
+#ifndef lint
+#if 0
+static char sccsid[] = "@(#)du.c 8.5 (Berkeley) 5/4/95";
+#else
+__RCSID("$NetBSD: du.c,v 1.36 2012/03/11 11:23:20 shattered Exp $");
+#endif
+#endif /* not lint */
+
+#include <sys/param.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#include <dirent.h>
+#include <err.h>
+#include <errno.h>
+#include <fts.h>
+#include <inttypes.h>
+#include <util.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <limits.h>
+
+/* Count inodes or file size */
+#define COUNT (iflag ? 1 : p->fts_statp->st_blocks)
+
+static int linkchk(dev_t, ino_t);
+static void prstat(const char *, int64_t);
+__dead static void usage(void);
+
+static int hflag, iflag;
+static long blocksize;
+
+int
+main(int argc, char *argv[])
+{
+ FTS *fts;
+ FTSENT *p;
+ int64_t totalblocks;
+ int ftsoptions, listfiles;
+ int depth;
+ int Hflag, Lflag, aflag, ch, cflag, dflag, gkmflag, nflag, rval, sflag;
+ const char *noargv[2];
+
+ Hflag = Lflag = aflag = cflag = dflag = gkmflag = nflag = sflag = 0;
+ totalblocks = 0;
+ ftsoptions = FTS_PHYSICAL;
+ depth = INT_MAX;
+ while ((ch = getopt(argc, argv, "HLPacd:ghikmnrsx")) != -1)
+ switch (ch) {
+ case 'H':
+ Hflag = 1;
+ Lflag = 0;
+ break;
+ case 'L':
+ Lflag = 1;
+ Hflag = 0;
+ break;
+ case 'P':
+ Hflag = Lflag = 0;
+ break;
+ case 'a':
+ aflag = 1;
+ break;
+ case 'c':
+ cflag = 1;
+ break;
+ case 'd':
+ dflag = 1;
+ depth = atoi(optarg);
+ if (depth < 0 || depth > SHRT_MAX) {
+ warnx("invalid argument to option d: %s",
+ optarg);
+ usage();
+ }
+ break;
+ case 'g':
+ blocksize = 1024 * 1024 * 1024;
+ gkmflag = 1;
+ break;
+ case 'h':
+ hflag = 1;
+ break;
+ case 'i':
+ iflag = 1;
+ break;
+ case 'k':
+ blocksize = 1024;
+ gkmflag = 1;
+ break;
+ case 'm':
+ blocksize = 1024 * 1024;
+ gkmflag = 1;
+ break;
+ case 'n':
+ nflag = 1;
+ break;
+ case 'r':
+ break;
+ case 's':
+ sflag = 1;
+ break;
+ case 'x':
+ ftsoptions |= FTS_XDEV;
+ break;
+ case '?':
+ default:
+ usage();
+ }
+ argc -= optind;
+ argv += optind;
+
+ /*
+ * XXX
+ * Because of the way that fts(3) works, logical walks will not count
+ * the blocks actually used by symbolic links. We rationalize this by
+ * noting that users computing logical sizes are likely to do logical
+ * copies, so not counting the links is correct. The real reason is
+ * that we'd have to re-implement the kernel's symbolic link traversing
+ * algorithm to get this right. If, for example, you have relative
+ * symbolic links referencing other relative symbolic links, it gets
+ * very nasty, very fast. The bottom line is that it's documented in
+ * the man page, so it's a feature.
+ */
+ if (Hflag)
+ ftsoptions |= FTS_COMFOLLOW;
+ if (Lflag) {
+ ftsoptions &= ~FTS_PHYSICAL;
+ ftsoptions |= FTS_LOGICAL;
+ }
+
+ listfiles = 0;
+ if (aflag) {
+ if (sflag || dflag)
+ usage();
+ listfiles = 1;
+ } else if (sflag) {
+ if (dflag)
+ usage();
+ depth = 0;
+ }
+
+ if (!*argv) {
+ noargv[0] = ".";
+ noargv[1] = NULL;
+ argv = __UNCONST(noargv);
+ }
+
+ if (!gkmflag)
+ (void)getbsize(NULL, &blocksize);
+ blocksize /= 512;
+
+ if ((fts = fts_open(argv, ftsoptions, NULL)) == NULL)
+ err(1, "fts_open `%s'", *argv);
+
+ for (rval = 0; (p = fts_read(fts)) != NULL;) {
+ if (nflag) {
+ switch (p->fts_info) {
+ case FTS_NS:
+ case FTS_SLNONE:
+ /* nothing */
+ break;
+ default:
+ if (p->fts_statp->st_flags & UF_NODUMP) {
+ fts_set(fts, p, FTS_SKIP);
+ continue;
+ }
+ }
+ }
+ switch (p->fts_info) {
+ case FTS_D: /* Ignore. */
+ break;
+ case FTS_DP:
+ p->fts_parent->fts_number +=
+ p->fts_number += COUNT;
+ if (cflag)
+ totalblocks += COUNT;
+ /*
+ * If listing each directory, or not listing files
+ * or directories and this is post-order of the
+ * root of a traversal, display the total.
+ */
+ if (p->fts_level <= depth
+ || (!listfiles && !p->fts_level))
+ prstat(p->fts_path, p->fts_number);
+ break;
+ case FTS_DC: /* Ignore. */
+ break;
+ case FTS_DNR: /* Warn, continue. */
+ case FTS_ERR:
+ case FTS_NS:
+ warnx("%s: %s", p->fts_path, strerror(p->fts_errno));
+ rval = 1;
+ break;
+ default:
+ if (p->fts_statp->st_nlink > 1 &&
+ linkchk(p->fts_statp->st_dev, p->fts_statp->st_ino))
+ break;
+ /*
+ * If listing each file, or a non-directory file was
+ * the root of a traversal, display the total.
+ */
+ if (listfiles || !p->fts_level)
+ prstat(p->fts_path, COUNT);
+ p->fts_parent->fts_number += COUNT;
+ if (cflag)
+ totalblocks += COUNT;
+ }
+ }
+ if (errno)
+ err(1, "fts_read");
+ if (cflag)
+ prstat("total", totalblocks);
+ exit(rval);
+}
+
+static void
+prstat(const char *fname, int64_t blocks)
+{
+ if (iflag) {
+ (void)printf("%" PRId64 "\t%s\n", blocks, fname);
+ return;
+ }
+
+ if (hflag) {
+ char buf[5];
+ int64_t sz = blocks * 512;
+
+ humanize_number(buf, sizeof(buf), sz, "", HN_AUTOSCALE,
+ HN_B | HN_NOSPACE | HN_DECIMAL);
+
+ (void)printf("%s\t%s\n", buf, fname);
+ } else
+ (void)printf("%" PRId64 "\t%s\n",
+ howmany(blocks, (int64_t)blocksize),
+ fname);
+}
+
+static int
+linkchk(dev_t dev, ino_t ino)
+{
+ static struct entry {
+ dev_t dev;
+ ino_t ino;
+ } *htable;
+ static int htshift; /* log(allocated size) */
+ static int htmask; /* allocated size - 1 */
+ static int htused; /* 2*number of insertions */
+ static int sawzero; /* Whether zero is in table or not */
+ int h, h2;
+ uint64_t tmp;
+ /* this constant is (1<<64)/((1+sqrt(5))/2)
+ * aka (word size)/(golden ratio)
+ */
+ const uint64_t HTCONST = 11400714819323198485ULL;
+ const int HTBITS = CHAR_BIT * sizeof(tmp);
+
+ /* Never store zero in hashtable */
+ if (dev == 0 && ino == 0) {
+ h = sawzero;
+ sawzero = 1;
+ return h;
+ }
+
+ /* Extend hash table if necessary, keep load under 0.5 */
+ if (htused<<1 >= htmask) {
+ struct entry *ohtable;
+
+ if (!htable)
+ htshift = 10; /* starting hashtable size */
+ else
+ htshift++; /* exponential hashtable growth */
+
+ htmask = (1 << htshift) - 1;
+ htused = 0;
+
+ ohtable = htable;
+ htable = calloc(htmask+1, sizeof(*htable));
+ if (!htable)
+ err(1, "calloc");
+
+ /* populate newly allocated hashtable */
+ if (ohtable) {
+ int i;
+ for (i = 0; i <= htmask>>1; i++)
+ if (ohtable[i].ino || ohtable[i].dev)
+ linkchk(ohtable[i].dev, ohtable[i].ino);
+ free(ohtable);
+ }
+ }
+
+ /* multiplicative hashing */
+ tmp = dev;
+ tmp <<= HTBITS>>1;
+ tmp |= ino;
+ tmp *= HTCONST;
+ h = tmp >> (HTBITS - htshift);
+ h2 = 1 | ( tmp >> (HTBITS - (htshift<<1) - 1)); /* must be odd */
+
+ /* open address hashtable search with double hash probing */
+ while (htable[h].ino || htable[h].dev) {
+ if ((htable[h].ino == ino) && (htable[h].dev == dev))
+ return 1;
+ h = (h + h2) & htmask;
+ }
+
+ /* Insert the current entry into hashtable */
+ htable[h].dev = dev;
+ htable[h].ino = ino;
+ htused++;
+ return 0;
+}
+
+static void
+usage(void)
+{
+
+ (void)fprintf(stderr,
+ "usage: du [-H | -L | -P] [-a | -d depth | -s] [-cghikmnrx] [file ...]\n");
+ exit(1);
+}
diff --git a/usr.bin/env/env.1 b/usr.bin/env/env.1
new file mode 100644
index 0000000..d4597bd
--- /dev/null
+++ b/usr.bin/env/env.1
@@ -0,0 +1,122 @@
+.\" $NetBSD: env.1,v 1.12 2007/06/08 18:20:42 wiz Exp $
+.\"
+.\" Copyright (c) 1980, 1990 The Regents of the University of California.
+.\" All rights reserved.
+.\"
+.\" This code is derived from software contributed to Berkeley by
+.\" the Institute of Electrical and Electronics Engineers, Inc.
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\" notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must reproduce the above copyright
+.\" notice, this list of conditions and the following disclaimer in the
+.\" documentation and/or other materials provided with the distribution.
+.\" 3. Neither the name of the University nor the names of its contributors
+.\" may be used to endorse or promote products derived from this software
+.\" without specific prior written permission.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+.\" ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+.\" SUCH DAMAGE.
+.\"
+.\" from: @(#)printenv.1 6.7 (Berkeley) 7/28/91
+.\" $NetBSD: env.1,v 1.12 2007/06/08 18:20:42 wiz Exp $
+.\"
+.Dd June 8, 2007
+.Dt ENV 1
+.Os
+.Sh NAME
+.Nm env
+.Nd set and print environment
+.Sh SYNOPSIS
+.Nm
+.Op Fl i
+.Op Ar name=value ...
+.Oo
+.Ar utility
+.Op argument ...
+.Oc
+.Sh DESCRIPTION
+.Nm
+executes
+.Ar utility
+after modifying the environment as
+specified on the command line.
+The option
+.Ar name=value
+specifies
+an environmental variable,
+.Ar name ,
+with a value of
+.Ar value .
+The option
+.Sq Fl i
+causes
+.Nm
+to completely ignore the environment
+it inherits.
+.Pp
+If no
+.Ar utility
+is specified,
+.Nm
+prints out the names and values
+of the variables in the environment, with one
+.Ar name=value
+pair per line.
+.Sh EXIT STATUS
+.Nm
+exits with one of the following values:
+.Bl -tag -width Ds
+.It 0
+.Ar utility
+was invoked and completed successfully.
+In this case the exit code is returned by the utility itself, not
+.Nm .
+If no utility was specified, then
+.Nm
+completed successfully and returned the exit code itself.
+.It 1
+An invalid command line option was passed to
+.Nm .
+.It 1\-125
+.Ar utility
+was invoked, but failed in some way;
+see its manual page for more information.
+In this case the exit code is returned by the utility itself, not
+.Nm .
+.It 126
+.Ar utility
+was found, but could not be invoked.
+.It 127
+.Ar utility
+could not be found.
+.El
+.Sh COMPATIBILITY
+The historic
+.Fl
+option has been deprecated but is still supported in this implementation.
+.Sh SEE ALSO
+.Xr execvp 3 ,
+.Xr environ 7
+.Sh STANDARDS
+The
+.Nm
+utility conforms to
+.St -p1003.2-92 .
+.Sh BUGS
+.Nm
+doesn't handle commands with equal
+.Pq Dq =
+signs in their
+names, for obvious reasons.
diff --git a/usr.bin/env/env.c b/usr.bin/env/env.c
new file mode 100644
index 0000000..bf2430d
--- /dev/null
+++ b/usr.bin/env/env.c
@@ -0,0 +1,99 @@
+/*
+ * Copyright (c) 1988, 1993, 1994
+ * The Regents of the University of California. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+#ifndef lint
+__COPYRIGHT("@(#) Copyright (c) 1988, 1993, 1994\
+ The Regents of the University of California. All rights reserved.");
+#endif /* not lint */
+
+#ifndef lint
+/*static char sccsid[] = "@(#)env.c 8.3 (Berkeley) 4/2/94";*/
+__RCSID("$NetBSD: env.c,v 1.20 2010/11/16 02:53:49 christos Exp $");
+#endif /* not lint */
+
+#include <err.h>
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <locale.h>
+#include <errno.h>
+
+static void usage(void) __attribute__((__noreturn__));
+
+extern char **environ;
+
+int
+main(int argc, char **argv)
+{
+ char **ep;
+ char *cleanenv[1];
+ int ch;
+
+ setprogname(*argv);
+ (void)setlocale(LC_ALL, "");
+
+ while ((ch = getopt(argc, argv, "-i")) != -1)
+ switch((char)ch) {
+ case '-': /* obsolete */
+ case 'i':
+ environ = cleanenv;
+ cleanenv[0] = NULL;
+ break;
+ case '?':
+ default:
+ usage();
+ }
+
+ for (argv += optind; *argv && strchr(*argv, '=') != NULL; ++argv)
+ (void)putenv(*argv);
+
+ if (*argv) {
+ /* return 127 if the command to be run could not be found; 126
+ if the command was found but could not be invoked */
+
+ (void)execvp(*argv, argv);
+ err((errno == ENOENT) ? 127 : 126, "%s", *argv);
+ /* NOTREACHED */
+ }
+
+ for (ep = environ; *ep; ep++)
+ (void)printf("%s\n", *ep);
+
+ exit(0);
+}
+
+static void
+usage(void)
+{
+ (void)fprintf(stderr, "Usage: %s [-i] [name=value ...] [command]\n",
+ getprogname());
+ exit(1);
+}
diff --git a/usr.bin/expand/expand.1 b/usr.bin/expand/expand.1
new file mode 100644
index 0000000..5f36025
--- /dev/null
+++ b/usr.bin/expand/expand.1
@@ -0,0 +1,87 @@
+.\" $NetBSD: expand.1,v 1.12 2012/05/12 14:52:57 reed Exp $
+.\"
+.\" Copyright (c) 1980, 1990, 1993
+.\" The Regents of the University of California. All rights reserved.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\" notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must reproduce the above copyright
+.\" notice, this list of conditions and the following disclaimer in the
+.\" documentation and/or other materials provided with the distribution.
+.\" 3. Neither the name of the University nor the names of its contributors
+.\" may be used to endorse or promote products derived from this software
+.\" without specific prior written permission.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+.\" ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+.\" SUCH DAMAGE.
+.\"
+.\" @(#)expand.1 8.1 (Berkeley) 6/9/93
+.\"
+.Dd December 20, 2008
+.Dt EXPAND 1
+.Os
+.Sh NAME
+.Nm expand ,
+.Nm unexpand
+.Nd expand tabs to spaces, and vice versa
+.Sh SYNOPSIS
+.Nm
+.Op Fl t Ar tabstop
+.Op Fl t Ar tab1,tab2,...,tabn
+.Op Ar
+.Nm unexpand
+.Op Fl a
+.Op Fl t Ar tab1,tab2,...,tabn
+.Op Ar
+.Sh DESCRIPTION
+.Nm
+processes the named files or the standard input writing
+the standard output with tabs changed into blanks.
+Backspace characters are preserved into the output and decrement
+the column count for tab calculations.
+.Nm
+is useful for pre-processing character files
+(before sorting, looking at specific columns, etc.) that
+contain tabs.
+.Pp
+If a single
+.Ar tabstop
+argument is given, then tabs are set
+.Ar tabstop
+spaces apart instead of the default 8.
+If multiple tabstops are given then the tabs are set at those
+specific columns.
+.Pp
+.Nm unexpand
+puts tabs back into the data from the standard input or the named
+files and writes the result on the standard output.
+.Pp
+Option (with
+.Nm unexpand
+only):
+.Bl -tag -width flag
+.It Fl a
+By default, only leading blanks and tabs
+are reconverted to maximal strings of tabs.
+If the
+.Fl a
+option is given, then tabs are inserted whenever they would compress the
+resultant file by replacing two or more characters.
+.El
+.Sh HISTORY
+The
+.Nm
+command appeared in
+.Bx 1 .
diff --git a/usr.bin/expand/expand.c b/usr.bin/expand/expand.c
new file mode 100644
index 0000000..c69fda1
--- /dev/null
+++ b/usr.bin/expand/expand.c
@@ -0,0 +1,182 @@
+/* $NetBSD: expand.c,v 1.14 2016/09/05 00:40:28 sevan Exp $ */
+
+/*
+ * Copyright (c) 1980, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+#ifndef lint
+__COPYRIGHT("@(#) Copyright (c) 1980, 1993\
+ The Regents of the University of California. All rights reserved.");
+#endif /* not lint */
+
+#ifndef lint
+#if 0
+static char sccsid[] = "@(#)expand.c 8.1 (Berkeley) 6/9/93";
+#endif
+__RCSID("$NetBSD: expand.c,v 1.14 2016/09/05 00:40:28 sevan Exp $");
+#endif /* not lint */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <ctype.h>
+#include <unistd.h>
+#include <err.h>
+
+/*
+ * expand - expand tabs to equivalent spaces
+ */
+size_t nstops;
+size_t tabstops[100];
+
+static void getstops(const char *);
+static void usage(void) __dead;
+
+int
+main(int argc, char *argv[])
+{
+ int c;
+ size_t n, column;
+
+ setprogname(argv[0]);
+
+ /* handle obsolete syntax */
+ while (argc > 1 &&
+ argv[1][0] == '-' && isdigit((unsigned char)argv[1][1])) {
+ getstops(&argv[1][1]);
+ argc--; argv++;
+ }
+
+ while ((c = getopt (argc, argv, "t:")) != -1) {
+ switch (c) {
+ case 't':
+ getstops(optarg);
+ break;
+ case '?':
+ default:
+ usage();
+ /* NOTREACHED */
+ }
+ }
+ argc -= optind;
+ argv += optind;
+
+ do {
+ if (argc > 0) {
+ if (freopen(argv[0], "r", stdin) == NULL)
+ err(EXIT_FAILURE, "Cannot open `%s'", argv[0]);
+ argc--, argv++;
+ }
+ column = 0;
+ while ((c = getchar()) != EOF) {
+ switch (c) {
+ case '\t':
+ if (nstops == 0) {
+ do {
+ putchar(' ');
+ column++;
+ } while (column & 07);
+ continue;
+ }
+ if (nstops == 1) {
+ do {
+ putchar(' ');
+ column++;
+ } while (((column - 1) % tabstops[0])
+ != (tabstops[0] - 1));
+ continue;
+ }
+ for (n = 0; n < nstops; n++)
+ if (tabstops[n] > column)
+ break;
+ if (n == nstops) {
+ putchar(' ');
+ column++;
+ continue;
+ }
+ while (column < tabstops[n]) {
+ putchar(' ');
+ column++;
+ }
+ continue;
+
+ case '\b':
+ if (column)
+ column--;
+ putchar('\b');
+ continue;
+
+ default:
+ putchar(c);
+ column++;
+ continue;
+
+ case '\n':
+ putchar(c);
+ column = 0;
+ continue;
+ }
+ }
+ } while (argc > 0);
+ return EXIT_SUCCESS;
+}
+
+static void
+getstops(const char *spec)
+{
+ int i;
+ const char *cp = spec;
+
+ nstops = 0;
+ for (;;) {
+ i = 0;
+ while (*cp >= '0' && *cp <= '9')
+ i = i * 10 + *cp++ - '0';
+ if (i <= 0 || i > 256)
+ errx(EXIT_FAILURE, "Too large tab stop spec `%d'", i);
+ if (nstops > 0 && (size_t)i <= tabstops[nstops-1])
+ errx(EXIT_FAILURE, "Out of order tabstop spec `%d'", i);
+ if (nstops == sizeof(tabstops) / sizeof(tabstops[0]) - 1)
+ errx(EXIT_FAILURE, "Too many tabstops");
+ tabstops[nstops++] = i;
+ if (*cp == '\0')
+ break;
+ if (*cp != ',' && *cp != ' ')
+ errx(EXIT_FAILURE, "Illegal tab stop spec `%s'", spec);
+ cp++;
+ }
+}
+
+static void
+usage(void)
+{
+
+ (void)fprintf(stderr, "Usage: %s [-t tablist] [file ...]\n",
+ getprogname());
+ exit(EXIT_FAILURE);
+}
diff --git a/usr.bin/false/false.1 b/usr.bin/false/false.1
new file mode 100644
index 0000000..175b67a
--- /dev/null
+++ b/usr.bin/false/false.1
@@ -0,0 +1,56 @@
+.\" $NetBSD: false.1,v 1.8 2003/08/07 11:13:40 agc Exp $
+.\"
+.\" Copyright (c) 1983, 1990 The Regents of the University of California.
+.\" All rights reserved.
+.\"
+.\" This code is derived from software contributed to Berkeley by
+.\" the Institute of Electrical and Electronics Engineers, Inc.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\" notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must reproduce the above copyright
+.\" notice, this list of conditions and the following disclaimer in the
+.\" documentation and/or other materials provided with the distribution.
+.\" 3. Neither the name of the University nor the names of its contributors
+.\" may be used to endorse or promote products derived from this software
+.\" without specific prior written permission.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+.\" ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+.\" SUCH DAMAGE.
+.\"
+.\" from: @(#)false.1 6.6 (Berkeley) 7/24/91
+.\" $NetBSD: false.1,v 1.8 2003/08/07 11:13:40 agc Exp $
+.\"
+.Dd July 24, 1991
+.Dt FALSE 1
+.Os
+.Sh NAME
+.Nm false
+.Nd return false value
+.Sh SYNOPSIS
+.Nm
+.Sh DESCRIPTION
+The
+.Nm
+utility always exits with a nonzero exit code.
+.Sh SEE ALSO
+.Xr csh 1 ,
+.Xr sh 1 ,
+.Xr true 1
+.Sh STANDARDS
+The
+.Nm
+utility conforms to
+.St -p1003.2-92 .
diff --git a/usr.bin/false/false.sh b/usr.bin/false/false.sh
new file mode 100644
index 0000000..2526efb
--- /dev/null
+++ b/usr.bin/false/false.sh
@@ -0,0 +1,2 @@
+#! /bin/sh
+exit 1
diff --git a/usr.bin/find/extern.h b/usr.bin/find/extern.h
new file mode 100644
index 0000000..5c94035
--- /dev/null
+++ b/usr.bin/find/extern.h
@@ -0,0 +1,102 @@
+/* $NetBSD: extern.h,v 1.29 2016/06/13 00:04:40 pgoyette Exp $ */
+
+/*-
+ * Copyright (c) 1991, 1993, 1994
+ * The Regents of the University of California. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * from: @(#)extern.h 8.3 (Berkeley) 4/16/94
+ */
+
+#include <sys/cdefs.h>
+
+void brace_subst(char *, char **, char *, int *);
+PLAN *find_create(char ***);
+int find_execute(PLAN *, char **);
+PLAN *find_formplan(char **);
+int find_traverse(PLAN *, int (*)(PLAN *, void *), void *);
+int f_expr(PLAN *, FTSENT *);
+PLAN *not_squish(PLAN *);
+PLAN *or_squish(PLAN *);
+PLAN *paren_squish(PLAN *);
+int plan_cleanup(PLAN *, void *);
+void printlong(char *, char *, struct stat *);
+int queryuser(char **);
+void show_path(int);
+
+PLAN *c_amin(char ***, int, char *);
+PLAN *c_anewer(char ***, int, char *);
+PLAN *c_asince(char ***, int, char *);
+PLAN *c_atime(char ***, int, char *);
+PLAN *c_cmin(char ***, int, char *);
+PLAN *c_cnewer(char ***, int, char *);
+PLAN *c_csince(char ***, int, char *);
+PLAN *c_ctime(char ***, int, char *);
+PLAN *c_delete(char ***, int, char *);
+PLAN *c_depth(char ***, int, char *);
+PLAN *c_empty(char ***, int, char *);
+PLAN *c_exec(char ***, int, char *);
+PLAN *c_execdir(char ***, int, char *);
+PLAN *c_exit(char ***, int, char *);
+PLAN *c_false(char ***, int, char *);
+PLAN *c_flags(char ***, int, char *);
+PLAN *c_follow(char ***, int, char *);
+PLAN *c_fprint(char ***, int, char *);
+PLAN *c_fstype(char ***, int, char *);
+PLAN *c_group(char ***, int, char *);
+PLAN *c_iname(char ***, int, char *);
+PLAN *c_inum(char ***, int, char *);
+PLAN *c_iregex(char ***, int, char *);
+PLAN *c_links(char ***, int, char *);
+PLAN *c_ls(char ***, int, char *);
+PLAN *c_maxdepth(char ***, int, char *);
+PLAN *c_mindepth(char ***, int, char *);
+PLAN *c_mmin(char ***, int, char *);
+PLAN *c_mtime(char ***, int, char *);
+PLAN *c_name(char ***, int, char *);
+PLAN *c_newer(char ***, int, char *);
+PLAN *c_nogroup(char ***, int, char *);
+PLAN *c_nouser(char ***, int, char *);
+PLAN *c_path(char ***, int, char *);
+PLAN *c_perm(char ***, int, char *);
+PLAN *c_print(char ***, int, char *);
+PLAN *c_print0(char ***, int, char *);
+PLAN *c_printx(char ***, int, char *);
+PLAN *c_prune(char ***, int, char *);
+PLAN *c_regex(char ***, int, char *);
+PLAN *c_since(char ***, int, char *);
+PLAN *c_size(char ***, int, char *);
+PLAN *c_type(char ***, int, char *);
+PLAN *c_user(char ***, int, char *);
+PLAN *c_xdev(char ***, int, char *);
+PLAN *c_openparen(char ***, int, char *);
+PLAN *c_closeparen(char ***, int, char *);
+PLAN *c_not(char ***, int, char *);
+PLAN *c_or(char ***, int, char *);
+PLAN *c_null(char ***, int, char *);
+
+extern int ftsoptions, isdeprecated, isdepth, isoutput, issort, isxargs,
+ regcomp_flags;
diff --git a/usr.bin/find/find.1 b/usr.bin/find/find.1
new file mode 100644
index 0000000..60ce94a
--- /dev/null
+++ b/usr.bin/find/find.1
@@ -0,0 +1,974 @@
+.\" $NetBSD: find.1,v 1.89 2017/07/03 21:34:57 wiz Exp $
+.\"
+.\" Copyright (c) 1990, 1993
+.\" The Regents of the University of California. All rights reserved.
+.\"
+.\" This code is derived from software contributed to Berkeley by
+.\" the Institute of Electrical and Electronics Engineers, Inc.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\" notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must reproduce the above copyright
+.\" notice, this list of conditions and the following disclaimer in the
+.\" documentation and/or other materials provided with the distribution.
+.\" 3. Neither the name of the University nor the names of its contributors
+.\" may be used to endorse or promote products derived from this software
+.\" without specific prior written permission.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+.\" ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+.\" SUCH DAMAGE.
+.\"
+.\" from: @(#)find.1 8.7 (Berkeley) 5/9/95
+.\"
+.Dd June 13, 2016
+.Dt FIND 1
+.Os
+.Sh NAME
+.Nm find
+.Nd walk a file hierarchy
+.Sh SYNOPSIS
+.Nm
+.Op Fl H | Fl L | Fl P
+.Op Fl dEhsXx
+.Ar file
+.Op Ar file ...
+.Op Ar expression
+.Nm
+.Op Fl H | Fl L | Fl P
+.Op Fl dEhsXx
+.Fl f Ar file
+.Op Ar file ...
+.Op Ar expression
+.Sh DESCRIPTION
+.Nm
+recursively descends the directory tree for each
+.Ar file
+listed, evaluating an
+.Ar expression
+(composed of the
+.Dq primaries
+and
+.Dq operands
+listed below) in terms
+of each file in the tree.
+.Pp
+The options are as follows:
+.Bl -tag -width Ds
+.It Fl H
+Causes the file information and file type (see
+.Xr stat 2 )
+returned for each symbolic link encountered on the command line to be
+those of the file referenced by the link, not the link itself.
+If the referenced file does not exist, the file information and type will
+be for the link itself.
+File information of all symbolic links not on the command line is that
+of the link itself.
+.It Fl L
+Causes the file information and file type (see
+.Xr stat 2 )
+returned for each symbolic link to be those of the file referenced by the
+link, not the link itself.
+If the referenced file does not exist, the file information and type will
+be for the link itself.
+.It Fl P
+Causes the file information and file type (see
+.Xr stat 2 )
+returned for each symbolic link to be those of the link itself.
+.It Fl d
+Causes
+.Nm
+to perform a depth-first traversal, i.e., directories
+are visited in post-order, and all entries in a directory will be acted
+on before the directory itself.
+By default,
+.Nm
+visits directories in pre-order, i.e., before their contents.
+Note, the default is
+.Em not
+a breadth-first traversal.
+.It Fl E
+Causes
+.Ar regexp
+arguments to primaries to be interpreted as extended regular
+expressions (see
+.Xr re_format 7 ) .
+.It Fl f
+Specifies a file hierarchy for
+.Nm
+to traverse.
+File hierarchies may also be specified as the operands immediately
+following the options.
+.It Fl h
+Causes the file information and file type (see
+.Xr stat 2 )
+returned for each symbolic link to be those of the file referenced by the
+link, not the link itself.
+If the referenced file does not exist, the file information and type will
+be for the link itself.
+.It Fl s
+Causes the entries of each directory to be sorted in
+lexicographical order.
+Note that the sorting is done only inside of each directory;
+files in different directories are not sorted.
+Therefore,
+.Sq Li a/b
+appears before
+.Sq Li a.b ,
+which is different from
+.Dq Li "find ... \&| sort"
+order.
+.It Fl X
+Modifies the output to permit
+.Nm
+to be safely used in conjunction with
+.Xr xargs 1 .
+If a file name contains any of the delimiting characters used by
+.Xr xargs 1 ,
+a diagnostic message is displayed on standard error, and the file
+is skipped.
+The delimiting characters include single
+.Pq Dq \&'
+and double
+.Pq Dq \&"
+quotes, backslash
+.Pq Dq \e ,
+space, tab, and newline characters.
+Alternatively, the
+.Ic -print0
+or
+.Ic -printx
+primaries can be used to format the output in a way that
+.Xr xargs 1
+can accept.
+.It Fl x
+Restricts the search to the file system containing the
+directory specified.
+Does not list mount points to other file systems.
+.El
+.Sh PRIMARIES
+All primaries which take a numeric argument of
+.Ar n
+allow the number to be preceded by a plus sign
+.Pq Dq \&+
+or a minus sign
+.Pq Dq \- .
+A preceding plus sign means
+.Dq more than Ar n ,
+a preceding minus sign means
+.Dq less than Ar n ,
+and neither means
+.Dq exactly Ar n .
+(The argument specified for the
+.Ic -user
+and
+.Ic -group
+primaries
+are similarly treated if the value is numeric and does not correspond to a
+valid user or group name.)
+.Pp
+For primaries which take a
+.Ar timestamp
+argument, the argument must be valid input to
+.Xr parsedate 3 .
+If the argument contains multiple words, enclose the argument in quotes.
+.Pp
+.Bl -tag -width Ds -compact
+.It Ic -amin Ar n
+True if the difference between the file last access time and the time
+.Nm
+was started, rounded up to the next full minute, is
+.Ar n
+minutes.
+.Pp
+.It Ic -anewer Ar file
+True if the current file has a more recent last access time than
+.Ar file .
+.Pp
+.It Ic -asince Ar timestamp
+True if the file last access time is greater than the specified
+.Ar timestamp .
+.Pp
+.It Ic -atime Ar n
+True if the difference between the file last access time and the time
+.Nm
+was started, rounded up to the next full 24-hour period, is
+.Ar n
+24-hour periods.
+.Pp
+.It Ic -cmin Ar n
+True if the difference between the time of last change of file status
+information and the time
+.Nm
+was started, rounded up to the next full minute, is
+.Ar n
+minutes.
+.Pp
+.It Ic -cnewer Ar file
+True if the current file has a more recent last change time than
+.Ar file .
+.Pp
+.It Ic -csince Ar timestamp
+True if the file last status change time is greater than the specified
+.Ar timestamp .
+.Pp
+.It Ic -ctime Ar n
+True if the difference between the time of last change of file status
+information and the time
+.Nm
+was started, rounded up to the next full 24-hour period, is
+.Ar n
+24-hour periods.
+.Pp
+.It Ic -delete
+Delete found files, symbolic links, and directories.
+Always returns true.
+This executes from the current working directory as
+.Nm
+recurses down the tree.
+To avoid deleting unexpected files, it will ignore any filenames that
+.Xr fts 3
+returns that contain a
+.Dq /
+.Xr ( fts 3
+should not return such pathnames).
+Depth-first traversal processing is implied by this option.
+This primary can also be invoked as
+.Ic -rm .
+.Pp
+.It Ic -empty
+True if the current file or directory is empty.
+.Pp
+.It Ic -exec Ar utility Oo argument ... Oc Ic \&;
+.It Ic -exec Ar utility Oo argument ... Oc Ic {} Ic \&+
+Execute the specified
+.Ar utility
+with the specified arguments.
+.Pp
+The list of arguments for
+.Ar utility
+is terminated by a lone semicolon
+.Dq Ic \&;
+or plus
+.Dq Ic \&+
+character as a separate parameter.
+The command specified by
+.Ar utility
+will be executed with its current working directory being the directory
+from which
+.Nm
+was executed.
+.Pp
+If the list of arguments is terminated by a semicolon
+.Pq Dq Ic \&; ,
+then
+.Ar utility
+is invoked once per pathname.
+If
+the string
+.Dq Ic {}
+appears one or more times in the utility name or arguments,
+then it is replaced by the pathname of the current file
+(but it need not appear, in which case the pathname
+will not be passed to
+.Ar utility ) .
+The semicolon-terminated form of the
+.Ic -exec
+primary returns true if and only if
+.Ar utility
+exits with a zero exit status.
+Note that the semicolon will have to be escaped on the shell command line
+in order to be passed as a parameter.
+.Pp
+If the list of arguments is terminated by a plus sign
+.Pq Dq Ic \&+ ,
+then the pathnames for which the primary is evaluated are aggregated
+into sets, and
+.Ar utility
+will be invoked once per set, similar to
+.Xr xargs 1 .
+In this case the string
+.Dq Ic {}
+must appear, and must appear as the last item in the argument list,
+just before the
+.Dq Ic \&+
+parameter, and is replaced by the pathnames of the current set of files.
+Each set is limited to no more than 5,000 pathnames,
+and is also limited such that the total number of bytes in the argument
+list does not exceed
+.Dv ARG_MAX .
+The plus-terminated form of the
+.Ic -exec
+primary always returns true.
+If the plus-terminated form of the
+.Ic -exec
+primary results in any invocation of
+.Ar utility
+exiting with non-zero exit status, then
+.Nm
+will eventually exit with non-zero status as well,
+but this does not cause
+.Nm
+to exit early.
+.Pp
+.It Ic -execdir Ar utility Oo argument ... Oc Ic \&;
+The
+.Ic -execdir
+primary is similar to the semicolon-terminated
+.Pq Dq Ic \&;
+variant of the
+.Ic -exec
+primary, with the exception that
+.Ar utility
+will be executed from the directory that holds
+the current file.
+Only the base filename is substituted for the string
+.Dq Ic {} .
+Set aggregation
+.Pq Do Ic \&+ Dc termination
+is not supported.
+.Pp
+.It Ic -exit Op Ar status
+This primary causes
+.Nm
+to stop traversing the file system and exit immediately,
+with the specified numeric exit status.
+If the
+.Ar status
+value is not specified, then
+.Nm
+will exit with status zero.
+Note that any preceding primaries will be evaluated and acted upon
+before exiting.
+.Pp
+.It Ic -false
+This primary always evaluates to false.
+This can be used following a primary that caused the
+expression to be true to make the expression to be false.
+This can be useful after using a
+.Ic -fprint
+primary so it can continue to the next expression (using an
+.Cm -or
+operator, for example).
+.Pp
+.It Ic -flags Oo Fl Oc Ns Ar flags
+If
+.Ar flags
+are preceded by a dash
+.Pq Dq Ic \- ,
+this primary evaluates to true
+if at least all of the bits in
+.Ar flags
+are set in the file's flags bits.
+If
+.Ar flags
+are not preceded by a dash, this primary evaluates to true if
+the bits in
+.Ar flags
+exactly match the file's flags bits.
+If
+.Ar flags
+is
+.Dq none ,
+files with no flags bits set are matched.
+(See
+.Xr chflags 1
+for more information about file flags.)
+.Pp
+.It Ic -follow
+Follow symbolic links.
+.Pp
+.It Ic -fprint Ar filename
+This primary always evaluates to true.
+This creates
+.Ar filename
+or overwrites the file if it already exists.
+The file is created at startup.
+It writes the pathname of the current file to this file, followed
+by a newline character.
+The file will be empty if no files are matched.
+.Pp
+.It Ic -fstype Ar type
+True if the file is contained in a file system of type
+.Ar type .
+The
+.Xr sysctl 8
+command can be used to find out the types of file systems
+that are available on the system:
+.Bd -literal -offset indent
+sysctl vfs.generic.fstypes
+.Ed
+.Pp
+In addition, there are two pseudo-types,
+.Dq local
+and
+.Dq rdonly .
+The former matches any file system physically mounted on the system where
+the
+.Nm
+is being executed, and the latter matches any file system which is
+mounted read-only.
+.Pp
+.It Ic -group Ar gname
+True if the file belongs to the group
+.Ar gname .
+If
+.Ar gname
+is numeric and there is no such group name, then
+.Ar gname
+is treated as a group id (and considered a numeric argument).
+.Pp
+.It Ic -iname Ar pattern
+True if the last component of the pathname being examined matches
+.Ar pattern
+in a case-insensitive manner.
+Special shell pattern matching characters
+.Po
+.Dq \&[ ,
+.Dq \&] ,
+.Dq \&* ,
+and
+.Dq \&?
+.Pc
+may be used as part of
+.Ar pattern .
+These characters may be matched explicitly by escaping them with a
+backslash
+.Pq Dq \e .
+.Pp
+.It Ic -inum Ar n
+True if the file has inode number
+.Ar n .
+.Pp
+.It Ic -iregex Ar regexp
+True if the path name of the current file matches the case-insensitive
+basic regular expression
+.Pq see Xr re_format 7
+.Ar regexp .
+This is a match on the whole path, not a search for the regular expression
+within the path.
+.Pp
+.It Ic -links Ar n
+True if the file has
+.Ar n
+links.
+.Pp
+.It Ic -rm
+This primary is an alias for
+.Ic -delete .
+.Pp
+.It Ic -ls
+This primary always evaluates to true.
+The following information for the current file is written to standard output:
+its inode number, size in 512-byte blocks, file permissions, number of hard
+links, owner, group, size in bytes, last modification time, and pathname.
+If the file is a block or character special file, the major and minor numbers
+will be displayed instead of the size in bytes.
+If the file is a symbolic link, the pathname of the linked-to file will be
+displayed preceded by
+.Dq -> .
+The format is identical to that produced by
+.Dq ls -dgils .
+.Pp
+.It Ic -maxdepth Ar depth
+True if the current search depth is less than or equal to what is specified in
+.Ar depth .
+.Pp
+.It Ic -mindepth Ar depth
+True if the current search depth is at least what is specified in
+.Ar depth .
+.Pp
+.It Ic -mmin Ar n
+True if the difference between the file last modification time and the time
+.Nm
+was started, rounded up to the next full minute, is
+.Ar n
+minutes.
+.Pp
+.It Ic -mtime Ar n
+True if the difference between the file last modification time and the time
+.Nm
+was started, rounded up to the next full 24-hour period, is
+.Ar n
+24-hour periods.
+.Pp
+.It Ic -ok Ar utility Oo argument ... Oc Ic \&;
+The
+.Ic -ok
+primary is similar to the semicolon-terminated
+.Pq Dq \&;
+variant of the
+.Ic -exec
+primary, with the exception that
+.Nm
+requests user affirmation for the execution of
+.Ar utility
+by printing
+a message to the terminal and reading a response.
+If the response is other than
+.Dq y ,
+the command is not executed and the
+.Ic -ok
+primary evaluates to false.
+Set aggregation
+.Pq Do \&+ Dc termination
+is not supported.
+.Pp
+.It Ic -name Ar pattern
+True if the last component of the pathname being examined matches
+.Ar pattern .
+Special shell pattern matching characters
+.Po
+.Dq \&[ ,
+.Dq \&] ,
+.Dq \&* ,
+and
+.Dq \&?
+.Pc
+may be used as part of
+.Ar pattern .
+These characters may be matched explicitly by escaping them with a
+backslash
+.Pq Dq \e .
+.Pp
+.It Ic -newer Ar file
+True if the current file has a more recent last modification time than
+.Ar file .
+.Pp
+.It Ic -newerXY Ar reference
+For compatibility with Gnu findutils.
+.Bl -column -offset indent ".Sy findutils" ".Sy equivalent"
+.It Sy findutils Ta Sy find
+.It Sy option Ta Sy equivalent
+.It -neweraa Ta -anewer
+.It -newerat Ta -asince
+.It -newercc Ta -cnewer
+.It -newerct Ta -csince
+.It -newermm Ta -newer
+.It -newermt Ta -since
+.El
+.Pp
+Other option variants from findutils are not implemented.
+.Pp
+.It Ic -nouser
+True if the file belongs to an unknown user.
+.Pp
+.It Ic -nogroup
+True if the file belongs to an unknown group.
+.Pp
+.It Ic -path Ar pattern
+True if the pathname being examined matches
+.Ar pattern .
+Special shell pattern matching characters
+.Po
+.Dq \&[ ,
+.Dq \&] ,
+.Dq \&* ,
+and
+.Dq \&?
+.Pc
+may be used as part of
+.Ar pattern .
+These characters may be matched explicitly by escaping them with a
+backslash
+.Pq Dq \e .
+Slashes
+.Pq Dq /
+are treated as normal characters and do not have to be
+matched explicitly.
+.Pp
+.It Ic -perm Oo Fl Oc Ns Ar mode
+The
+.Ar mode
+may be either symbolic (see
+.Xr chmod 1 )
+or an octal number.
+If the mode is symbolic, a starting value of zero is assumed and the
+mode sets or clears permissions without regard to the process' file mode
+creation mask.
+If the mode is octal, only bits 07777
+.Pf ( Dv S_ISUID
+|
+.Dv S_ISGID
+|
+.Dv S_ISTXT
+|
+.Dv S_IRWXU
+|
+.Dv S_IRWXG
+|
+.Dv S_IRWXO )
+of the file's mode bits participate
+in the comparison.
+If the mode is preceded by a dash
+.Pq Dq Ic \- ,
+this primary evaluates to true
+if at least all of the bits in the mode are set in the file's mode bits.
+If the mode is not preceded by a dash, this primary evaluates to true if
+the bits in the mode exactly match the file's mode bits.
+Note, the first character of a symbolic mode may not be a dash
+.Pq Dq Ic \- .
+.Pp
+.It Ic -print
+This primary always evaluates to true.
+It prints the pathname of the current file to standard output, followed
+by a newline character.
+If none of
+.Ic -delete ,
+.Ic -exec ,
+.Ic -execdir ,
+.Ic -exit ,
+.Ic -fprint ,
+.Ic -ls ,
+.Ic -ok ,
+.Ic -print0 ,
+.Ic -printx ,
+nor
+.Ic -rm
+is specified, the given expression shall be effectively replaced by
+.Cm \&( Ns Ar given\& expression Ns Cm \&)
+.Ic -print .
+.Pp
+.It Ic -print0
+This primary always evaluates to true.
+It prints the pathname of the current file to standard output, followed
+by a NUL character.
+.Pp
+.It Ic -printx
+This primary always evaluates to true.
+It prints the pathname of the current file to standard output,
+with each space, tab, newline, backslash, dollar sign, and single,
+double, or back quotation mark prefixed by a backslash, so the output of
+.Nm
+can safely be used as input to
+.Xr xargs 1 .
+.Pp
+.It Ic -prune
+This primary always evaluates to true.
+It causes
+.Nm
+to not descend into the current file.
+Note, the
+.Ic -prune
+primary has no effect if the
+.Fl d
+option was specified.
+.Pp
+.It Ic -regex Ar regexp
+True if the path name of the current file matches the case-sensitive
+basic regular expression
+.Pq see Xr re_format 7
+.Ar regexp .
+This is a match on the whole path, not a search for the regular expression
+within the path.
+.Pp
+.It Ic -since Ar timestamp
+True if the file last modification time is more recent than
+.Ar timestamp .
+.Pp
+.It Ic -size Ar n Ns Op Cm c
+True if the file's size, rounded up, in 512-byte blocks is
+.Ar n .
+If
+.Ar n
+is followed by a
+.Dq Ic c ,
+then the primary is true if the file's size is
+.Ar n
+bytes.
+.Pp
+.It Ic -type Ar t
+True if the file is of the specified type.
+Possible file types are as follows:
+.Pp
+.Bl -tag -width flag -offset indent -compact
+.It Cm b
+block special
+.It Cm c
+character special
+.It Cm d
+directory
+.It Cm f
+regular file
+.It Cm l
+symbolic link
+.It Cm p
+FIFO
+.It Cm s
+socket
+.It Cm W
+whiteout
+.It Cm w
+whiteout
+.El
+.Pp
+.It Ic -user Ar username
+True if the file belongs to the user
+.Ar username .
+If
+.Ar username
+is numeric and there is no such user on the system, then
+.Ar username
+is treated as a user id (and considered a numeric argument).
+.Pp
+.It Ic -xdev
+This primary always evaluates to true.
+It causes find not to descend past directories that have a different
+device ID
+.Va ( st_dev ,
+see
+.Xr stat 2
+S5.6.2 [POSIX.1]).
+.El
+.Sh OPERATORS
+The primaries may be combined using the following operators.
+The operators are listed in order of decreasing precedence.
+.Bl -tag -width (expression)
+.It Cm \&( Ar expression Cm \&)
+This evaluates to true if the parenthesized expression evaluates to
+true.
+.It Cm \&! Ar expression
+This is the unary
+.Tn NOT
+operator.
+It evaluates to true if the expression is false.
+.It Ar expression Cm -and Ar expression
+.It Ar expression expression
+The
+.Cm -and
+operator is the logical
+.Tn AND
+operator.
+As it is implied by the juxtaposition of two expressions it does not
+have to be specified.
+The expression evaluates to true if both expressions are true.
+The second expression is not evaluated if the first expression is false.
+.It Ar expression Cm -or Ar expression
+The
+.Cm -or
+operator is the logical
+.Tn OR
+operator.
+The expression evaluates to true if either the first or the second expression
+is true.
+The second expression is not evaluated if the first expression is true.
+.El
+.Pp
+All operands and primaries must be separate arguments to
+.Nm .
+Primaries which themselves take arguments expect each argument
+to be a separate argument to
+.Nm .
+.Sh EXIT STATUS
+The
+.Nm
+utility normally exits 0 on success, and exits with 1 under certain
+internal error conditions.
+If any invocations of
+.Dq Ic -exec Ar ... Ic \&+
+primaries return non-zero exit-status, then
+.Nm
+will do so as well.
+.Sh EXAMPLES
+The following examples are shown as given to the shell:
+.Bl -tag -width findx
+.It Li "find / \e! -name \*q*.c\*q \-print"
+Print out a list of all the files whose names do not end in
+.Dq \&.c .
+.It Li "find / \-newer ttt \-user wnj \-print"
+Print out a list of all the files owned by user
+.Dq wnj
+that are newer than the file
+.Dq ttt .
+.It Li "find . \-type f \-mmin \-30 \-print \-or \-mindepth 1 \-prune"
+Print out a list of all the files in the current directory that are
+newer than 30 minutes.
+.It Li "find . \-type f \-atime +10 \-mindepth 2 \-print"
+Print out a list of all the files in any sub-directories that have not
+been accessed in the past ten days.
+.It Li "find . \-mtime +90 \-exec rm \-i {} + \-or \-mindepth 1 \-prune"
+Interactively remove all of the files in the current directory that have
+not been modified in 90 days.
+.It Li "find . \-type f \-mtime +90 \-ok mv {} {}.old \e;"
+Interactively rename all of the files in the current directory and all
+sub-directories that have not been modified in 90 days.
+.It Li "find / \e! \e( \-newer ttt \-user wnj \e) \-print"
+Print out a list of all the files which are not both newer than
+.Dq ttt
+and owned by
+.Dq wnj .
+.It Li "find / \e( \-newer ttt \-or \-user wnj \e) \-print"
+Print out a list of all the files that are either owned by
+.Dq wnj
+or that are newer than
+.Dq ttt .
+.It Li "find / \e( \-newer ttt \-or \-user wnj \e) \-exit 1"
+Return immediately with a value of 1 if any files are found that are either
+owned by
+.Dq wnj
+or that are newer than
+.Dq ttt ,
+but do not print them.
+.It Li "find / \e( \-newer ttt \-or \-user wnj \e) \-ls \-exit 1"
+Same as above, but list the first file matching the criteria before exiting
+with a value of 1.
+.It Li "find . \-type f \-exec sh \-c 'file=\*[q]$1\*[q]; ...;' - {} \;"
+Perform an arbitrarily complex shell command for every file.
+.El
+.Sh SEE ALSO
+.Xr chflags 1 ,
+.Xr chmod 1 ,
+.Xr locate 1 ,
+.Xr xargs 1 ,
+.Xr stat 2 ,
+.Xr fts 3 ,
+.Xr getgrent 3 ,
+.Xr getpwent 3 ,
+.Xr strmode 3 ,
+.Xr re_format 7 ,
+.Xr symlink 7 ,
+.Xr sysctl 8
+.Sh STANDARDS
+The
+.Nm
+utility syntax is a superset of the syntax specified by the
+.St -p1003.2
+standard.
+.Pp
+The options and the
+.Ic -amin ,
+.Ic -anewer ,
+.Ic -asince ,
+.Ic -cmin ,
+.Ic -cnewer ,
+.Ic -csince ,
+.Ic -delete ,
+.Ic -empty ,
+.Ic -execdir ,
+.Ic -follow ,
+.Ic -fstype ,
+.Ic -iname ,
+.Ic -inum ,
+.Ic -iregex ,
+.Ic -links ,
+.Ic -ls ,
+.Ic -maxdepth ,
+.Ic -mindepth ,
+.Ic -mmin ,
+.Ic -path ,
+.Ic -print0 ,
+.Ic -printx ,
+.Ic -regex ,
+.Ic -rm ,
+and
+.Ic -since
+primaries are extensions to
+.St -p1003.2 .
+.Pp
+Historically, the
+.Fl d ,
+.Fl h ,
+and
+.Fl x
+options were implemented using the primaries
+.Dq Ic -depth ,
+.Dq Ic -follow ,
+and
+.Dq Ic -xdev .
+These primaries always evaluated to true, and always
+took effect when the
+.Ar expression
+was parsed, before the file system traversal began.
+As a result, some legal expressions could be confusing.
+For example, in the expression
+.Dq Ic -print Ic -or Ic -depth ,
+.Ic -print
+always evaluates to true, so the standard meaning of
+.Ic -or
+implies that
+.Ic -depth
+would never be evaluated, but that is not what happens;
+in fact,
+.Ic -depth
+takes effect immediately, without testing whether
+.Ic -print
+returns true or false.
+.Pp
+Historically, the operator
+.Dq Ic -or
+was implemented as
+.Dq Ic -o ,
+and the operator
+.Dq Ic -and
+was implemented as
+.Dq Ic -a .
+.Pp
+Historic implementations of the
+.Dq Ic -exec
+and
+.Dq Ic -ok
+primaries did not replace the string
+.Dq Ic {}
+in the utility name or the
+utility arguments if it did not appear as a separate argument.
+This version replaces it no matter where in the utility name or arguments
+it appears.
+.Pp
+Support for
+.Dq Ic -exec Ar ... Ic \&+
+is consistent with
+.Em IEEE PASC Interpretation 1003.2 #210 ,
+though the feature originated in
+.Tn SVR4 .
+.Pp
+The
+.Ic -delete
+primary does not interact well with other options that cause the file system
+tree traversal options to be changed.
+.Sh HISTORY
+A much simpler
+.Nm
+command appeared in First Edition AT&T Unix.
+The syntax had become similar to the present version by
+the time of the Fifth Edition.
+.Sh BUGS
+The special characters used by
+.Nm
+are also special characters to many shell programs.
+In particular, the characters
+.Dq \&* ,
+.Dq \&[ ,
+.Dq \&] ,
+.Dq \&? ,
+.Dq \&( ,
+.Dq \&) ,
+.Dq \&! ,
+.Dq \e ,
+and
+.Dq \&;
+may have to be escaped from the shell.
+.Pp
+As there is no delimiter separating options and file names or file
+names and the
+.Ar expression ,
+it is difficult to specify files named
+.Dq -xdev
+or
+.Dq \&! .
+These problems are handled by the
+.Fl f
+option and the
+.Xr getopt 3
+.Dq --
+construct.
diff --git a/usr.bin/find/find.c b/usr.bin/find/find.c
new file mode 100644
index 0000000..473244b
--- /dev/null
+++ b/usr.bin/find/find.c
@@ -0,0 +1,306 @@
+/* $NetBSD: find.c,v 1.30 2016/06/13 00:04:40 pgoyette Exp $ */
+
+/*-
+ * Copyright (c) 1991, 1993, 1994
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Cimarron D. Taylor of the University of California, Berkeley.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+#ifndef lint
+#if 0
+static char sccsid[] = "from: @(#)find.c 8.5 (Berkeley) 8/5/94";
+#else
+__RCSID("$NetBSD: find.c,v 1.30 2016/06/13 00:04:40 pgoyette Exp $");
+#endif
+#endif /* not lint */
+
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#include <err.h>
+#include <errno.h>
+#include <fts.h>
+#include <signal.h>
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <stdbool.h>
+#include <unistd.h>
+
+#include "find.h"
+
+static int ftscompare(const FTSENT **, const FTSENT **);
+
+/*
+ * find_formplan --
+ * process the command line and create a "plan" corresponding to the
+ * command arguments.
+ */
+PLAN *
+find_formplan(char **argv)
+{
+ PLAN *plan, *tail, *new;
+
+ /*
+ * for each argument in the command line, determine what kind of node
+ * it is, create the appropriate node type and add the new plan node
+ * to the end of the existing plan. The resulting plan is a linked
+ * list of plan nodes. For example, the string:
+ *
+ * % find . -name foo -newer bar -print
+ *
+ * results in the plan:
+ *
+ * [-name foo]--> [-newer bar]--> [-print]
+ *
+ * in this diagram, `[-name foo]' represents the plan node generated
+ * by c_name() with an argument of foo and `-->' represents the
+ * plan->next pointer.
+ */
+ for (plan = tail = NULL; *argv;) {
+ if (!(new = find_create(&argv)))
+ continue;
+ if (plan == NULL)
+ tail = plan = new;
+ else {
+ tail->next = new;
+ tail = new;
+ }
+ }
+
+ /*
+ * if the user didn't specify one of -print, -ok, -fprint, -exec, or
+ * -exit, then -print is assumed so we bracket the current expression
+ * with parens, if necessary, and add a -print node on the end.
+ */
+ if (!isoutput) {
+ if (plan == NULL) {
+ new = c_print(NULL, 0, NULL);
+ tail = plan = new;
+ } else {
+ new = c_openparen(NULL, 0, NULL);
+ new->next = plan;
+ plan = new;
+ new = c_closeparen(NULL, 0, NULL);
+ tail->next = new;
+ tail = new;
+ new = c_print(NULL, 0, NULL);
+ tail->next = new;
+ tail = new;
+ }
+ }
+
+ /*
+ * the command line has been completely processed into a search plan
+ * except for the (, ), !, and -o operators. Rearrange the plan so
+ * that the portions of the plan which are affected by the operators
+ * are moved into operator nodes themselves. For example:
+ *
+ * [!]--> [-name foo]--> [-print]
+ *
+ * becomes
+ *
+ * [! [-name foo] ]--> [-print]
+ *
+ * and
+ *
+ * [(]--> [-depth]--> [-name foo]--> [)]--> [-print]
+ *
+ * becomes
+ *
+ * [expr [-depth]-->[-name foo] ]--> [-print]
+ *
+ * operators are handled in order of precedence.
+ */
+
+ plan = paren_squish(plan); /* ()'s */
+ plan = not_squish(plan); /* !'s */
+ plan = or_squish(plan); /* -o's */
+ return (plan);
+}
+
+static int
+ftscompare(const FTSENT **e1, const FTSENT **e2)
+{
+
+ return (strcoll((*e1)->fts_name, (*e2)->fts_name));
+}
+
+static sigset_t ss;
+static bool notty;
+
+static __inline void
+sig_init(void)
+{
+ struct sigaction sa;
+ notty = !(isatty(STDIN_FILENO) || isatty(STDOUT_FILENO) ||
+ isatty(STDERR_FILENO));
+ if (notty)
+ return;
+ sigemptyset(&ss);
+ sigaddset(&ss, SIGINFO); /* block SIGINFO */
+
+ memset(&sa, 0, sizeof(sa));
+ sa.sa_flags = SA_RESTART;
+ sa.sa_handler = show_path;
+ (void)sigaction(SIGINFO, &sa, NULL);
+
+}
+
+static __inline void
+sig_lock(sigset_t *s)
+{
+ if (notty)
+ return;
+ sigprocmask(SIG_BLOCK, &ss, s);
+}
+
+static __inline void
+sig_unlock(const sigset_t *s)
+{
+ if (notty)
+ return;
+ sigprocmask(SIG_SETMASK, s, NULL);
+}
+
+FTS *tree; /* pointer to top of FTS hierarchy */
+FTSENT *g_entry; /* shared with SIGINFO handler */
+
+/*
+ * find_execute --
+ * take a search plan and an array of search paths and executes the plan
+ * over all FTSENT's returned for the given search paths.
+ */
+int
+find_execute(PLAN *plan, char **paths)
+{
+ PLAN *p;
+ int r, rval, cval;
+ sigset_t s;
+
+ cval = 1;
+
+ if (!(tree = fts_open(paths, ftsoptions, issort ? ftscompare : NULL)))
+ err(1, "ftsopen");
+
+ sig_init();
+ sig_lock(&s);
+ for (rval = 0; cval && (g_entry = fts_read(tree)) != NULL;) {
+ switch (g_entry->fts_info) {
+ case FTS_D:
+ if (isdepth)
+ continue;
+ break;
+ case FTS_DP:
+ if (!isdepth)
+ continue;
+ break;
+ case FTS_DNR:
+ case FTS_ERR:
+ case FTS_NS:
+ sig_unlock(&s);
+ (void)fflush(stdout);
+ warnx("%s: %s",
+ g_entry->fts_path, strerror(g_entry->fts_errno));
+ rval = 1;
+ sig_lock(&s);
+ continue;
+ }
+#define BADCH " \t\n\\'\""
+ if (isxargs && strpbrk(g_entry->fts_path, BADCH)) {
+ sig_unlock(&s);
+ (void)fflush(stdout);
+ warnx("%s: illegal path", g_entry->fts_path);
+ rval = 1;
+ sig_lock(&s);
+ continue;
+ }
+
+ /*
+ * Call all the functions in the execution plan until one is
+ * false or all have been executed. This is where we do all
+ * the work specified by the user on the command line.
+ */
+ sig_unlock(&s);
+ for (p = plan; p && (p->eval)(p, g_entry); p = p->next)
+ if (p->type == N_EXIT) {
+ rval = p->exit_val;
+ cval = 0;
+ }
+ sig_lock(&s);
+ }
+
+ sig_unlock(&s);
+ if (g_entry == NULL && errno)
+ err(1, "fts_read");
+ (void)fts_close(tree);
+
+ /*
+ * Cleanup any plans with leftover state.
+ * Keep the last non-zero return value.
+ */
+ if ((r = find_traverse(plan, plan_cleanup, NULL)) != 0)
+ rval = r;
+
+ return (rval);
+}
+
+/*
+ * find_traverse --
+ * traverse the plan tree and execute func() on all plans. This
+ * does not evaluate each plan's eval() function; it is intended
+ * for operations that must run on all plans, such as state
+ * cleanup.
+ *
+ * If any func() returns non-zero, then so will find_traverse().
+ */
+int
+find_traverse(PLAN *plan, int (*func)(PLAN *, void *), void *arg)
+{
+ PLAN *p;
+ int r, rval;
+
+ rval = 0;
+ for (p = plan; p; p = p->next) {
+ if ((r = func(p, arg)) != 0)
+ rval = r;
+ if (p->type == N_EXPR || p->type == N_OR) {
+ if (p->p_data[0])
+ if ((r = find_traverse(p->p_data[0],
+ func, arg)) != 0)
+ rval = r;
+ if (p->p_data[1])
+ if ((r = find_traverse(p->p_data[1],
+ func, arg)) != 0)
+ rval = r;
+ }
+ }
+ return rval;
+}
diff --git a/usr.bin/find/find.h b/usr.bin/find/find.h
new file mode 100644
index 0000000..912e60d
--- /dev/null
+++ b/usr.bin/find/find.h
@@ -0,0 +1,138 @@
+/* $NetBSD: find.h,v 1.26 2016/06/13 00:04:40 pgoyette Exp $ */
+
+/*-
+ * Copyright (c) 1990, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Cimarron D. Taylor of the University of California, Berkeley.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * from: @(#)find.h 8.1 (Berkeley) 6/6/93
+ */
+
+#include <regex.h>
+#include <time.h>
+
+/* node type */
+enum ntype {
+ N_AND = 1, /* must start > 0 */
+ N_AMIN, N_ANEWER, N_ASINCE, N_ATIME, N_CLOSEPAREN, N_CMIN, N_CNEWER,
+ N_CSINCE, N_CTIME, N_DEPTH, N_EMPTY, N_EXEC, N_EXECDIR, N_EXIT,
+ N_EXPR, N_FALSE, N_FLAGS, N_FOLLOW, N_FPRINT, N_FSTYPE, N_GROUP,
+ N_INAME, N_INUM, N_IREGEX, N_LINKS, N_LS, N_MINDEPTH, N_MAXDEPTH,
+ N_MMIN, N_MTIME, N_NAME, N_NEWER, N_NOGROUP, N_NOT, N_NOUSER, N_OK,
+ N_OPENPAREN, N_OR, N_PATH, N_PERM, N_PRINT, N_PRINT0, N_PRINTX,
+ N_PRUNE, N_REGEX, N_SINCE, N_SIZE, N_TYPE, N_USER, N_XDEV, N_DELETE
+};
+
+/* node definition */
+typedef struct _plandata {
+ struct _plandata *next; /* next node */
+ int (*eval)(struct _plandata *, FTSENT *);
+ /* node evaluation function */
+#define F_EQUAL 1 /* [acm]time inum links size */
+#define F_LESSTHAN 2
+#define F_GREATER 3
+#define F_NEEDOK 1 /* exec ok */
+#define F_PLUSSET 2 /* -exec ... {} + */
+#define F_MTFLAG 1 /* fstype */
+#define F_MTTYPE 2
+#define F_ATLEAST 1 /* perm */
+ int flags; /* private flags */
+ enum ntype type; /* plan node type */
+ union {
+ u_int32_t _f_data; /* flags */
+ gid_t _g_data; /* gid */
+ ino_t _i_data; /* inode */
+ mode_t _m_data; /* mode mask */
+ nlink_t _l_data; /* link count */
+ off_t _o_data; /* file size */
+ time_t _t_data; /* time value */
+ struct timespec _ts_data; /* time value */
+ uid_t _u_data; /* uid */
+ short _mt_data; /* mount flags */
+ struct _plandata *_p_data[2]; /* PLAN trees */
+ struct _ex {
+ char **_e_argv; /* argv array */
+ char **_e_orig; /* original strings */
+ int *_e_len; /* allocated length */
+ char **_ep_bxp; /* ptr to 1st addt'l arg */
+ char *_ep_p; /* current buffer pointer */
+ char *_ep_bbp; /* begin buffer pointer */
+ char *_ep_ebp; /* end buffer pointer */
+ int _ep_maxargs; /* max #args */
+ int _ep_narg; /* # addt'l args */
+ int _ep_rval; /* return value */
+ } ex;
+ char *_a_data[2]; /* array of char pointers */
+ char *_c_data; /* char pointer */
+ int _exit_val; /* exit value */
+ int _max_data; /* tree depth */
+ int _min_data; /* tree depth */
+ regex_t _regexp_data; /* compiled regexp */
+ FILE *_fprint_file; /* file stream for -fprint */
+ } p_un;
+} PLAN;
+#define a_data p_un._a_data
+#define c_data p_un._c_data
+#define i_data p_un._i_data
+#define f_data p_un._f_data
+#define g_data p_un._g_data
+#define l_data p_un._l_data
+#define m_data p_un._m_data
+#define mt_data p_un._mt_data
+#define o_data p_un._o_data
+#define p_data p_un._p_data
+#define t_data p_un._t_data
+#define ts_data p_un._ts_data
+#define u_data p_un._u_data
+#define e_argv p_un.ex._e_argv
+#define e_orig p_un.ex._e_orig
+#define e_len p_un.ex._e_len
+#define ep_p p_un.ex._ep_p
+#define ep_bbp p_un.ex._ep_bbp
+#define ep_ebp p_un.ex._ep_ebp
+#define ep_bxp p_un.ex._ep_bxp
+#define ep_cnt p_un.ex._ep_cnt
+#define ep_maxargs p_un.ex._ep_maxargs
+#define ep_nline p_un.ex._ep_nline
+#define ep_narg p_un.ex._ep_narg
+#define ep_rval p_un.ex._ep_rval
+#define exit_val p_un._exit_val
+#define max_data p_un._max_data
+#define min_data p_un._min_data
+#define regexp_data p_un._regexp_data
+#define fprint_file p_un._fprint_file
+
+typedef struct _option {
+ const char *name; /* option name */
+ enum ntype token; /* token type */
+ PLAN *(*create)(char ***, int, char *); /* create function */
+ int arg; /* function needs arg */
+} OPTION;
+
+#include "extern.h"
diff --git a/usr.bin/find/function.c b/usr.bin/find/function.c
new file mode 100644
index 0000000..02540f2
--- /dev/null
+++ b/usr.bin/find/function.c
@@ -0,0 +1,2025 @@
+/* $NetBSD: function.c,v 1.77 2018/09/04 15:16:15 kre Exp $ */
+
+/*-
+ * Copyright (c) 1990, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Cimarron D. Taylor of the University of California, Berkeley.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+#ifndef lint
+#if 0
+static char sccsid[] = "from: @(#)function.c 8.10 (Berkeley) 5/4/95";
+#else
+__RCSID("$NetBSD: function.c,v 1.77 2018/09/04 15:16:15 kre Exp $");
+#endif
+#endif /* not lint */
+
+#include <sys/param.h>
+#include <sys/stat.h>
+#include <sys/wait.h>
+#include <sys/mount.h>
+
+#include <dirent.h>
+#include <err.h>
+#include <errno.h>
+#include <fnmatch.h>
+#include <fts.h>
+#include <grp.h>
+#include <inttypes.h>
+#include <limits.h>
+#include <pwd.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <tzfile.h>
+#include <unistd.h>
+#include <util.h>
+
+#include "find.h"
+
+#define COMPARE(a, b) { \
+ switch (plan->flags) { \
+ case F_EQUAL: \
+ return (a == b); \
+ case F_LESSTHAN: \
+ return (a < b); \
+ case F_GREATER: \
+ return (a > b); \
+ default: \
+ abort(); \
+ } \
+}
+
+static int64_t find_parsenum(PLAN *, const char *, const char *, char *);
+static void run_f_exec(PLAN *);
+ int f_always_true(PLAN *, FTSENT *);
+ int f_amin(PLAN *, FTSENT *);
+ int f_anewer(PLAN *, FTSENT *);
+ int f_asince(PLAN *, FTSENT *);
+ int f_atime(PLAN *, FTSENT *);
+ int f_cmin(PLAN *, FTSENT *);
+ int f_cnewer(PLAN *, FTSENT *);
+ int f_csince(PLAN *, FTSENT *);
+ int f_ctime(PLAN *, FTSENT *);
+ int f_delete(PLAN *, FTSENT *);
+ int f_empty(PLAN *, FTSENT *);
+ int f_exec(PLAN *, FTSENT *);
+ int f_execdir(PLAN *, FTSENT *);
+ int f_false(PLAN *, FTSENT *);
+ int f_flags(PLAN *, FTSENT *);
+ int f_fprint(PLAN *, FTSENT *);
+ int f_fstype(PLAN *, FTSENT *);
+ int f_group(PLAN *, FTSENT *);
+ int f_iname(PLAN *, FTSENT *);
+ int f_inum(PLAN *, FTSENT *);
+ int f_links(PLAN *, FTSENT *);
+ int f_ls(PLAN *, FTSENT *);
+ int f_mindepth(PLAN *, FTSENT *);
+ int f_maxdepth(PLAN *, FTSENT *);
+ int f_mmin(PLAN *, FTSENT *);
+ int f_mtime(PLAN *, FTSENT *);
+ int f_name(PLAN *, FTSENT *);
+ int f_newer(PLAN *, FTSENT *);
+/*
+ * Unimplemented Gnu findutils options
+ *
+ int f_newerBB(PLAN *, FTSENT *);
+ int f_newerBa(PLAN *, FTSENT *);
+ int f_newerBc(PLAN *, FTSENT *);
+ int f_newerBm(PLAN *, FTSENT *);
+ int f_newerBt(PLAN *, FTSENT *);
+ int f_neweraB(PLAN *, FTSENT *);
+ int f_newerac(PLAN *, FTSENT *);
+ int f_neweram(PLAN *, FTSENT *);
+ int f_newerca(PLAN *, FTSENT *);
+ int f_newercm(PLAN *, FTSENT *);
+ int f_newercB(PLAN *, FTSENT *);
+ int f_newermB(PLAN *, FTSENT *);
+ int f_newerma(PLAN *, FTSENT *);
+ int f_newermc(PLAN *, FTSENT *);
+ *
+ */
+ int f_nogroup(PLAN *, FTSENT *);
+ int f_nouser(PLAN *, FTSENT *);
+ int f_path(PLAN *, FTSENT *);
+ int f_perm(PLAN *, FTSENT *);
+ int f_print(PLAN *, FTSENT *);
+ int f_print0(PLAN *, FTSENT *);
+ int f_printx(PLAN *, FTSENT *);
+ int f_prune(PLAN *, FTSENT *);
+ int f_regex(PLAN *, FTSENT *);
+ int f_since(PLAN *, FTSENT *);
+ int f_size(PLAN *, FTSENT *);
+ int f_type(PLAN *, FTSENT *);
+ int f_user(PLAN *, FTSENT *);
+ int f_not(PLAN *, FTSENT *);
+ int f_or(PLAN *, FTSENT *);
+static PLAN *c_regex_common(char ***, int, enum ntype, bool);
+static PLAN *palloc(enum ntype, int (*)(PLAN *, FTSENT *));
+
+extern int dotfd;
+extern FTS *tree;
+extern time_t now;
+
+/*
+ * find_parsenum --
+ * Parse a string of the form [+-]# and return the value.
+ */
+static int64_t
+find_parsenum(PLAN *plan, const char *option, const char *vp, char *endch)
+{
+ int64_t value;
+ const char *str;
+ char *endchar; /* Pointer to character ending conversion. */
+
+ /* Determine comparison from leading + or -. */
+ str = vp;
+ switch (*str) {
+ case '+':
+ ++str;
+ plan->flags = F_GREATER;
+ break;
+ case '-':
+ ++str;
+ plan->flags = F_LESSTHAN;
+ break;
+ default:
+ plan->flags = F_EQUAL;
+ break;
+ }
+
+ /*
+ * Convert the string with strtol(). Note, if strtol() returns zero
+ * and endchar points to the beginning of the string we know we have
+ * a syntax error.
+ */
+ value = strtoq(str, &endchar, 10);
+ if (value == 0 && endchar == str)
+ errx(1, "%s: %s: illegal numeric value", option, vp);
+ if (endchar[0] && (endch == NULL || endchar[0] != *endch))
+ errx(1, "%s: %s: illegal trailing character", option, vp);
+ if (endch)
+ *endch = endchar[0];
+ return (value);
+}
+
+/*
+ * find_parsedate --
+ *
+ * Validate the timestamp argument or report an error
+ */
+static time_t
+find_parsedate(PLAN *plan, const char *option, const char *vp)
+{
+ time_t timestamp;
+
+ errno = 0;
+ timestamp = parsedate(vp, NULL, NULL);
+ if (timestamp == -1 && errno != 0)
+ errx(1, "%s: %s: invalid timestamp value", option, vp);
+ return timestamp;
+}
+
+/*
+ * The value of n for the inode times (atime, ctime, and mtime) is a range,
+ * i.e. n matches from (n - 1) to n 24 hour periods. This interacts with
+ * -n, such that "-mtime -1" would be less than 0 days, which isn't what the
+ * user wanted. Correct so that -1 is "less than 1".
+ */
+#define TIME_CORRECT(p, ttype) \
+ if ((p)->type == ttype && (p)->flags == F_LESSTHAN) \
+ ++((p)->t_data);
+
+/*
+ * -amin n functions --
+ *
+ * True if the difference between the file access time and the
+ * current time is n 1 minute periods.
+ */
+int
+f_amin(PLAN *plan, FTSENT *entry)
+{
+ COMPARE((now - entry->fts_statp->st_atime +
+ SECSPERMIN - 1) / SECSPERMIN, plan->t_data);
+}
+
+PLAN *
+c_amin(char ***argvp, int isok, char *opt)
+{
+ char *arg = **argvp;
+ PLAN *new;
+
+ (*argvp)++;
+ ftsoptions &= ~FTS_NOSTAT;
+
+ new = palloc(N_AMIN, f_amin);
+ new->t_data = find_parsenum(new, opt, arg, NULL);
+ TIME_CORRECT(new, N_AMIN);
+ return (new);
+}
+
+/*
+ * -anewer file functions --
+ *
+ * True if the current file has been accessed more recently
+ * than the access time of the file named by the pathname
+ * file.
+ */
+int
+f_anewer(PLAN *plan, FTSENT *entry)
+{
+
+ return timespeccmp(&entry->fts_statp->st_atim, &plan->ts_data, >);
+}
+
+PLAN *
+c_anewer(char ***argvp, int isok, char *opt)
+{
+ char *filename = **argvp;
+ PLAN *new;
+ struct stat sb;
+
+ (*argvp)++;
+ ftsoptions &= ~FTS_NOSTAT;
+
+ if (stat(filename, &sb))
+ err(1, "%s: %s", opt, filename);
+ new = palloc(N_ANEWER, f_anewer);
+ new->ts_data = sb.st_atim;
+ return (new);
+}
+
+/*
+ * -asince "timestamp" functions --
+ *
+ * True if the file access time is greater than the timestamp value
+ */
+int
+f_asince(PLAN *plan, FTSENT *entry)
+{
+ COMPARE(entry->fts_statp->st_atime, plan->t_data);
+}
+
+PLAN *
+c_asince(char ***argvp, int isok, char *opt)
+{
+ char *arg = **argvp;
+ PLAN *new;
+
+ (*argvp)++;
+ ftsoptions &= ~FTS_NOSTAT;
+
+ new = palloc(N_ASINCE, f_asince);
+ new->t_data = find_parsedate(new, opt, arg);
+ new->flags = F_GREATER;
+ return (new);
+}
+
+/*
+ * -atime n functions --
+ *
+ * True if the difference between the file access time and the
+ * current time is n 24 hour periods.
+ */
+int
+f_atime(PLAN *plan, FTSENT *entry)
+{
+ COMPARE((now - entry->fts_statp->st_atime +
+ SECSPERDAY - 1) / SECSPERDAY, plan->t_data);
+}
+
+PLAN *
+c_atime(char ***argvp, int isok, char *opt)
+{
+ char *arg = **argvp;
+ PLAN *new;
+
+ (*argvp)++;
+ ftsoptions &= ~FTS_NOSTAT;
+
+ new = palloc(N_ATIME, f_atime);
+ new->t_data = find_parsenum(new, opt, arg, NULL);
+ TIME_CORRECT(new, N_ATIME);
+ return (new);
+}
+
+/*
+ * -cmin n functions --
+ *
+ * True if the difference between the last change of file
+ * status information and the current time is n 24 hour periods.
+ */
+int
+f_cmin(PLAN *plan, FTSENT *entry)
+{
+ COMPARE((now - entry->fts_statp->st_ctime +
+ SECSPERMIN - 1) / SECSPERMIN, plan->t_data);
+}
+
+PLAN *
+c_cmin(char ***argvp, int isok, char *opt)
+{
+ char *arg = **argvp;
+ PLAN *new;
+
+ (*argvp)++;
+ ftsoptions &= ~FTS_NOSTAT;
+
+ new = palloc(N_CMIN, f_cmin);
+ new->t_data = find_parsenum(new, opt, arg, NULL);
+ TIME_CORRECT(new, N_CMIN);
+ return (new);
+}
+
+/*
+ * -cnewer file functions --
+ *
+ * True if the current file has been changed more recently
+ * than the changed time of the file named by the pathname
+ * file.
+ */
+int
+f_cnewer(PLAN *plan, FTSENT *entry)
+{
+
+ return timespeccmp(&entry->fts_statp->st_ctim, &plan->ts_data, >);
+}
+
+PLAN *
+c_cnewer(char ***argvp, int isok, char *opt)
+{
+ char *filename = **argvp;
+ PLAN *new;
+ struct stat sb;
+
+ (*argvp)++;
+ ftsoptions &= ~FTS_NOSTAT;
+
+ if (stat(filename, &sb))
+ err(1, "%s: %s ", opt, filename);
+ new = palloc(N_CNEWER, f_cnewer);
+ new->ts_data = sb.st_ctim;
+ return (new);
+}
+
+/*
+ * -csince "timestamp" functions --
+ *
+ * True if the file status change time is greater than the timestamp value
+ */
+int
+f_csince(PLAN *plan, FTSENT *entry)
+{
+ COMPARE(entry->fts_statp->st_ctime, plan->t_data);
+}
+
+PLAN *
+c_csince(char ***argvp, int isok, char *opt)
+{
+ char *arg = **argvp;
+ PLAN *new;
+
+ (*argvp)++;
+ ftsoptions &= ~FTS_NOSTAT;
+
+ new = palloc(N_CSINCE, f_csince);
+ new->t_data = find_parsedate(new, opt, arg);
+ new->flags = F_GREATER;
+ return (new);
+}
+
+/*
+ * -ctime n functions --
+ *
+ * True if the difference between the last change of file
+ * status information and the current time is n 24 hour periods.
+ */
+int
+f_ctime(PLAN *plan, FTSENT *entry)
+{
+ COMPARE((now - entry->fts_statp->st_ctime +
+ SECSPERDAY - 1) / SECSPERDAY, plan->t_data);
+}
+
+PLAN *
+c_ctime(char ***argvp, int isok, char *opt)
+{
+ char *arg = **argvp;
+ PLAN *new;
+
+ (*argvp)++;
+ ftsoptions &= ~FTS_NOSTAT;
+
+ new = palloc(N_CTIME, f_ctime);
+ new->t_data = find_parsenum(new, opt, arg, NULL);
+ TIME_CORRECT(new, N_CTIME);
+ return (new);
+}
+
+/*
+ * -delete functions --
+ *
+ * Always true. Makes its best shot and continues on regardless.
+ */
+int
+f_delete(PLAN *plan __unused, FTSENT *entry)
+{
+ /* ignore these from fts */
+ if (strcmp(entry->fts_accpath, ".") == 0 ||
+ strcmp(entry->fts_accpath, "..") == 0)
+ return 1;
+
+ /* sanity check */
+ if (isdepth == 0 || /* depth off */
+ (ftsoptions & FTS_NOSTAT) || /* not stat()ing */
+ !(ftsoptions & FTS_PHYSICAL) || /* physical off */
+ (ftsoptions & FTS_LOGICAL)) /* or finally, logical on */
+ errx(1, "-delete: insecure options got turned on");
+
+ /* Potentially unsafe - do not accept relative paths whatsoever */
+ if (entry->fts_level > 0 && strchr(entry->fts_accpath, '/') != NULL)
+ errx(1, "-delete: %s: relative path potentially not safe",
+ entry->fts_accpath);
+
+ /* Turn off user immutable bits if running as root */
+ if ((entry->fts_statp->st_flags & (UF_APPEND|UF_IMMUTABLE)) &&
+ !(entry->fts_statp->st_flags & (SF_APPEND|SF_IMMUTABLE)) &&
+ geteuid() == 0)
+ chflags(entry->fts_accpath,
+ entry->fts_statp->st_flags &= ~(UF_APPEND|UF_IMMUTABLE));
+
+ /* rmdir directories, unlink everything else */
+ if (S_ISDIR(entry->fts_statp->st_mode)) {
+ if (rmdir(entry->fts_accpath) < 0 && errno != ENOTEMPTY)
+ warn("-delete: rmdir(%s)", entry->fts_path);
+ } else {
+ if (unlink(entry->fts_accpath) < 0)
+ warn("-delete: unlink(%s)", entry->fts_path);
+ }
+
+ /* "succeed" */
+ return 1;
+}
+
+PLAN *
+c_delete(char ***argvp __unused, int isok, char *opt)
+{
+
+ ftsoptions &= ~FTS_NOSTAT; /* no optimize */
+ ftsoptions |= FTS_PHYSICAL; /* disable -follow */
+ ftsoptions &= ~FTS_LOGICAL; /* disable -follow */
+ isoutput = 1; /* possible output */
+ isdepth = 1; /* -depth implied */
+
+ return palloc(N_DELETE, f_delete);
+}
+
+/*
+ * -depth functions --
+ *
+ * Always true, causes descent of the directory hierarchy to be done
+ * so that all entries in a directory are acted on before the directory
+ * itself.
+ */
+int
+f_always_true(PLAN *plan, FTSENT *entry)
+{
+
+ return (1);
+}
+
+PLAN *
+c_depth(char ***argvp, int isok, char *opt)
+{
+ isdepth = 1;
+
+ return (palloc(N_DEPTH, f_always_true));
+}
+
+/*
+ * -empty functions --
+ *
+ * True if the file or directory is empty
+ */
+int
+f_empty(PLAN *plan, FTSENT *entry)
+{
+ if (S_ISREG(entry->fts_statp->st_mode) &&
+ entry->fts_statp->st_size == 0)
+ return (1);
+ if (S_ISDIR(entry->fts_statp->st_mode)) {
+ struct dirent *dp;
+ int empty;
+ DIR *dir;
+
+ empty = 1;
+ dir = opendir(entry->fts_accpath);
+ if (dir == NULL)
+ return (0);
+ for (dp = readdir(dir); dp; dp = readdir(dir))
+ if (dp->d_name[0] != '.' ||
+ (dp->d_name[1] != '\0' &&
+ (dp->d_name[1] != '.' || dp->d_name[2] != '\0'))) {
+ empty = 0;
+ break;
+ }
+ closedir(dir);
+ return (empty);
+ }
+ return (0);
+}
+
+PLAN *
+c_empty(char ***argvp, int isok, char *opt)
+{
+ ftsoptions &= ~FTS_NOSTAT;
+
+ return (palloc(N_EMPTY, f_empty));
+}
+
+/*
+ * [-exec | -ok] utility [arg ... ] ; functions --
+ * [-exec | -ok] utility [arg ... ] {} + functions --
+ *
+ * If the end of the primary expression is delimited by a
+ * semicolon: true if the executed utility returns a zero value
+ * as exit status. If "{}" occurs anywhere, it gets replaced by
+ * the current pathname.
+ *
+ * If the end of the primary expression is delimited by a plus
+ * sign: always true. Pathnames for which the primary is
+ * evaluated shall be aggregated into sets. The utility will be
+ * executed once per set, with "{}" replaced by the entire set of
+ * pathnames (as if xargs). "{}" must appear last.
+ *
+ * The current directory for the execution of utility is the same
+ * as the current directory when the find utility was started.
+ *
+ * The primary -ok is different in that it requests affirmation
+ * of the user before executing the utility.
+ */
+int
+f_exec(PLAN *plan, FTSENT *entry)
+{
+ size_t cnt;
+ int l;
+ pid_t pid;
+ int status;
+
+ if (plan->flags & F_PLUSSET) {
+ /*
+ * Confirm sufficient buffer space, then copy the path
+ * to the buffer.
+ */
+ l = strlen(entry->fts_path);
+ if (plan->ep_p + l < plan->ep_ebp) {
+ plan->ep_bxp[plan->ep_narg++] =
+ strcpy(plan->ep_p, entry->fts_path);
+ plan->ep_p += l + 1;
+
+ if (plan->ep_narg == plan->ep_maxargs)
+ run_f_exec(plan);
+ } else {
+ /*
+ * Without sufficient space to copy in the next
+ * argument, run the command to empty out the
+ * buffer before re-attepting the copy.
+ */
+ run_f_exec(plan);
+ if ((plan->ep_p + l < plan->ep_ebp)) {
+ plan->ep_bxp[plan->ep_narg++]
+ = strcpy(plan->ep_p, entry->fts_path);
+ plan->ep_p += l + 1;
+ } else
+ errx(1, "insufficient space for argument");
+ }
+ return (1);
+ } else {
+ for (cnt = 0; plan->e_argv[cnt]; ++cnt)
+ if (plan->e_len[cnt])
+ brace_subst(plan->e_orig[cnt],
+ &plan->e_argv[cnt],
+ entry->fts_path,
+ &plan->e_len[cnt]);
+ if (plan->flags & F_NEEDOK && !queryuser(plan->e_argv))
+ return (0);
+
+ /* Don't mix output of command with find output. */
+ fflush(stdout);
+ fflush(stderr);
+
+ switch (pid = vfork()) {
+ case -1:
+ err(1, "vfork");
+ /* NOTREACHED */
+ case 0:
+ if (fchdir(dotfd)) {
+ warn("chdir");
+ _exit(1);
+ }
+ execvp(plan->e_argv[0], plan->e_argv);
+ warn("%s", plan->e_argv[0]);
+ _exit(1);
+ }
+ pid = waitpid(pid, &status, 0);
+ return (pid != -1 && WIFEXITED(status)
+ && !WEXITSTATUS(status));
+ }
+}
+
+static void
+run_f_exec(PLAN *plan)
+{
+ pid_t pid;
+ int rval, status;
+
+ /* Ensure arg list is null terminated. */
+ plan->ep_bxp[plan->ep_narg] = NULL;
+
+ /* Don't mix output of command with find output. */
+ fflush(stdout);
+ fflush(stderr);
+
+ switch (pid = vfork()) {
+ case -1:
+ err(1, "vfork");
+ /* NOTREACHED */
+ case 0:
+ if (fchdir(dotfd)) {
+ warn("chdir");
+ _exit(1);
+ }
+ execvp(plan->e_argv[0], plan->e_argv);
+ warn("%s", plan->e_argv[0]);
+ _exit(1);
+ }
+
+ /* Clear out the argument list. */
+ plan->ep_narg = 0;
+ plan->ep_bxp[plan->ep_narg] = NULL;
+ /* As well as the argument buffer. */
+ plan->ep_p = plan->ep_bbp;
+ *plan->ep_p = '\0';
+
+ pid = waitpid(pid, &status, 0);
+ if (WIFEXITED(status))
+ rval = WEXITSTATUS(status);
+ else
+ rval = -1;
+
+ /*
+ * If we have a non-zero exit status, preserve it so find(1) can
+ * later exit with it.
+ */
+ if (rval)
+ plan->ep_rval = rval;
+}
+
+/*
+ * c_exec --
+ * build three parallel arrays, one with pointers to the strings passed
+ * on the command line, one with (possibly duplicated) pointers to the
+ * argv array, and one with integer values that are lengths of the
+ * strings, but also flags meaning that the string has to be massaged.
+ *
+ * If -exec ... {} +, use only the first array, but make it large
+ * enough to hold 5000 args (cf. src/usr.bin/xargs/xargs.c for a
+ * discussion), and then allocate ARG_MAX - 4K of space for args.
+ */
+PLAN *
+c_exec(char ***argvp, int isok, char *opt)
+{
+ PLAN *new; /* node returned */
+ size_t cnt;
+ int brace, lastbrace;
+ char **argv, **ap, *p;
+
+ isoutput = 1;
+
+ new = palloc(N_EXEC, f_exec);
+ if (isok)
+ new->flags |= F_NEEDOK;
+
+ /*
+ * Terminate if we encounter an arg exactly equal to ";", or an
+ * arg exactly equal to "+" following an arg exactly equal to
+ * "{}".
+ */
+ for (ap = argv = *argvp, brace = 0;; ++ap) {
+ if (!*ap)
+ errx(1, "%s: no terminating \";\" or \"+\"", opt);
+ lastbrace = brace;
+ brace = 0;
+ if (strcmp(*ap, "{}") == 0)
+ brace = 1;
+ if (strcmp(*ap, ";") == 0)
+ break;
+ if (strcmp(*ap, "+") == 0 && lastbrace) {
+ new->flags |= F_PLUSSET;
+ break;
+ }
+ }
+
+ /*
+ * POSIX says -ok ... {} + "need not be supported," and it does
+ * not make much sense anyway.
+ */
+ if (new->flags & F_NEEDOK && new->flags & F_PLUSSET)
+ errx(1, "%s: terminating \"+\" not permitted.", opt);
+
+ if (new->flags & F_PLUSSET) {
+ size_t c, bufsize;
+
+ cnt = ap - *argvp - 1; /* units are words */
+ new->ep_maxargs = ARG_MAX / (sizeof (char *) + 16);
+ if (new->ep_maxargs > 5000)
+ new->ep_maxargs = 5000;
+ new->e_argv = emalloc((cnt + new->ep_maxargs)
+ * sizeof(*new->e_argv));
+
+ /* We start stuffing arguments after the user's last one. */
+ new->ep_bxp = &new->e_argv[cnt];
+ new->ep_narg = 0;
+
+ /*
+ * Count up the space of the user's arguments, and
+ * subtract that from what we allocate.
+ */
+#define MAXARG (ARG_MAX - 4 * 1024)
+ for (argv = *argvp, c = 0, cnt = 0;
+ argv < ap;
+ ++argv, ++cnt) {
+ c += strlen(*argv) + 1;
+ if (c >= MAXARG)
+ errx(1, "Arguments too long");
+ new->e_argv[cnt] = *argv;
+ }
+ if (c + new->ep_maxargs * sizeof (char *) >= MAXARG)
+ errx(1, "Arguments too long");
+ bufsize = MAXARG - c - new->ep_maxargs * sizeof (char *);
+
+ /*
+ * Allocate, and then initialize current, base, and
+ * end pointers.
+ */
+ new->ep_p = new->ep_bbp = emalloc(bufsize + 1);
+ new->ep_ebp = new->ep_bbp + bufsize - 1;
+ new->ep_rval = 0;
+ } else { /* !F_PLUSSET */
+ cnt = ap - *argvp + 1;
+ new->e_argv = emalloc(cnt * sizeof(*new->e_argv));
+ new->e_orig = emalloc(cnt * sizeof(*new->e_orig));
+ new->e_len = emalloc(cnt * sizeof(*new->e_len));
+
+ for (argv = *argvp, cnt = 0; argv < ap; ++argv, ++cnt) {
+ new->e_orig[cnt] = *argv;
+ for (p = *argv; *p; ++p)
+ if (p[0] == '{' && p[1] == '}') {
+ new->e_argv[cnt] =
+ emalloc(MAXPATHLEN);
+ new->e_len[cnt] = MAXPATHLEN;
+ break;
+ }
+ if (!*p) {
+ new->e_argv[cnt] = *argv;
+ new->e_len[cnt] = 0;
+ }
+ }
+ new->e_orig[cnt] = NULL;
+ }
+
+ new->e_argv[cnt] = NULL;
+ *argvp = argv + 1;
+ return (new);
+}
+
+/*
+ * -execdir utility [arg ... ] ; functions --
+ *
+ * True if the executed utility returns a zero value as exit status.
+ * The end of the primary expression is delimited by a semicolon. If
+ * "{}" occurs anywhere, it gets replaced by the unqualified pathname.
+ * The current directory for the execution of utility is the same as
+ * the directory where the file lives.
+ */
+int
+f_execdir(PLAN *plan, FTSENT *entry)
+{
+ size_t cnt;
+ pid_t pid;
+ int status;
+ char *file;
+
+ /* XXX - if file/dir ends in '/' this will not work -- can it? */
+ if ((file = strrchr(entry->fts_path, '/')))
+ file++;
+ else
+ file = entry->fts_path;
+
+ for (cnt = 0; plan->e_argv[cnt]; ++cnt)
+ if (plan->e_len[cnt])
+ brace_subst(plan->e_orig[cnt], &plan->e_argv[cnt],
+ file, &plan->e_len[cnt]);
+
+ /* don't mix output of command with find output */
+ fflush(stdout);
+ fflush(stderr);
+
+ switch (pid = vfork()) {
+ case -1:
+ err(1, "fork");
+ /* NOTREACHED */
+ case 0:
+ execvp(plan->e_argv[0], plan->e_argv);
+ warn("%s", plan->e_argv[0]);
+ _exit(1);
+ }
+ pid = waitpid(pid, &status, 0);
+ return (pid != -1 && WIFEXITED(status) && !WEXITSTATUS(status));
+}
+
+/*
+ * c_execdir --
+ * build three parallel arrays, one with pointers to the strings passed
+ * on the command line, one with (possibly duplicated) pointers to the
+ * argv array, and one with integer values that are lengths of the
+ * strings, but also flags meaning that the string has to be massaged.
+ */
+PLAN *
+c_execdir(char ***argvp, int isok, char *opt)
+{
+ PLAN *new; /* node returned */
+ size_t cnt;
+ char **argv, **ap, *p;
+
+ ftsoptions &= ~FTS_NOSTAT;
+ isoutput = 1;
+
+ new = palloc(N_EXECDIR, f_execdir);
+
+ for (ap = argv = *argvp;; ++ap) {
+ if (!*ap)
+ errx(1, "%s: no terminating \";\"", opt);
+ if (**ap == ';')
+ break;
+ }
+
+ cnt = ap - *argvp + 1;
+ new->e_argv = emalloc(cnt * sizeof(*new->e_argv));
+ new->e_orig = emalloc(cnt * sizeof(*new->e_orig));
+ new->e_len = emalloc(cnt * sizeof(*new->e_len));
+
+ for (argv = *argvp, cnt = 0; argv < ap; ++argv, ++cnt) {
+ new->e_orig[cnt] = *argv;
+ for (p = *argv; *p; ++p)
+ if (p[0] == '{' && p[1] == '}') {
+ new->e_argv[cnt] = emalloc(MAXPATHLEN);
+ new->e_len[cnt] = MAXPATHLEN;
+ break;
+ }
+ if (!*p) {
+ new->e_argv[cnt] = *argv;
+ new->e_len[cnt] = 0;
+ }
+ }
+ new->e_argv[cnt] = new->e_orig[cnt] = NULL;
+
+ *argvp = argv + 1;
+ return (new);
+}
+
+PLAN *
+c_exit(char ***argvp, int isok, char *opt)
+{
+ char *arg = **argvp;
+ PLAN *new;
+
+ /* not technically true, but otherwise '-print' is implied */
+ isoutput = 1;
+
+ new = palloc(N_EXIT, f_always_true);
+
+ if (arg) {
+ (*argvp)++;
+ new->exit_val = find_parsenum(new, opt, arg, NULL);
+ } else
+ new->exit_val = 0;
+
+ return (new);
+}
+
+
+/*
+ * -false function
+ */
+int
+f_false(PLAN *plan, FTSENT *entry)
+{
+
+ return (0);
+}
+
+PLAN *
+c_false(char ***argvp, int isok, char *opt)
+{
+ return (palloc(N_FALSE, f_false));
+}
+
+
+/*
+ * -flags [-]flags functions --
+ */
+int
+f_flags(PLAN *plan, FTSENT *entry)
+{
+ u_int32_t flags;
+
+ flags = entry->fts_statp->st_flags;
+ if (plan->flags == F_ATLEAST)
+ return ((plan->f_data | flags) == flags);
+ else
+ return (flags == plan->f_data);
+ /* NOTREACHED */
+}
+
+PLAN *
+c_flags(char ***argvp, int isok, char *opt)
+{
+ char *flags = **argvp;
+ PLAN *new;
+ u_long flagset;
+
+ (*argvp)++;
+ ftsoptions &= ~FTS_NOSTAT;
+
+ new = palloc(N_FLAGS, f_flags);
+
+ if (*flags == '-') {
+ new->flags = F_ATLEAST;
+ ++flags;
+ }
+
+ flagset = 0;
+ if ((strcmp(flags, "none") != 0) &&
+ (string_to_flags(&flags, &flagset, NULL) != 0))
+ errx(1, "%s: %s: illegal flags string", opt, flags);
+ new->f_data = flagset;
+ return (new);
+}
+
+/*
+ * -follow functions --
+ *
+ * Always true, causes symbolic links to be followed on a global
+ * basis.
+ */
+PLAN *
+c_follow(char ***argvp, int isok, char *opt)
+{
+ ftsoptions &= ~FTS_PHYSICAL;
+ ftsoptions |= FTS_LOGICAL;
+
+ return (palloc(N_FOLLOW, f_always_true));
+}
+
+/* -fprint functions --
+ *
+ * Causes the current pathame to be written to the defined output file.
+ */
+int
+f_fprint(PLAN *plan, FTSENT *entry)
+{
+
+ if (-1 == fprintf(plan->fprint_file, "%s\n", entry->fts_path))
+ warn("fprintf");
+
+ return(1);
+
+ /* no descriptors are closed; they will be closed by
+ operating system when this find command exits. */
+}
+
+PLAN *
+c_fprint(char ***argvp, int isok, char *opt)
+{
+ PLAN *new;
+
+ isoutput = 1; /* do not assume -print */
+
+ new = palloc(N_FPRINT, f_fprint);
+
+ if (NULL == (new->fprint_file = fopen(**argvp, "w")))
+ err(1, "%s: %s: cannot create file", opt, **argvp);
+
+ (*argvp)++;
+ return (new);
+}
+
+/*
+ * -fstype functions --
+ *
+ * True if the file is of a certain type.
+ */
+int
+f_fstype(PLAN *plan, FTSENT *entry)
+{
+ static dev_t curdev; /* need a guaranteed illegal dev value */
+ static int first = 1;
+ struct statvfs sb;
+ static short val;
+ static char fstype[sizeof(sb.f_fstypename)];
+ char *p, save[2];
+
+ memset(&save, 0, sizeof save); /* XXX gcc */
+
+ /* Only check when we cross mount point. */
+ if (first || curdev != entry->fts_statp->st_dev) {
+ curdev = entry->fts_statp->st_dev;
+
+ /*
+ * Statfs follows symlinks; find wants the link's file system,
+ * not where it points.
+ */
+ if (entry->fts_info == FTS_SL ||
+ entry->fts_info == FTS_SLNONE) {
+ if ((p = strrchr(entry->fts_accpath, '/')) != NULL)
+ ++p;
+ else
+ p = entry->fts_accpath;
+ save[0] = p[0];
+ p[0] = '.';
+ save[1] = p[1];
+ p[1] = '\0';
+
+ } else
+ p = NULL;
+
+ if (statvfs(entry->fts_accpath, &sb))
+ err(1, "%s", entry->fts_accpath);
+
+ if (p) {
+ p[0] = save[0];
+ p[1] = save[1];
+ }
+
+ first = 0;
+
+ /*
+ * Further tests may need both of these values, so
+ * always copy both of them.
+ */
+ val = sb.f_flag;
+ strlcpy(fstype, sb.f_fstypename, sizeof(fstype));
+ }
+ switch (plan->flags) {
+ case F_MTFLAG:
+ return (val & plan->mt_data);
+ case F_MTTYPE:
+ return (strncmp(fstype, plan->c_data, sizeof(fstype)) == 0);
+ default:
+ abort();
+ }
+}
+
+PLAN *
+c_fstype(char ***argvp, int isok, char *opt)
+{
+ char *arg = **argvp;
+ PLAN *new;
+
+ (*argvp)++;
+ ftsoptions &= ~FTS_NOSTAT;
+
+ new = palloc(N_FSTYPE, f_fstype);
+
+ switch (*arg) {
+ case 'l':
+ if (!strcmp(arg, "local")) {
+ new->flags = F_MTFLAG;
+ new->mt_data = MNT_LOCAL;
+ return (new);
+ }
+ break;
+ case 'r':
+ if (!strcmp(arg, "rdonly")) {
+ new->flags = F_MTFLAG;
+ new->mt_data = MNT_RDONLY;
+ return (new);
+ }
+ break;
+ }
+
+ new->flags = F_MTTYPE;
+ new->c_data = arg;
+ return (new);
+}
+
+/*
+ * -group gname functions --
+ *
+ * True if the file belongs to the group gname. If gname is numeric and
+ * an equivalent of the getgrnam() function does not return a valid group
+ * name, gname is taken as a group ID.
+ */
+int
+f_group(PLAN *plan, FTSENT *entry)
+{
+
+ COMPARE(entry->fts_statp->st_gid, plan->g_data);
+}
+
+PLAN *
+c_group(char ***argvp, int isok, char *opt)
+{
+ char *gname = **argvp;
+ PLAN *new;
+ struct group *g;
+ gid_t gid;
+
+ (*argvp)++;
+ ftsoptions &= ~FTS_NOSTAT;
+
+ new = palloc(N_GROUP, f_group);
+ g = getgrnam(gname);
+ if (g == NULL) {
+ if (atoi(gname) == 0 && gname[0] != '0' &&
+ strcmp(gname, "+0") && strcmp(gname, "-0"))
+ errx(1, "%s: %s: no such group", opt, gname);
+ gid = find_parsenum(new, "-group", gname, NULL);
+
+ } else {
+ new->flags = F_EQUAL;
+ gid = g->gr_gid;
+ }
+
+ new->g_data = gid;
+ return (new);
+}
+
+/*
+ * -inum n functions --
+ *
+ * True if the file has inode # n.
+ */
+int
+f_inum(PLAN *plan, FTSENT *entry)
+{
+
+ COMPARE(entry->fts_statp->st_ino, plan->i_data);
+}
+
+PLAN *
+c_inum(char ***argvp, int isok, char *opt)
+{
+ char *arg = **argvp;
+ PLAN *new;
+
+ (*argvp)++;
+ ftsoptions &= ~FTS_NOSTAT;
+
+ new = palloc(N_INUM, f_inum);
+ new->i_data = find_parsenum(new, opt, arg, NULL);
+ return (new);
+}
+
+/*
+ * -links n functions --
+ *
+ * True if the file has n links.
+ */
+int
+f_links(PLAN *plan, FTSENT *entry)
+{
+
+ COMPARE(entry->fts_statp->st_nlink, plan->l_data);
+}
+
+PLAN *
+c_links(char ***argvp, int isok, char *opt)
+{
+ char *arg = **argvp;
+ PLAN *new;
+
+ (*argvp)++;
+ ftsoptions &= ~FTS_NOSTAT;
+
+ new = palloc(N_LINKS, f_links);
+ new->l_data = (nlink_t)find_parsenum(new, opt, arg, NULL);
+ return (new);
+}
+
+/*
+ * -ls functions --
+ *
+ * Always true - prints the current entry to stdout in "ls" format.
+ */
+int
+f_ls(PLAN *plan, FTSENT *entry)
+{
+
+ printlong(entry->fts_path, entry->fts_accpath, entry->fts_statp);
+ return (1);
+}
+
+PLAN *
+c_ls(char ***argvp, int isok, char *opt)
+{
+
+ ftsoptions &= ~FTS_NOSTAT;
+ isoutput = 1;
+
+ return (palloc(N_LS, f_ls));
+}
+
+/*
+ * - maxdepth n functions --
+ *
+ * True if the current search depth is less than or equal to the
+ * maximum depth specified
+ */
+int
+f_maxdepth(PLAN *plan, FTSENT *entry)
+{
+ extern FTS *tree;
+
+ if (entry->fts_level >= plan->max_data)
+ fts_set(tree, entry, FTS_SKIP);
+ return (entry->fts_level <= plan->max_data);
+}
+
+PLAN *
+c_maxdepth(char ***argvp, int isok, char *opt)
+{
+ char *arg = **argvp;
+ PLAN *new;
+
+ (*argvp)++;
+ new = palloc(N_MAXDEPTH, f_maxdepth);
+ new->max_data = atoi(arg);
+ return (new);
+}
+
+/*
+ * - mindepth n functions --
+ *
+ * True if the current search depth is greater than or equal to the
+ * minimum depth specified
+ */
+int
+f_mindepth(PLAN *plan, FTSENT *entry)
+{
+ return (entry->fts_level >= plan->min_data);
+}
+
+PLAN *
+c_mindepth(char ***argvp, int isok, char *opt)
+{
+ char *arg = **argvp;
+ PLAN *new;
+
+ (*argvp)++;
+ new = palloc(N_MINDEPTH, f_mindepth);
+ new->min_data = atoi(arg);
+ return (new);
+}
+
+/*
+ * -mmin n functions --
+ *
+ * True if the difference between the file modification time and the
+ * current time is n 24 hour periods.
+ */
+int
+f_mmin(PLAN *plan, FTSENT *entry)
+{
+ COMPARE((now - entry->fts_statp->st_mtime + SECSPERMIN - 1) /
+ SECSPERMIN, plan->t_data);
+}
+
+PLAN *
+c_mmin(char ***argvp, int isok, char *opt)
+{
+ char *arg = **argvp;
+ PLAN *new;
+
+ (*argvp)++;
+ ftsoptions &= ~FTS_NOSTAT;
+
+ new = palloc(N_MMIN, f_mmin);
+ new->t_data = find_parsenum(new, opt, arg, NULL);
+ TIME_CORRECT(new, N_MMIN);
+ return (new);
+}
+
+/*
+ * -mtime n functions --
+ *
+ * True if the difference between the file modification time and the
+ * current time is n 24 hour periods.
+ */
+int
+f_mtime(PLAN *plan, FTSENT *entry)
+{
+ COMPARE((now - entry->fts_statp->st_mtime + SECSPERDAY - 1) /
+ SECSPERDAY, plan->t_data);
+}
+
+PLAN *
+c_mtime(char ***argvp, int isok, char *opt)
+{
+ char *arg = **argvp;
+ PLAN *new;
+
+ (*argvp)++;
+ ftsoptions &= ~FTS_NOSTAT;
+
+ new = palloc(N_MTIME, f_mtime);
+ new->t_data = find_parsenum(new, opt, arg, NULL);
+ TIME_CORRECT(new, N_MTIME);
+ return (new);
+}
+
+/*
+ * -name functions --
+ *
+ * True if the basename of the filename being examined
+ * matches pattern using Pattern Matching Notation S3.14
+ */
+int
+f_name(PLAN *plan, FTSENT *entry)
+{
+
+ return (!fnmatch(plan->c_data, entry->fts_name, 0));
+}
+
+PLAN *
+c_name(char ***argvp, int isok, char *opt)
+{
+ char *pattern = **argvp;
+ PLAN *new;
+
+ (*argvp)++;
+ new = palloc(N_NAME, f_name);
+ new->c_data = pattern;
+ return (new);
+}
+
+/*
+ * -iname functions --
+ *
+ * Similar to -name, but does case insensitive matching
+ *
+ */
+int
+f_iname(PLAN *plan, FTSENT *entry)
+{
+ return (!fnmatch(plan->c_data, entry->fts_name, FNM_CASEFOLD));
+}
+
+PLAN *
+c_iname(char ***argvp, int isok, char *opt)
+{
+ char *pattern = **argvp;
+ PLAN *new;
+
+ (*argvp)++;
+ new = palloc(N_INAME, f_iname);
+ new->c_data = pattern;
+ return (new);
+}
+
+/*
+ * -newer file functions --
+ *
+ * True if the current file has been modified more recently
+ * than the modification time of the file named by the pathname
+ * file.
+ */
+int
+f_newer(PLAN *plan, FTSENT *entry)
+{
+
+ return timespeccmp(&entry->fts_statp->st_mtim, &plan->ts_data, >);
+}
+
+PLAN *
+c_newer(char ***argvp, int isok, char *opt)
+{
+ char *filename = **argvp;
+ PLAN *new;
+ struct stat sb;
+
+ (*argvp)++;
+ ftsoptions &= ~FTS_NOSTAT;
+
+ if (stat(filename, &sb))
+ err(1, "%s: %s", opt, filename);
+ new = palloc(N_NEWER, f_newer);
+ new->ts_data = sb.st_mtim;
+ return (new);
+}
+
+/*
+ * -nogroup functions --
+ *
+ * True if file belongs to a user ID for which the equivalent
+ * of the getgrnam() 9.2.1 [POSIX.1] function returns NULL.
+ */
+int
+f_nogroup(PLAN *plan, FTSENT *entry)
+{
+
+ return (group_from_gid(entry->fts_statp->st_gid, 1) ? 0 : 1);
+}
+
+PLAN *
+c_nogroup(char ***argvp, int isok, char *opt)
+{
+ ftsoptions &= ~FTS_NOSTAT;
+
+ return (palloc(N_NOGROUP, f_nogroup));
+}
+
+/*
+ * -nouser functions --
+ *
+ * True if file belongs to a user ID for which the equivalent
+ * of the getpwuid() 9.2.2 [POSIX.1] function returns NULL.
+ */
+int
+f_nouser(PLAN *plan, FTSENT *entry)
+{
+
+ return (user_from_uid(entry->fts_statp->st_uid, 1) ? 0 : 1);
+}
+
+PLAN *
+c_nouser(char ***argvp, int isok, char *opt)
+{
+ ftsoptions &= ~FTS_NOSTAT;
+
+ return (palloc(N_NOUSER, f_nouser));
+}
+
+/*
+ * -path functions --
+ *
+ * True if the path of the filename being examined
+ * matches pattern using Pattern Matching Notation S3.14
+ */
+int
+f_path(PLAN *plan, FTSENT *entry)
+{
+
+ return (!fnmatch(plan->c_data, entry->fts_path, 0));
+}
+
+PLAN *
+c_path(char ***argvp, int isok, char *opt)
+{
+ char *pattern = **argvp;
+ PLAN *new;
+
+ (*argvp)++;
+ new = palloc(N_NAME, f_path);
+ new->c_data = pattern;
+ return (new);
+}
+
+/*
+ * -perm functions --
+ *
+ * The mode argument is used to represent file mode bits. If it starts
+ * with a leading digit, it's treated as an octal mode, otherwise as a
+ * symbolic mode.
+ */
+int
+f_perm(PLAN *plan, FTSENT *entry)
+{
+ mode_t mode;
+
+ mode = entry->fts_statp->st_mode &
+ (S_ISUID|S_ISGID|S_ISTXT|S_IRWXU|S_IRWXG|S_IRWXO);
+ if (plan->flags == F_ATLEAST)
+ return ((plan->m_data | mode) == mode);
+ else
+ return (mode == plan->m_data);
+ /* NOTREACHED */
+}
+
+PLAN *
+c_perm(char ***argvp, int isok, char *opt)
+{
+ char *perm = **argvp;
+ PLAN *new;
+ mode_t *set;
+
+ (*argvp)++;
+ ftsoptions &= ~FTS_NOSTAT;
+
+ new = palloc(N_PERM, f_perm);
+
+ if (*perm == '-') {
+ new->flags = F_ATLEAST;
+ ++perm;
+ }
+
+ if ((set = setmode(perm)) == NULL)
+ err(1, "%s: Cannot set file mode `%s'", opt, perm);
+
+ new->m_data = getmode(set, 0);
+ free(set);
+ return (new);
+}
+
+/*
+ * -print functions --
+ *
+ * Always true, causes the current pathame to be written to
+ * standard output.
+ */
+int
+f_print(PLAN *plan, FTSENT *entry)
+{
+
+ (void)printf("%s\n", entry->fts_path);
+ return (1);
+}
+
+int
+f_print0(PLAN *plan, FTSENT *entry)
+{
+
+ (void)fputs(entry->fts_path, stdout);
+ (void)fputc('\0', stdout);
+ return (1);
+}
+
+int
+f_printx(PLAN *plan, FTSENT *entry)
+{
+ char *cp;
+
+ for (cp = entry->fts_path; *cp; cp++) {
+ if (*cp == '\'' || *cp == '\"' || *cp == ' ' ||
+ *cp == '$' || *cp == '`' ||
+ *cp == '\t' || *cp == '\n' || *cp == '\\')
+ fputc('\\', stdout);
+
+ fputc(*cp, stdout);
+ }
+
+ fputc('\n', stdout);
+ return (1);
+}
+
+PLAN *
+c_print(char ***argvp, int isok, char *opt)
+{
+
+ isoutput = 1;
+
+ return (palloc(N_PRINT, f_print));
+}
+
+PLAN *
+c_print0(char ***argvp, int isok, char *opt)
+{
+
+ isoutput = 1;
+
+ return (palloc(N_PRINT0, f_print0));
+}
+
+PLAN *
+c_printx(char ***argvp, int isok, char *opt)
+{
+
+ isoutput = 1;
+
+ return (palloc(N_PRINTX, f_printx));
+}
+
+/*
+ * -prune functions --
+ *
+ * Prune a portion of the hierarchy.
+ */
+int
+f_prune(PLAN *plan, FTSENT *entry)
+{
+ if (fts_set(tree, entry, FTS_SKIP))
+ err(1, "%s", entry->fts_path);
+ return (1);
+}
+
+PLAN *
+c_prune(char ***argvp, int isok, char *opt)
+{
+
+ return (palloc(N_PRUNE, f_prune));
+}
+
+/*
+ * -regex regexp (and related) functions --
+ *
+ * True if the complete file path matches the regular expression regexp.
+ * For -regex, regexp is a case-sensitive (basic) regular expression.
+ * For -iregex, regexp is a case-insensitive (basic) regular expression.
+ */
+int
+f_regex(PLAN *plan, FTSENT *entry)
+{
+
+ return (regexec(&plan->regexp_data, entry->fts_path, 0, NULL, 0) == 0);
+}
+
+static PLAN *
+c_regex_common(char ***argvp, int isok, enum ntype type, bool icase)
+{
+ char errbuf[LINE_MAX];
+ regex_t reg;
+ char *regexp = **argvp;
+ char *lineregexp;
+ PLAN *new;
+ int rv;
+ size_t len;
+
+ (*argvp)++;
+
+ len = strlen(regexp) + 1 + 6;
+ lineregexp = malloc(len); /* max needed */
+ if (lineregexp == NULL)
+ err(1, NULL);
+ snprintf(lineregexp, len, "^%s(%s%s)$",
+ (regcomp_flags & REG_EXTENDED) ? "" : "\\", regexp,
+ (regcomp_flags & REG_EXTENDED) ? "" : "\\");
+ rv = regcomp(&reg, lineregexp, REG_NOSUB|regcomp_flags|
+ (icase ? REG_ICASE : 0));
+ free(lineregexp);
+ if (rv != 0) {
+ regerror(rv, &reg, errbuf, sizeof errbuf);
+ errx(1, "regexp %s: %s", regexp, errbuf);
+ }
+
+ new = palloc(type, f_regex);
+ new->regexp_data = reg;
+ return (new);
+}
+
+PLAN *
+c_regex(char ***argvp, int isok, char *opt)
+{
+
+ return (c_regex_common(argvp, isok, N_REGEX, false));
+}
+
+PLAN *
+c_iregex(char ***argvp, int isok, char *opt)
+{
+
+ return (c_regex_common(argvp, isok, N_IREGEX, true));
+}
+
+/*
+ * -since "timestamp" functions --
+ *
+ * True if the file modification time is greater than the timestamp value
+ */
+int
+f_since(PLAN *plan, FTSENT *entry)
+{
+ COMPARE(entry->fts_statp->st_mtime, plan->t_data);
+}
+
+PLAN *
+c_since(char ***argvp, int isok, char *opt)
+{
+ char *arg = **argvp;
+ PLAN *new;
+
+ (*argvp)++;
+ ftsoptions &= ~FTS_NOSTAT;
+
+ new = palloc(N_SINCE, f_since);
+ new->t_data = find_parsedate(new, opt, arg);
+ new->flags = F_GREATER;
+ return (new);
+}
+
+/*
+ * -size n[c] functions --
+ *
+ * True if the file size in bytes, divided by an implementation defined
+ * value and rounded up to the next integer, is n. If n is followed by
+ * a c, the size is in bytes.
+ */
+#define FIND_SIZE 512
+static int divsize = 1;
+
+int
+f_size(PLAN *plan, FTSENT *entry)
+{
+ off_t size;
+
+ size = divsize ? (entry->fts_statp->st_size + FIND_SIZE - 1) /
+ FIND_SIZE : entry->fts_statp->st_size;
+ COMPARE(size, plan->o_data);
+}
+
+PLAN *
+c_size(char ***argvp, int isok, char *opt)
+{
+ char *arg = **argvp;
+ PLAN *new;
+ char endch;
+
+ (*argvp)++;
+ ftsoptions &= ~FTS_NOSTAT;
+
+ new = palloc(N_SIZE, f_size);
+ endch = 'c';
+ new->o_data = find_parsenum(new, opt, arg, &endch);
+ if (endch == 'c')
+ divsize = 0;
+ return (new);
+}
+
+/*
+ * -type c functions --
+ *
+ * True if the type of the file is c, where c is b, c, d, p, f or w
+ * for block special file, character special file, directory, FIFO,
+ * regular file or whiteout respectively.
+ */
+int
+f_type(PLAN *plan, FTSENT *entry)
+{
+
+ return ((entry->fts_statp->st_mode & S_IFMT) == plan->m_data);
+}
+
+PLAN *
+c_type(char ***argvp, int isok, char *opt)
+{
+ char *typestring = **argvp;
+ PLAN *new;
+ mode_t mask = (mode_t)0;
+
+ (*argvp)++;
+ ftsoptions &= ~FTS_NOSTAT;
+
+ switch (typestring[0]) {
+ case 'b':
+ mask = S_IFBLK;
+ break;
+ case 'c':
+ mask = S_IFCHR;
+ break;
+ case 'd':
+ mask = S_IFDIR;
+ break;
+ case 'f':
+ mask = S_IFREG;
+ break;
+ case 'l':
+ mask = S_IFLNK;
+ break;
+ case 'p':
+ mask = S_IFIFO;
+ break;
+ case 's':
+ mask = S_IFSOCK;
+ break;
+#ifdef S_IFWHT
+ case 'W':
+ case 'w':
+ mask = S_IFWHT;
+#ifdef FTS_WHITEOUT
+ ftsoptions |= FTS_WHITEOUT;
+#endif
+ break;
+#endif /* S_IFWHT */
+ default:
+ errx(1, "%s: %s: unknown type", opt, typestring);
+ }
+
+ new = palloc(N_TYPE, f_type);
+ new->m_data = mask;
+ return (new);
+}
+
+/*
+ * -user uname functions --
+ *
+ * True if the file belongs to the user uname. If uname is numeric and
+ * an equivalent of the getpwnam() S9.2.2 [POSIX.1] function does not
+ * return a valid user name, uname is taken as a user ID.
+ */
+int
+f_user(PLAN *plan, FTSENT *entry)
+{
+
+ COMPARE(entry->fts_statp->st_uid, plan->u_data);
+}
+
+PLAN *
+c_user(char ***argvp, int isok, char *opt)
+{
+ char *username = **argvp;
+ PLAN *new;
+ struct passwd *p;
+ uid_t uid;
+
+ (*argvp)++;
+ ftsoptions &= ~FTS_NOSTAT;
+
+ new = palloc(N_USER, f_user);
+ p = getpwnam(username);
+ if (p == NULL) {
+ if (atoi(username) == 0 && username[0] != '0' &&
+ strcmp(username, "+0") && strcmp(username, "-0"))
+ errx(1, "%s: %s: no such user", opt, username);
+ uid = find_parsenum(new, opt, username, NULL);
+
+ } else {
+ new->flags = F_EQUAL;
+ uid = p->pw_uid;
+ }
+
+ new->u_data = uid;
+ return (new);
+}
+
+/*
+ * -xdev functions --
+ *
+ * Always true, causes find not to descend past directories that have a
+ * different device ID (st_dev, see stat() S5.6.2 [POSIX.1])
+ */
+PLAN *
+c_xdev(char ***argvp, int isok, char *opt)
+{
+ ftsoptions |= FTS_XDEV;
+
+ return (palloc(N_XDEV, f_always_true));
+}
+
+/*
+ * ( expression ) functions --
+ *
+ * True if expression is true.
+ */
+int
+f_expr(PLAN *plan, FTSENT *entry)
+{
+ PLAN *p;
+ int state;
+
+ state = 0;
+ for (p = plan->p_data[0];
+ p && (state = (p->eval)(p, entry)); p = p->next);
+ return (state);
+}
+
+/*
+ * N_OPENPAREN and N_CLOSEPAREN nodes are temporary place markers. They are
+ * eliminated during phase 2 of find_formplan() --- the '(' node is converted
+ * to a N_EXPR node containing the expression and the ')' node is discarded.
+ */
+PLAN *
+c_openparen(char ***argvp, int isok, char *opt)
+{
+
+ return (palloc(N_OPENPAREN, (int (*)(PLAN *, FTSENT *))-1));
+}
+
+PLAN *
+c_closeparen(char ***argvp, int isok, char *opt)
+{
+
+ return (palloc(N_CLOSEPAREN, (int (*)(PLAN *, FTSENT *))-1));
+}
+
+/*
+ * ! expression functions --
+ *
+ * Negation of a primary; the unary NOT operator.
+ */
+int
+f_not(PLAN *plan, FTSENT *entry)
+{
+ PLAN *p;
+ int state;
+
+ state = 0;
+ for (p = plan->p_data[0];
+ p && (state = (p->eval)(p, entry)); p = p->next);
+ return (!state);
+}
+
+PLAN *
+c_not(char ***argvp, int isok, char *opt)
+{
+
+ return (palloc(N_NOT, f_not));
+}
+
+/*
+ * expression -o expression functions --
+ *
+ * Alternation of primaries; the OR operator. The second expression is
+ * not evaluated if the first expression is true.
+ */
+int
+f_or(PLAN *plan, FTSENT *entry)
+{
+ PLAN *p;
+ int state;
+
+ state = 0;
+ for (p = plan->p_data[0];
+ p && (state = (p->eval)(p, entry)); p = p->next);
+
+ if (state)
+ return (1);
+
+ for (p = plan->p_data[1];
+ p && (state = (p->eval)(p, entry)); p = p->next);
+ return (state);
+}
+
+PLAN *
+c_or(char ***argvp, int isok, char *opt)
+{
+
+ return (palloc(N_OR, f_or));
+}
+
+PLAN *
+c_null(char ***argvp, int isok, char *opt)
+{
+
+ return (NULL);
+}
+
+
+/*
+ * plan_cleanup --
+ * Check and see if the specified plan has any residual state,
+ * and if so, clean it up as appropriate.
+ *
+ * At the moment, only N_EXEC has state. Two kinds: 1)
+ * lists of files to feed to subprocesses 2) State on exit
+ * statusses of past subprocesses.
+ */
+/* ARGSUSED1 */
+int
+plan_cleanup(PLAN *plan, void *arg)
+{
+ if (plan->type==N_EXEC && plan->ep_narg)
+ run_f_exec(plan);
+
+ return plan->ep_rval; /* Passed save exit-status up chain */
+}
+
+static PLAN *
+palloc(enum ntype t, int (*f)(PLAN *, FTSENT *))
+{
+ PLAN *new;
+
+ if ((new = malloc(sizeof(PLAN))) == NULL)
+ err(1, NULL);
+ memset(new, 0, sizeof(PLAN));
+ new->type = t;
+ new->eval = f;
+ return (new);
+}
diff --git a/usr.bin/find/ls.c b/usr.bin/find/ls.c
new file mode 100644
index 0000000..23b7cfe
--- /dev/null
+++ b/usr.bin/find/ls.c
@@ -0,0 +1,124 @@
+/* $NetBSD: ls.c,v 1.21 2011/08/31 16:24:57 plunky Exp $ */
+
+/*
+ * Copyright (c) 1989, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+#ifndef lint
+#if 0
+static char sccsid[] = "from: @(#)ls.c 8.1 (Berkeley) 6/6/93";
+#else
+__RCSID("$NetBSD: ls.c,v 1.21 2011/08/31 16:24:57 plunky Exp $");
+#endif
+#endif /* not lint */
+
+#include <sys/param.h>
+#include <sys/stat.h>
+
+#include <err.h>
+#include <errno.h>
+#include <fts.h>
+#include <grp.h>
+#include <pwd.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <tzfile.h>
+#include <unistd.h>
+
+#include "find.h"
+
+/* Derived from the print routines in the ls(1) source code. */
+
+static void printlink(char *);
+static void printtime(time_t);
+
+void
+printlong(char *name, /* filename to print */
+ char *accpath, /* current valid path to filename */
+ struct stat *sb) /* stat buffer */
+{
+ char modep[15];
+
+ (void)printf("%7lu %6lld ", (u_long)sb->st_ino,
+ (long long)sb->st_blocks);
+ (void)strmode(sb->st_mode, modep);
+ (void)printf("%s %3lu %-*s %-*s ", modep, (unsigned long)sb->st_nlink,
+ LOGIN_NAME_MAX, user_from_uid(sb->st_uid, 0), LOGIN_NAME_MAX,
+ group_from_gid(sb->st_gid, 0));
+
+ if (S_ISCHR(sb->st_mode) || S_ISBLK(sb->st_mode))
+ (void)printf("%3llu,%5llu ",
+ (unsigned long long)major(sb->st_rdev),
+ (unsigned long long)minor(sb->st_rdev));
+ else
+ (void)printf("%9lld ", (long long)sb->st_size);
+ printtime(sb->st_mtime);
+ (void)printf("%s", name);
+ if (S_ISLNK(sb->st_mode))
+ printlink(accpath);
+ (void)putchar('\n');
+}
+
+static void
+printtime(time_t ftime)
+{
+ int i;
+ char *longstring;
+
+ longstring = ctime(&ftime);
+ for (i = 4; i < 11; ++i)
+ (void)putchar(longstring[i]);
+
+#define SIXMONTHS ((DAYSPERNYEAR / 2) * SECSPERDAY)
+ if (ftime + SIXMONTHS > time(NULL))
+ for (i = 11; i < 16; ++i)
+ (void)putchar(longstring[i]);
+ else {
+ (void)putchar(' ');
+ for (i = 20; i < 24; ++i)
+ (void)putchar(longstring[i]);
+ }
+ (void)putchar(' ');
+}
+
+static void
+printlink(char *name)
+{
+ int lnklen;
+ char path[MAXPATHLEN + 1];
+
+ if ((lnklen = readlink(name, path, sizeof(path) - 1)) == -1) {
+ warn("%s", name);
+ return;
+ }
+ path[lnklen] = '\0';
+ (void)printf(" -> %s", path);
+}
diff --git a/usr.bin/find/main.c b/usr.bin/find/main.c
new file mode 100644
index 0000000..5aab177
--- /dev/null
+++ b/usr.bin/find/main.c
@@ -0,0 +1,164 @@
+/* $NetBSD: main.c,v 1.31 2013/01/24 17:50:08 christos Exp $ */
+
+/*-
+ * Copyright (c) 1990, 1993, 1994
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Cimarron D. Taylor of the University of California, Berkeley.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+#ifndef lint
+#if 0
+static char sccsid[] = "@(#)main.c 8.4 (Berkeley) 5/4/95";
+#else
+__COPYRIGHT("@(#) Copyright (c) 1990, 1993, 1994\
+ The Regents of the University of California. All rights reserved.");
+__RCSID("$NetBSD: main.c,v 1.31 2013/01/24 17:50:08 christos Exp $");
+#endif
+#endif /* not lint */
+
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#include <err.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <fts.h>
+#include <locale.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <unistd.h>
+
+#include "find.h"
+
+time_t now; /* time find was run */
+int dotfd; /* starting directory */
+int ftsoptions; /* options for the ftsopen(3) call */
+int isdeprecated; /* using deprecated syntax */
+int isdepth; /* do directories on post-order visit */
+int isoutput; /* user specified output operator */
+int issort; /* sort directory entries */
+int isxargs; /* don't permit xargs delimiting chars */
+int regcomp_flags = REG_BASIC; /* regex compilation flags */
+
+__dead static void usage(void);
+
+int
+main(int argc, char *argv[])
+{
+ char **p, **start;
+ int ch;
+
+ (void)time(&now); /* initialize the time-of-day */
+ (void)setlocale(LC_ALL, "");
+
+ /* array to hold dir list. at most (argc - 1) elements. */
+ p = start = malloc(argc * sizeof (char *));
+ if (p == NULL)
+ err(1, NULL);
+
+ ftsoptions = FTS_NOSTAT | FTS_PHYSICAL;
+ while ((ch = getopt(argc, argv, "HLPdEf:hsXx")) != -1)
+ switch (ch) {
+ case 'H':
+ ftsoptions &= ~FTS_LOGICAL;
+ ftsoptions |= FTS_PHYSICAL|FTS_COMFOLLOW;
+ break;
+ case 'L':
+ ftsoptions &= ~(FTS_COMFOLLOW|FTS_PHYSICAL);
+ ftsoptions |= FTS_LOGICAL;
+ break;
+ case 'P':
+ ftsoptions &= ~(FTS_COMFOLLOW|FTS_LOGICAL);
+ ftsoptions |= FTS_PHYSICAL;
+ break;
+ case 'd':
+ isdepth = 1;
+ break;
+ case 'E':
+ regcomp_flags = REG_EXTENDED;
+ break;
+ case 'f':
+ *p++ = optarg;
+ break;
+ case 'h':
+ ftsoptions &= ~FTS_PHYSICAL;
+ ftsoptions |= FTS_LOGICAL;
+ break;
+ case 's':
+ issort = 1;
+ break;
+ case 'X':
+ isxargs = 1;
+ break;
+ case 'x':
+ ftsoptions |= FTS_XDEV;
+ break;
+ case '?':
+ default:
+ break;
+ }
+
+ argc -= optind;
+ argv += optind;
+
+ /*
+ * Find first option to delimit the file list. The first argument
+ * that starts with a -, or is a ! or a ( must be interpreted as a
+ * part of the find expression, according to POSIX .2.
+ */
+ for (; *argv != NULL; *p++ = *argv++) {
+ if (argv[0][0] == '-')
+ break;
+ if ((argv[0][0] == '!' || argv[0][0] == '(') &&
+ argv[0][1] == '\0')
+ break;
+ }
+
+ if (p == start)
+ usage();
+
+ *p = NULL;
+
+ if ((dotfd = open(".", O_RDONLY | O_CLOEXEC, 0)) == -1)
+ err(1, ".");
+
+ exit(find_execute(find_formplan(argv), start));
+}
+
+static void
+usage(void)
+{
+
+ (void)fprintf(stderr, "Usage: %s [-H | -L | -P] [-dEhsXx] [-f file] "
+ "file [file ...] [expression]\n", getprogname());
+ exit(1);
+}
diff --git a/usr.bin/find/misc.c b/usr.bin/find/misc.c
new file mode 100644
index 0000000..8ba84c0
--- /dev/null
+++ b/usr.bin/find/misc.c
@@ -0,0 +1,151 @@
+/* $NetBSD: misc.c,v 1.14 2006/10/11 19:51:10 apb Exp $ */
+
+/*-
+ * Copyright (c) 1990, 1993, 1994
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Cimarron D. Taylor of the University of California, Berkeley.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+#ifndef lint
+#if 0
+static char sccsid[] = "from: @(#)misc.c 8.2 (Berkeley) 4/1/94";
+#else
+__RCSID("$NetBSD: misc.c,v 1.14 2006/10/11 19:51:10 apb Exp $");
+#endif
+#endif /* not lint */
+
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#include <err.h>
+#include <errno.h>
+#include <fts.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "find.h"
+
+/*
+ * brace_subst --
+ * Replace occurrences of {} in orig with path, and place it in a malloced
+ * area of memory set in store.
+ */
+void
+brace_subst(char *orig, char **store, char *path, int *len)
+{
+ int nlen, plen, rest;
+ char ch, *p, *ostore;
+
+ plen = strlen(path);
+ for (p = *store; (ch = *orig) != '\0'; ++orig)
+ if (ch == '{' && orig[1] == '}') {
+ /* Length of string after the {}. */
+ rest = strlen(&orig[2]);
+
+ nlen = *len;
+ while ((p - *store) + plen + rest + 1 > nlen)
+ nlen *= 2;
+
+ if (nlen > *len) {
+ ostore = *store;
+ if ((*store = realloc(ostore, nlen)) == NULL)
+ err(1, "realloc");
+ *len = nlen;
+ p += *store - ostore; /* Relocate. */
+ }
+ memmove(p, path, plen);
+ p += plen;
+ ++orig;
+ } else
+ *p++ = ch;
+ *p = '\0';
+}
+
+/*
+ * queryuser --
+ * print a message to standard error and then read input from standard
+ * input. If the input is 'y' then 1 is returned.
+ */
+int
+queryuser(char **argv)
+{
+ int ch, first, nl;
+
+ (void)fprintf(stderr, "\"%s", *argv);
+ while (*++argv)
+ (void)fprintf(stderr, " %s", *argv);
+ (void)fprintf(stderr, "\"? ");
+ (void)fflush(stderr);
+
+ first = ch = getchar();
+ for (nl = 0;;) {
+ if (ch == '\n') {
+ nl = 1;
+ break;
+ }
+ if (ch == EOF)
+ break;
+ ch = getchar();
+ }
+
+ if (!nl) {
+ (void)fprintf(stderr, "\n");
+ (void)fflush(stderr);
+ }
+ return (first == 'y');
+}
+
+/*
+ * show_path --
+ * called on SIGINFO
+ */
+/* ARGSUSED */
+void
+show_path(int sig)
+{
+ extern FTSENT *g_entry;
+ int errno_bak;
+
+ if (g_entry == NULL) {
+ /*
+ * not initialized yet.
+ * assumption: pointer assignment is atomic.
+ */
+ return;
+ }
+
+ errno_bak = errno;
+ write(STDERR_FILENO, "find path: ", 11);
+ write(STDERR_FILENO, g_entry->fts_path, g_entry->fts_pathlen);
+ write(STDERR_FILENO, "\n", 1);
+ errno = errno_bak;
+}
diff --git a/usr.bin/find/operator.c b/usr.bin/find/operator.c
new file mode 100644
index 0000000..63a6aa4
--- /dev/null
+++ b/usr.bin/find/operator.c
@@ -0,0 +1,272 @@
+/* $NetBSD: operator.c,v 1.10 2014/10/18 08:33:30 snj Exp $ */
+
+/*-
+ * Copyright (c) 1990, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Cimarron D. Taylor of the University of California, Berkeley.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+#ifndef lint
+#if 0
+static char sccsid[] = "from: @(#)operator.c 8.1 (Berkeley) 6/6/93";
+#else
+__RCSID("$NetBSD: operator.c,v 1.10 2014/10/18 08:33:30 snj Exp $");
+#endif
+#endif /* not lint */
+
+#include <sys/types.h>
+
+#include <err.h>
+#include <fts.h>
+#include <stdio.h>
+
+#include "find.h"
+
+static PLAN *yanknode(PLAN **);
+static PLAN *yankexpr(PLAN **);
+
+/*
+ * yanknode --
+ * destructively removes the top from the plan
+ */
+static PLAN *
+yanknode(PLAN **planp) /* pointer to top of plan (modified) */
+{
+ PLAN *node; /* top node removed from the plan */
+
+ if ((node = (*planp)) == NULL)
+ return (NULL);
+ (*planp) = (*planp)->next;
+ node->next = NULL;
+ return (node);
+}
+
+/*
+ * yankexpr --
+ * Removes one expression from the plan. This is used mainly by
+ * paren_squish. In comments below, an expression is either a
+ * simple node or a N_EXPR node containing a list of simple nodes.
+ */
+static PLAN *
+yankexpr(PLAN **planp) /* pointer to top of plan (modified) */
+{
+ PLAN *next; /* temp node holding subexpression results */
+ PLAN *node; /* pointer to returned node or expression */
+ PLAN *tail; /* pointer to tail of subplan */
+ PLAN *subplan; /* pointer to head of ( ) expression */
+
+ /* first pull the top node from the plan */
+ if ((node = yanknode(planp)) == NULL)
+ return (NULL);
+
+ /*
+ * If the node is an '(' then we recursively slurp up expressions
+ * until we find its associated ')'. If it's a closing paren we
+ * just return it and unwind our recursion; all other nodes are
+ * complete expressions, so just return them.
+ */
+ if (node->type == N_OPENPAREN)
+ for (tail = subplan = NULL;;) {
+ if ((next = yankexpr(planp)) == NULL)
+ err(1, "(: missing closing ')'");
+ /*
+ * If we find a closing ')' we store the collected
+ * subplan in our '(' node and convert the node to
+ * a N_EXPR. The ')' we found is ignored. Otherwise,
+ * we just continue to add whatever we get to our
+ * subplan.
+ */
+ if (next->type == N_CLOSEPAREN) {
+ if (subplan == NULL)
+ errx(1, "(): empty inner expression");
+ node->p_data[0] = subplan;
+ node->type = N_EXPR;
+ node->eval = f_expr;
+ break;
+ } else {
+ if (subplan == NULL)
+ tail = subplan = next;
+ else {
+ tail->next = next;
+ tail = next;
+ }
+ tail->next = NULL;
+ }
+ }
+ return (node);
+}
+
+/*
+ * paren_squish --
+ * replaces "parentheisized" plans in our search plan with "expr" nodes.
+ */
+PLAN *
+paren_squish(PLAN *plan) /* plan with ( ) nodes */
+{
+ PLAN *expr; /* pointer to next expression */
+ PLAN *tail; /* pointer to tail of result plan */
+ PLAN *result; /* pointer to head of result plan */
+
+ result = tail = NULL;
+
+ /*
+ * the basic idea is to have yankexpr do all our work and just
+ * collect its results together.
+ */
+ while ((expr = yankexpr(&plan)) != NULL) {
+ /*
+ * if we find an unclaimed ')' it means there is a missing
+ * '(' someplace.
+ */
+ if (expr->type == N_CLOSEPAREN)
+ errx(1, "): no beginning '('");
+
+ /* add the expression to our result plan */
+ if (result == NULL)
+ tail = result = expr;
+ else {
+ tail->next = expr;
+ tail = expr;
+ }
+ tail->next = NULL;
+ }
+ return (result);
+}
+
+/*
+ * not_squish --
+ * compresses "!" expressions in our search plan.
+ */
+PLAN *
+not_squish(PLAN *plan) /* plan to process */
+{
+ PLAN *next; /* next node being processed */
+ PLAN *node; /* temporary node used in N_NOT processing */
+ PLAN *tail; /* pointer to tail of result plan */
+ PLAN *result; /* pointer to head of result plan */
+
+ tail = result = next = NULL;
+
+ while ((next = yanknode(&plan)) != NULL) {
+ /*
+ * if we encounter a ( expression ) then look for nots in
+ * the expr subplan.
+ */
+ if (next->type == N_EXPR)
+ next->p_data[0] = not_squish(next->p_data[0]);
+
+ /*
+ * if we encounter a not, then snag the next node and place
+ * it in the not's subplan. As an optimization we compress
+ * several not's to zero or one not.
+ */
+ if (next->type == N_NOT) {
+ int notlevel = 1;
+
+ node = yanknode(&plan);
+ while (node != NULL && node->type == N_NOT) {
+ ++notlevel;
+ node = yanknode(&plan);
+ }
+ if (node == NULL)
+ errx(1, "!: no following expression");
+ if (node->type == N_OR)
+ errx(1, "!: nothing between ! and -o");
+ if (node->type == N_EXPR)
+ node = not_squish(node);
+ if (notlevel % 2 != 1)
+ next = node;
+ else
+ next->p_data[0] = node;
+ }
+
+ /* add the node to our result plan */
+ if (result == NULL)
+ tail = result = next;
+ else {
+ tail->next = next;
+ tail = next;
+ }
+ tail->next = NULL;
+ }
+ return (result);
+}
+
+/*
+ * or_squish --
+ * compresses -o expressions in our search plan.
+ */
+PLAN *
+or_squish(PLAN *plan) /* plan with ors to be squished */
+{
+ PLAN *next; /* next node being processed */
+ PLAN *tail; /* pointer to tail of result plan */
+ PLAN *result; /* pointer to head of result plan */
+
+ tail = result = next = NULL;
+
+ while ((next = yanknode(&plan)) != NULL) {
+ /*
+ * if we encounter a ( expression ) then look for or's in
+ * the expr subplan.
+ */
+ if (next->type == N_EXPR)
+ next->p_data[0] = or_squish(next->p_data[0]);
+
+ /* if we encounter a not then look for not's in the subplan */
+ if (next->type == N_NOT)
+ next->p_data[0] = or_squish(next->p_data[0]);
+
+ /*
+ * if we encounter an or, then place our collected plan in the
+ * or's first subplan and then recursively collect the
+ * remaining stuff into the second subplan and return the or.
+ */
+ if (next->type == N_OR) {
+ if (result == NULL)
+ errx(1, "-o: no expression before -o");
+ next->p_data[0] = result;
+ next->p_data[1] = or_squish(plan);
+ if (next->p_data[1] == NULL)
+ errx(1, "-o: no expression after -o");
+ return (next);
+ }
+
+ /* add the node to our result plan */
+ if (result == NULL)
+ tail = result = next;
+ else {
+ tail->next = next;
+ tail = next;
+ }
+ tail->next = NULL;
+ }
+ return (result);
+}
diff --git a/usr.bin/find/option.c b/usr.bin/find/option.c
new file mode 100644
index 0000000..0af4447
--- /dev/null
+++ b/usr.bin/find/option.c
@@ -0,0 +1,194 @@
+/* $NetBSD: option.c,v 1.27 2016/06/13 00:04:40 pgoyette Exp $ */
+
+/*-
+ * Copyright (c) 1990, 1993, 1994
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Cimarron D. Taylor of the University of California, Berkeley.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+#ifndef lint
+#if 0
+static char sccsid[] = "from: @(#)option.c 8.2 (Berkeley) 4/16/94";
+#else
+__RCSID("$NetBSD: option.c,v 1.27 2016/06/13 00:04:40 pgoyette Exp $");
+#endif
+#endif /* not lint */
+
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#include <err.h>
+#include <fts.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "find.h"
+
+int typecompare(const void *, const void *);
+static OPTION *option(char *);
+
+/* NB: the following table must be sorted lexically. */
+static OPTION const options[] = {
+ { "!", N_NOT, c_not, 0 },
+ { "(", N_OPENPAREN, c_openparen, 0 },
+ { ")", N_CLOSEPAREN, c_closeparen, 0 },
+ { "-a", N_AND, c_null, 0 },
+ { "-amin", N_AMIN, c_amin, 1 },
+ { "-and", N_AND, c_null, 0 },
+ { "-anewer", N_ANEWER, c_anewer, 1 },
+ { "-asince", N_ASINCE, c_asince, 1 },
+ { "-atime", N_ATIME, c_atime, 1 },
+ { "-cmin", N_CMIN, c_cmin, 1 },
+ { "-cnewer", N_CNEWER, c_cnewer, 1 },
+ { "-csince", N_CSINCE, c_csince, 1 },
+ { "-ctime", N_CTIME, c_ctime, 1 },
+ { "-delete", N_DELETE, c_delete, 0 },
+ { "-depth", N_DEPTH, c_depth, 0 },
+ { "-empty", N_EMPTY, c_empty, 0 },
+ { "-exec", N_EXEC, c_exec, 1 },
+ { "-execdir", N_EXECDIR, c_execdir, 1 },
+ { "-exit", N_EXIT, c_exit, 0 },
+ { "-false", N_FALSE, c_false, 0 },
+ { "-flags", N_FLAGS, c_flags, 1 },
+ { "-follow", N_FOLLOW, c_follow, 0 },
+ { "-fprint", N_FPRINT, c_fprint, 1 },
+ { "-fstype", N_FSTYPE, c_fstype, 1 },
+ { "-group", N_GROUP, c_group, 1 },
+ { "-iname", N_INAME, c_iname, 1 },
+ { "-inum", N_INUM, c_inum, 1 },
+ { "-iregex", N_IREGEX, c_iregex, 1 },
+ { "-links", N_LINKS, c_links, 1 },
+ { "-ls", N_LS, c_ls, 0 },
+ { "-maxdepth", N_MAXDEPTH, c_maxdepth, 1 },
+ { "-mindepth", N_MINDEPTH, c_mindepth, 1 },
+ { "-mmin", N_MMIN, c_mmin, 1 },
+ { "-mtime", N_MTIME, c_mtime, 1 },
+ { "-name", N_NAME, c_name, 1 },
+ { "-newer", N_NEWER, c_newer, 1 },
+
+/* Aliases for compatability with Gnu findutils */
+ { "-neweraa", N_ANEWER, c_anewer, 1 },
+ { "-newerat", N_ASINCE, c_asince, 1 },
+ { "-newercc", N_CNEWER, c_cnewer, 1 },
+ { "-newerct", N_CSINCE, c_csince, 1 },
+ { "-newermm", N_NEWER, c_newer, 1 },
+ { "-newermt", N_SINCE, c_since, 1 },
+
+/*
+ * Unimplemented Gnu findutils options
+ *
+ * If you implement any of these, be sure to re-sort the table
+ * in ascii(7) order!
+ *
+ { "-newerBB", N_UNIMPL, c_unimpl, 1 },
+ { "-newerBa", N_UNIMPL, c_unimpl, 1 },
+ { "-newerBc", N_UNIMPL, c_unimpl, 1 },
+ { "-newerBm", N_UNIMPL, c_unimpl, 1 },
+ { "-newerBt", N_UNIMPL, c_unimpl, 1 },
+ { "-neweraB", N_UNIMPL, c_unimpl, 1 },
+ { "-newerac", N_UNIMPL, c_unimpl, 1 },
+ { "-neweram", N_UNIMPL, c_unimpl, 1 },
+ { "-newerca", N_UNIMPL, c_unimpl, 1 },
+ { "-newercm", N_UNIMPL, c_unimpl, 1 },
+ { "-newercB", N_UNIMPL, c_unimpl, 1 },
+ { "-newermB", N_UNIMPL, c_unimpl, 1 },
+ { "-newerma", N_UNIMPL, c_unimpl, 1 },
+ { "-newermc", N_UNIMPL, c_unimpl, 1 },
+ *
+ */
+
+ { "-nogroup", N_NOGROUP, c_nogroup, 0 },
+ { "-nouser", N_NOUSER, c_nouser, 0 },
+ { "-o", N_OR, c_or, 0 },
+ { "-ok", N_OK, c_exec, 1 },
+ { "-or", N_OR, c_or, 0 },
+ { "-path", N_PATH, c_path, 1 },
+ { "-perm", N_PERM, c_perm, 1 },
+ { "-print", N_PRINT, c_print, 0 },
+ { "-print0", N_PRINT0, c_print0, 0 },
+ { "-printx", N_PRINTX, c_printx, 0 },
+ { "-prune", N_PRUNE, c_prune, 0 },
+ { "-regex", N_REGEX, c_regex, 1 },
+ { "-rm", N_DELETE, c_delete, 0 },
+ { "-since", N_SINCE, c_since, 1 },
+ { "-size", N_SIZE, c_size, 1 },
+ { "-type", N_TYPE, c_type, 1 },
+ { "-user", N_USER, c_user, 1 },
+ { "-xdev", N_XDEV, c_xdev, 0 }
+};
+
+/*
+ * find_create --
+ * create a node corresponding to a command line argument.
+ *
+ * TODO:
+ * add create/process function pointers to node, so we can skip
+ * this switch stuff.
+ */
+PLAN *
+find_create(char ***argvp)
+{
+ OPTION *p;
+ PLAN *new;
+ char **argv;
+ char *opt;
+
+ argv = *argvp;
+ opt = *argv;
+
+ if ((p = option(opt)) == NULL)
+ errx(1, "%s: unknown option", opt);
+ ++argv;
+ if (p->arg && !*argv)
+ errx(1, "%s: requires additional arguments", opt);
+
+ new = (p->create)(&argv, p->token == N_OK, opt);
+
+ *argvp = argv;
+ return (new);
+}
+
+static OPTION *
+option(char *name)
+{
+ OPTION tmp;
+
+ tmp.name = name;
+ return ((OPTION *)bsearch(&tmp, options,
+ sizeof(options)/sizeof(OPTION), sizeof(OPTION), typecompare));
+}
+
+int
+typecompare(const void *a, const void *b)
+{
+
+ return (strcmp(((const OPTION *)a)->name, ((const OPTION *)b)->name));
+}
diff --git a/usr.bin/fold/fold.1 b/usr.bin/fold/fold.1
new file mode 100644
index 0000000..8f3f6a1
--- /dev/null
+++ b/usr.bin/fold/fold.1
@@ -0,0 +1,93 @@
+.\" $NetBSD: fold.1,v 1.17 2012/05/12 15:17:15 wiz Exp $
+.\"
+.\" Copyright (c) 1980, 1993
+.\" The Regents of the University of California. All rights reserved.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\" notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must reproduce the above copyright
+.\" notice, this list of conditions and the following disclaimer in the
+.\" documentation and/or other materials provided with the distribution.
+.\" 3. Neither the name of the University nor the names of its contributors
+.\" may be used to endorse or promote products derived from this software
+.\" without specific prior written permission.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+.\" ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+.\" SUCH DAMAGE.
+.\"
+.\" @(#)fold.1 8.1 (Berkeley) 6/6/93
+.\"
+.Dd May 12, 2012
+.Dt FOLD 1
+.Os
+.Sh NAME
+.Nm fold
+.Nd "fold long lines for finite width output device"
+.Sh SYNOPSIS
+.Nm
+.Op Fl bs
+.Op Fl w Ar width
+.Op Ar
+.Sh DESCRIPTION
+.Nm
+is a filter which folds the contents of the specified files,
+or the standard input if no files are specified,
+breaking the lines to have a maximum of 80 characters.
+.Pp
+The options are as follows:
+.Bl -tag -width XwXwidthXX
+.It Fl b
+Count
+.Ar width
+in bytes rather than column positions.
+.It Fl s
+Fold line after the last blank character within the first
+.Ar width
+column positions (or bytes).
+If a blank character does not exist within the width, then
+a longer line will still be split at the width.
+.It Fl w Ar width
+Specifies
+.Ar width
+to use as a line width, instead of the default 80 characters.
+.El
+.Sh ENVIRONMENT
+.Bl -tag -width indent
+.It Ev LC_CTYPE
+.El
+.Sh EXIT STATUS
+.Ex -std
+.Sh SEE ALSO
+.Xr expand 1 ,
+.Xr fmt 1
+.Sh STANDARDS
+The
+.Nm
+utility conforms to
+.St -p1003.1-2008 .
+.Sh HISTORY
+The
+.Nm
+utility appeared in
+.Bx 1 .
+.Sh BUGS
+If underlining is present it may be messed up by folding.
+.Pp
+.Ar Width
+should be a multiple of 8 if tabs are present, or the tabs should
+be expanded using
+.Xr expand 1
+before using
+.Nm .
diff --git a/usr.bin/fold/fold.c b/usr.bin/fold/fold.c
new file mode 100644
index 0000000..52ecc3d
--- /dev/null
+++ b/usr.bin/fold/fold.c
@@ -0,0 +1,248 @@
+/* $NetBSD: fold.c,v 1.17 2011/09/04 20:24:59 joerg Exp $ */
+
+/*-
+ * Copyright (c) 1990, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Kevin Ruddy.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+#ifndef lint
+__COPYRIGHT("@(#) Copyright (c) 1990, 1993\
+ The Regents of the University of California. All rights reserved.");
+#endif /* not lint */
+
+#ifndef lint
+#if 0
+static char sccsid[] = "@(#)fold.c 8.1 (Berkeley) 6/6/93";
+#endif
+__RCSID("$NetBSD: fold.c,v 1.17 2011/09/04 20:24:59 joerg Exp $");
+#endif /* not lint */
+
+#include <limits.h>
+#include <locale.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <wchar.h>
+#include <err.h>
+
+#define DEFLINEWIDTH 80
+
+static void fold(int);
+static int new_column_position(int, wint_t);
+__dead static void usage(void);
+
+static int count_bytes = 0;
+static int split_words = 0;
+
+int
+main(int argc, char **argv)
+{
+ int ch;
+ int width;
+ char *p;
+
+ setlocale(LC_CTYPE, "");
+ setprogname(argv[0]);
+
+ width = -1;
+ while ((ch = getopt(argc, argv, "0123456789bsw:")) != -1)
+ switch (ch) {
+ case 'b':
+ count_bytes = 1;
+ break;
+ case 's':
+ split_words = 1;
+ break;
+ case 'w':
+ if ((width = atoi(optarg)) <= 0)
+ errx(1, "illegal width value");
+ break;
+ case '0': case '1': case '2': case '3': case '4':
+ case '5': case '6': case '7': case '8': case '9':
+ if (width == -1) {
+ p = argv[optind - 1];
+ if (p[0] == '-' && p[1] == ch && !p[2])
+ width = atoi(++p);
+ else
+ width = atoi(argv[optind] + 1);
+ }
+ break;
+ default:
+ usage();
+ }
+ argv += optind;
+ argc -= optind;
+
+ if (width == -1)
+ width = DEFLINEWIDTH;
+
+ if (!*argv)
+ fold(width);
+ else for (; *argv; ++argv)
+ if (!freopen(*argv, "r", stdin)) {
+ err (1, "%s", *argv);
+ /* NOTREACHED */
+ } else
+ fold(width);
+ exit(0);
+}
+
+/*
+ * Fold the contents of standard input to fit within WIDTH columns
+ * (or bytes) and write to standard output.
+ *
+ * If split_words is set, split the line at the last space character
+ * on the line. This flag necessitates storing the line in a buffer
+ * until the current column > width, or a newline or EOF is read.
+ *
+ * The buffer can grow larger than WIDTH due to backspaces and carriage
+ * returns embedded in the input stream.
+ */
+static void
+fold(int width)
+{
+ static wchar_t *buf = NULL;
+ wchar_t *nbuf;
+ static int buf_max = 0;
+ wint_t ch;
+ int col, indx, i;
+
+ col = indx = 0;
+ while ((ch = getwchar()) != WEOF) {
+ if (ch == L'\n') {
+ if (indx != 0) {
+ for (i = 0; i < indx; i++)
+ putwchar(buf[i]);
+ }
+ putwchar(L'\n');
+ col = indx = 0;
+ continue;
+ }
+
+ col = new_column_position (col, ch);
+ if (col > width) {
+ int last_space;
+
+#ifdef __GNUC__
+ last_space = 0; /* XXX gcc */
+#endif
+ if (split_words) {
+ for (i = 0, last_space = -1; i < indx; i++)
+ if (buf[i] == L' ')
+ last_space = i;
+ }
+
+ if (split_words && last_space != -1) {
+ for (i = 0; i < last_space; i++)
+ putwchar(buf[i]);
+
+ /* increase last_space here, so we skip trailing whitespace */
+ last_space++;
+ wmemmove (buf, buf+last_space, indx-last_space);
+
+ indx -= last_space;
+ col = 0;
+ for (i = 0; i < indx; i++) {
+ col = new_column_position (col, buf[i]);
+ }
+ } else {
+ for (i = 0; i < indx; i++)
+ putwchar(buf[i]);
+ col = indx = 0;
+ }
+ putwchar('\n');
+
+ /* calculate the column position for the next line. */
+ col = new_column_position (col, ch);
+ }
+
+ if (indx + 1 > buf_max) {
+ /* Allocate buffer in LINE_MAX increments */
+ if ((nbuf = realloc (buf, buf_max + 2048)) == NULL) {
+ err (1, "realloc");
+ /* NOTREACHED */
+ }
+ buf = nbuf;
+ buf_max += 2048;
+ }
+ buf[indx++] = ch;
+ }
+
+ if (indx != 0) {
+ for (i = 0; i < indx; i++)
+ putwchar(buf[i]);
+ }
+}
+
+/*
+ * calculate the column position
+ */
+static int
+new_column_position (int col, wint_t ch)
+{
+ int w;
+
+ if (!count_bytes) {
+ switch (ch) {
+ case L'\b':
+ if (col > 0)
+ --col;
+ break;
+ case L'\r':
+ col = 0;
+ break;
+ case L'\t':
+ col = (col + 8) & ~7;
+ break;
+ default:
+ w = wcwidth(ch);
+ if (w > 0)
+ col += w;
+ break;
+ }
+ } else {
+ char dummy[MB_LEN_MAX];
+
+ /* XXX: we assume stateless encoding */
+ col += wcrtomb(dummy, ch, NULL);
+ }
+
+ return col;
+}
+
+static void
+usage(void)
+{
+ (void)fprintf(stderr,
+ "usage: %s [-bs] [-w width] [file ...]\n", getprogname());
+ exit(1);
+}
+
diff --git a/usr.bin/gencat/gencat.1 b/usr.bin/gencat/gencat.1
new file mode 100644
index 0000000..6fcc171
--- /dev/null
+++ b/usr.bin/gencat/gencat.1
@@ -0,0 +1,233 @@
+.\" $NetBSD: gencat.1,v 1.14 2018/07/28 08:03:41 wiz Exp $
+.\"
+.\" Copyright (c) 2007 The NetBSD Foundation, Inc.
+.\" All rights reserved.
+.\"
+.\" This code is derived from software contributed to The NetBSD Foundation
+.\" by Kee Hinckley and Brian Ginsbach.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\" notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must reproduce the above copyright
+.\" notice, this list of conditions and the following disclaimer in the
+.\" documentation and/or other materials provided with the distribution.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
+.\" ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+.\" TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+.\" PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
+.\" BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+.\" CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+.\" SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+.\" INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+.\" CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+.\" ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+.\" POSSIBILITY OF SUCH DAMAGE.
+.\"
+.\" Written by Kee Hinckley <nazgul@somewhere.com>
+.\"
+.Dd December 29, 2011
+.Dt GENCAT 1
+.Os
+.Sh NAME
+.Nm gencat
+.Nd generates a Native Language Support (NLS) message catalog file
+.Sh SYNOPSIS
+.Nm
+.Ar catfile
+.Op Ar msgfile|- ...
+.Sh DESCRIPTION
+The
+.Nm
+utility generates a formatted message catalog
+.Ar catfile
+from stdin or one or more message source text files
+.Ar msgfile .
+The file
+.Ar catfile
+is created if it does not already exist.
+If
+.Ar catfile
+does exist, its messages are included in the new
+.Ar catfile .
+The new message text defined in
+.Ar msgfile
+replaces the old message text currently in
+.Ar catfile
+when the set and message numbers match.
+.Pp
+The generated message catalog contains message
+strings that will be retrieved using the
+.Xr catgets 3
+library call.
+These messages are dynamically loaded by the
+Native Language Support (NLS) library at run time.
+Error messages are grouped into sets, and a program can load a
+particular set depending on which type, or language, of messages
+is desired.
+.Ss Message Text Source File Format
+The message text source files are text files in the format described below.
+Note that the fields of a message text source line are separated by
+space or tab characters.
+.\" XXX Required by POSIX; the code must be fixed first. Above line should be
+.\" a single space or tab character;
+.\" any other space or tab characters are considered to be part of the
+.\" field contents.
+.Bl -tag -width 3n
+.It Li $set Ar n comment
+Determines the set identifier to be used for all subsequent messages
+until the next
+.Li $set
+or end-of-file.
+The
+.Ar n
+is the set identifier which is defined as a number in the range
+.Bo 1 ,
+.Dv NL_SETMAX Bc .
+Set identifiers within a single source file need not be contiguous.
+Any string following the set identifier is treated as a comment.
+If no
+.Li $set
+directive is specified in a message text source file,
+all messages will be located in the default message set
+.Dv NL_SETD .
+.It Li $delset Ar n comment
+Removes message set
+.Ar n
+from the catalog.
+The
+.Ar n
+is a set identifier in the range
+.Bo 1 ,
+.Dv NL_SETMAX Bc .
+If a message set was created earlier in the
+current file, or in a file previously read by the
+.Nm
+command, this directive will remove it.
+Any string following the set identifier is treated as a comment.
+.It Li $ Ar comment
+A line beginning with
+.Li $
+followed by a space or tab character is treated as a comment.
+.It Ar m message-text
+A message line consists of a message identifier
+.Ar m
+in the range
+.Bo 1 ,
+.Dv NL_MSGMAX Bc
+and the
+.Ar message-text .
+The
+.Ar message-text
+is read until the end of the line or a quote character
+(if one is specified).
+The
+.Ar message-text
+is stored in the message catalog with
+the set identifier specified by the last
+.Li $set
+directive, and the message identifier
+.Ar m .
+If the
+.Ar message-text
+is empty and there is a space or tab character
+following the message identifier,
+an empty string is stored in the message catalog.
+If no
+.Ar message-text
+is provided,
+and if there is no space or tab character following the message
+identifier,
+the message with the message identifier
+.Ar m
+in the current set is removed from the catalog.
+Message identifiers need not be contiguous within a single set.
+The length of
+.Ar message-text
+must be in the range
+.Bo 0 ,
+.Dv NL_TEXTMAX Bc .
+.It Li $quote Ar c
+Sets an optional quote character to be used around the
+.Ar message-text .
+The quote character
+.Ar c
+may be any character other than white space.
+If this is specified, then messages must begin and end with the
+quote character.
+.\" XXX Remove next sentence when code is fixed for POSIX conformance.
+This is useful when messages must contain leading white space.
+.\" XXX Replacement when above is removed.
+.\" This is useful to make leading and trailing spaces or empty
+.\" messages visible.
+By default no quote character is used.
+If an empty
+.Li $quote
+directive is specified, then the current quote character is unset.
+.El
+.Pp
+Empty lines
+.\" XXX Remove next line when the code is fixed for POSIX conformance.
+and leading blanks
+in a message text source file are ignored.
+Any line beginning with any character other than those
+described above is ignored as a syntax error.
+.Pp
+Text message strings may contain any characters and
+the following special characters and escape sequences.
+.Bl -column -offset indent ".Sy carriage return" ".Sy Symbol" ".Sy Sequence"
+.It Sy Description Ta Sy Symbol Ta Sy Sequence
+.It newline Ta NL(LF) Ta Li \en
+.It horizontal tab Ta HT Ta Li \et
+.It vertical tab Ta VT Ta Li \ev
+.It backspace Ta BS Ta Li \eb
+.It carriage return Ta CR Ta Li \er
+.It form feed Ta FF Ta Li \ef
+.It backslash Ta \e Ta Li \e\e
+.It bit pattern Ta ddd Ta Li \eddd
+.El
+.Pp
+A bit pattern,
+.Li \eddd ,
+consists of a backslash followed by
+one, two, or three octal digits representing the value of the character.
+The current quote character, if defined, may be escaped with a backslash
+to generate the quote character.
+Any character following the backslash ('\e') other than those specified
+is ignored.
+.Pp
+A backslash at the end of the line continues the message onto the next line.
+The following two lines are an example of such a message:
+.Pp
+.Dl 1 This message continues \e
+.D1 on the next line
+.Pp
+Producing the following message:
+.Pp
+.Dl 1 This message continues on the next line
+.Sh EXIT STATUS
+.Ex -std
+.Sh SEE ALSO
+.Xr catclose 3 ,
+.Xr catgets 3 ,
+.Xr catopen 3 ,
+.Xr nls 7
+.\" XXX Close but not quite; add when code is fixed.
+.\".Sh STANDARDS
+.\"The
+.\".Nm
+.\"utility is compliant with the
+.\".St -p1003.1-2004
+.\"standard.
+.Sh AUTHORS
+.An -nosplit
+The Native Language Support (NLS) message catalog facility was
+contributed by
+.An J.T. Conklin
+.Aq Mt jtc@NetBSD.org .
+This page was originally written by
+.An Kee Hinckley
+.Aq Mt nazgul@somewhere.com .
diff --git a/usr.bin/gencat/gencat.c b/usr.bin/gencat/gencat.c
new file mode 100644
index 0000000..444ce2d
--- /dev/null
+++ b/usr.bin/gencat/gencat.c
@@ -0,0 +1,879 @@
+/* $NetBSD: gencat.c,v 1.36 2013/11/27 17:38:11 apb Exp $ */
+
+/*
+ * Copyright (c) 1996 The NetBSD Foundation, Inc.
+ * All rights reserved.
+ *
+ * This code is derived from software contributed to The NetBSD Foundation
+ * by J.T. Conklin.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
+ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+#if defined(__RCSID) && !defined(lint)
+__RCSID("$NetBSD: gencat.c,v 1.36 2013/11/27 17:38:11 apb Exp $");
+#endif
+
+/***********************************************************
+Copyright 1990, by Alfalfa Software Incorporated, Cambridge, Massachusetts.
+
+ All Rights Reserved
+
+Permission to use, copy, modify, and distribute this software and its
+documentation for any purpose and without fee is hereby granted,
+provided that the above copyright notice appear in all copies and that
+both that copyright notice and this permission notice appear in
+supporting documentation, and that Alfalfa's name not be used in
+advertising or publicity pertaining to distribution of the software
+without specific, written prior permission.
+
+ALPHALPHA DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING
+ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL
+ALPHALPHA BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR
+ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
+WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
+ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
+SOFTWARE.
+
+If you make any modifications, bugfixes or other changes to this software
+we'd appreciate it if you could send a copy to us so we can keep things
+up-to-date. Many thanks.
+ Kee Hinckley
+ Alfalfa Software, Inc.
+ 267 Allston St., #3
+ Cambridge, MA 02139 USA
+ nazgul@alfalfa.com
+
+******************************************************************/
+
+#if HAVE_NBTOOL_CONFIG_H
+#include "nbtool_config.h"
+#endif
+
+#define _NLS_PRIVATE
+
+#include <sys/types.h>
+#include <sys/queue.h>
+
+#include <netinet/in.h> /* Needed by arpa/inet.h on NetBSD */
+#include <arpa/inet.h> /* Needed for htonl() on POSIX systems */
+
+#include <ctype.h>
+#include <err.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <limits.h>
+#include <nl_types.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#ifndef NL_SETMAX
+#define NL_SETMAX 255
+#endif
+#ifndef NL_MSGMAX
+#define NL_MSGMAX 2048
+#endif
+
+struct _msgT {
+ long msgId;
+ char *str;
+ LIST_ENTRY(_msgT) entries;
+};
+
+struct _setT {
+ long setId;
+ LIST_HEAD(msghead, _msgT) msghead;
+ LIST_ENTRY(_setT) entries;
+};
+
+static LIST_HEAD(sethead, _setT) sethead = LIST_HEAD_INITIALIZER(sethead);
+static struct _setT *curSet;
+
+static const char *curfile;
+static char *curline = NULL;
+static long lineno = 0;
+
+static char *cskip(char *);
+__dead static void error(const char *);
+static char *get_line(int);
+static char *getmsg(int, char *, char);
+static void warning(const char *, const char *);
+static char *wskip(char *);
+static char *xstrdup(const char *);
+static void *xmalloc(size_t);
+static void *xrealloc(void *, size_t);
+
+static void MCParse(int fd);
+static void MCReadCat(int fd);
+static void MCWriteCat(int fd);
+static void MCDelMsg(int msgId);
+static void MCAddMsg(int msgId, const char *msg);
+static void MCAddSet(int setId);
+static void MCDelSet(int setId);
+__dead static void usage(void);
+
+#define CORRUPT "corrupt message catalog"
+#define NOMEMORY "out of memory"
+
+static void
+usage(void)
+{
+ fprintf(stderr, "usage: %s catfile [msgfile|- ...]\n", getprogname());
+ exit(1);
+}
+
+int
+main(int argc, char *argv[])
+{
+ int ofd, ifd;
+ char *catfile = NULL;
+ int c;
+ int updatecat = 0;
+
+ while ((c = getopt(argc, argv, "")) != -1) {
+ switch (c) {
+ case '?':
+ default:
+ usage();
+ /* NOTREACHED */
+ }
+ }
+ argc -= optind;
+ argv += optind;
+
+ if (argc < 1) {
+ usage();
+ /* NOTREACHED */
+ }
+ catfile = *argv++;
+
+ if ((catfile[0] == '-') && (catfile[1] == '\0')) {
+ ofd = STDOUT_FILENO;
+ } else {
+ ofd = open(catfile, O_WRONLY | O_CREAT | O_EXCL, 0666);
+ if (ofd < 0) {
+ if (errno == EEXIST) {
+ if ((ofd = open(catfile, O_RDWR)) < 0) {
+ err(1, "Unable to open %s", catfile);
+ /* NOTREACHED */
+ }
+ } else {
+ err(1, "Unable to create new %s", catfile);
+ /* NOTREACHED */
+ }
+ curfile = catfile;
+ updatecat = 1;
+ MCReadCat(ofd);
+ if (lseek(ofd, (off_t)0, SEEK_SET) == (off_t)-1) {
+ err(1, "Unable to seek on %s", catfile);
+ /* NOTREACHED */
+ }
+ }
+ }
+
+ if (argc < 2 || (((*argv)[0] == '-') && ((*argv)[1] == '\0'))) {
+ if (argc > 2)
+ usage();
+ /* NOTREACHED */
+ MCParse(STDIN_FILENO);
+ } else {
+ for (; *argv; argv++) {
+ if ((ifd = open(*argv, O_RDONLY)) < 0)
+ err(1, "Unable to read %s", *argv);
+ curfile = *argv;
+ lineno = 0;
+ MCParse(ifd);
+ close(ifd);
+ }
+ }
+
+ if (updatecat) {
+ if (ftruncate(ofd, 0) != 0) {
+ err(1, "Unable to truncate %s", catfile);
+ /* NOTREACHED */
+ }
+ }
+
+ MCWriteCat(ofd);
+ exit(0);
+}
+
+static void
+warning(const char *cptr, const char *msg)
+{
+ if (lineno) {
+ fprintf(stderr, "%s: %s on line %ld, %s\n",
+ getprogname(), msg, lineno, curfile);
+ fprintf(stderr, "%s\n", curline);
+ if (cptr) {
+ char *tptr;
+ for (tptr = curline; tptr < cptr; ++tptr)
+ putc(' ', stderr);
+ fprintf(stderr, "^\n");
+ }
+ } else {
+ fprintf(stderr, "%s: %s, %s\n", getprogname(), msg, curfile);
+ }
+}
+
+static void
+error(const char *msg)
+{
+ warning(NULL, msg);
+ exit(1);
+}
+
+static void *
+xmalloc(size_t len)
+{
+ void *p;
+
+ if ((p = malloc(len)) == NULL)
+ errx(1, NOMEMORY);
+ return (p);
+}
+
+static void *
+xrealloc(void *ptr, size_t size)
+{
+ if ((ptr = realloc(ptr, size)) == NULL)
+ errx(1, NOMEMORY);
+ return (ptr);
+}
+
+static char *
+xstrdup(const char *str)
+{
+ char *nstr;
+
+ if ((nstr = strdup(str)) == NULL)
+ errx(1, NOMEMORY);
+ return (nstr);
+}
+
+static char *
+get_line(int fd)
+{
+ static long curlen = BUFSIZ;
+ static char buf[BUFSIZ], *bptr = buf, *bend = buf;
+ char *cptr, *cend;
+ long buflen;
+
+ if (!curline) {
+ curline = xmalloc(curlen);
+ }
+ ++lineno;
+
+ cptr = curline;
+ cend = curline + curlen;
+ for (;;) {
+ for (; bptr < bend && cptr < cend; ++cptr, ++bptr) {
+ if (*bptr == '\n') {
+ *cptr = '\0';
+ ++bptr;
+ return (curline);
+ } else
+ *cptr = *bptr;
+ }
+ if (cptr == cend) {
+ cptr = curline = xrealloc(curline, curlen *= 2);
+ cend = curline + curlen;
+ }
+ if (bptr == bend) {
+ buflen = read(fd, buf, BUFSIZ);
+ if (buflen <= 0) {
+ if (cptr > curline) {
+ *cptr = '\0';
+ return (curline);
+ }
+ return (NULL);
+ }
+ bend = buf + buflen;
+ bptr = buf;
+ }
+ }
+}
+
+static char *
+wskip(char *cptr)
+{
+ if (!*cptr || !isspace((unsigned char) *cptr)) {
+ warning(cptr, "expected a space");
+ return (cptr);
+ }
+ while (*cptr && isspace((unsigned char) *cptr))
+ ++cptr;
+ return (cptr);
+}
+
+static char *
+cskip(char *cptr)
+{
+ if (!*cptr || isspace((unsigned char) *cptr)) {
+ warning(cptr, "wasn't expecting a space");
+ return (cptr);
+ }
+ while (*cptr && !isspace((unsigned char) *cptr))
+ ++cptr;
+ return (cptr);
+}
+
+static char *
+getmsg(int fd, char *cptr, char quote)
+{
+ static char *msg = NULL;
+ static size_t msglen = 0;
+ size_t clen, i;
+ int in_quote = 0;
+ char *tptr;
+
+ if (quote && *cptr == quote) {
+ ++cptr;
+ in_quote = 1;
+ }
+
+ clen = strlen(cptr) + 1;
+ if (clen > msglen) {
+ if (msglen)
+ msg = xrealloc(msg, clen);
+ else
+ msg = xmalloc(clen);
+ msglen = clen;
+ }
+ tptr = msg;
+
+ while (*cptr) {
+ if (quote && *cptr == quote) {
+ char *tmp;
+ tmp = cptr + 1;
+ if (!in_quote) {
+ /* XXX hard error? */
+ warning(cptr, "unexpected quote character, ignoring");
+ *tptr++ = *cptr++;
+ } else {
+ cptr++;
+ /* don't use wskip() */
+ while (*cptr && isspace((unsigned char) *cptr))
+#ifndef _BACKWARDS_COMPAT
+ cptr++;
+#else
+ *tptr++ = *cptr++;
+#endif
+ /* XXX hard error? */
+ if (*cptr)
+ warning(tmp, "unexpected extra characters, ignoring");
+ in_quote = 0;
+#ifndef _BACKWARDS_COMPAT
+ break;
+#endif
+ }
+ } else {
+ if (*cptr == '\\') {
+ ++cptr;
+ switch (*cptr) {
+ case '\0':
+ cptr = get_line(fd);
+ if (!cptr)
+ error("premature end of file");
+ msglen += strlen(cptr);
+ i = tptr - msg;
+ msg = xrealloc(msg, msglen);
+ tptr = msg + i;
+ break;
+ case 'n':
+ *tptr++ = '\n';
+ ++cptr;
+ break;
+ case 't':
+ *tptr++ = '\t';
+ ++cptr;
+ break;
+ case 'v':
+ *tptr++ = '\v';
+ ++cptr;
+ break;
+ case 'b':
+ *tptr++ = '\b';
+ ++cptr;
+ break;
+ case 'r':
+ *tptr++ = '\r';
+ ++cptr;
+ break;
+ case 'f':
+ *tptr++ = '\f';
+ ++cptr;
+ break;
+ case '\\':
+ *tptr++ = '\\';
+ ++cptr;
+ break;
+ default:
+ if (quote && *cptr == quote) {
+ *tptr++ = *cptr++;
+ } else if (isdigit((unsigned char) *cptr)) {
+ *tptr = 0;
+ for (i = 0; i < 3; ++i) {
+ if (!isdigit((unsigned char) *cptr))
+ break;
+ if (*cptr > '7')
+ warning(cptr, "octal number greater than 7?!");
+ *tptr *= 8;
+ *tptr += (*cptr - '0');
+ ++cptr;
+ }
+ } else {
+ warning(cptr, "unrecognized escape sequence");
+ }
+ break;
+ }
+ } else {
+ *tptr++ = *cptr++;
+ }
+ }
+ }
+
+ if (in_quote)
+ warning(cptr, "unterminated quoted message, ignoring");
+
+ *tptr = '\0';
+ return (msg);
+}
+
+static void
+MCParse(int fd)
+{
+ char *cptr, *str;
+ int msgid = 0;
+ int setid = 0;
+ char quote = 0;
+
+ while ((cptr = get_line(fd))) {
+ if (*cptr == '$') {
+ ++cptr;
+ if (strncmp(cptr, "set", 3) == 0) {
+ cptr += 3;
+ cptr = wskip(cptr);
+ setid = atoi(cptr);
+ MCAddSet(setid);
+ msgid = 0;
+ } else if (strncmp(cptr, "delset", 6) == 0) {
+ cptr += 6;
+ cptr = wskip(cptr);
+ setid = atoi(cptr);
+ MCDelSet(setid);
+ } else if (strncmp(cptr, "quote", 5) == 0) {
+ cptr += 5;
+ if (!*cptr)
+ quote = 0;
+ else {
+ cptr = wskip(cptr);
+ if (!*cptr)
+ quote = 0;
+ else
+ quote = *cptr;
+ }
+ } else if (isspace((unsigned char) *cptr)) {
+ ;
+ } else {
+ if (*cptr) {
+ cptr = wskip(cptr);
+ if (*cptr)
+ warning(cptr, "unrecognized line");
+ }
+ }
+ } else {
+ /*
+ * First check for (and eat) empty lines....
+ */
+ if (!*cptr)
+ continue;
+ /*
+ * We have a digit? Start of a message. Else,
+ * syntax error.
+ */
+ if (isdigit((unsigned char) *cptr)) {
+ msgid = atoi(cptr);
+ cptr = cskip(cptr);
+ if (*cptr) {
+ cptr = wskip(cptr);
+ if (!*cptr) {
+ MCAddMsg(msgid, "");
+ continue;
+ }
+ }
+ } else {
+ warning(cptr, "neither blank line nor start of a message id");
+ continue;
+ }
+ /*
+ * If no set directive specified, all messages
+ * shall be in default message set NL_SETD.
+ */
+ if (setid == 0) {
+ setid = NL_SETD;
+ MCAddSet(setid);
+ }
+ /*
+ * If we have a message ID, but no message,
+ * then this means "delete this message id
+ * from the catalog".
+ */
+ if (!*cptr) {
+ MCDelMsg(msgid);
+ } else {
+ str = getmsg(fd, cptr, quote);
+ MCAddMsg(msgid, str);
+ }
+ }
+ }
+}
+
+static void
+MCReadCat(int fd)
+{
+ void *msgcat; /* message catalog data */
+ struct _nls_cat_hdr cat_hdr;
+ struct _nls_set_hdr *set_hdr;
+ struct _nls_msg_hdr *msg_hdr;
+ char *strings;
+ ssize_t n;
+ int m, s;
+ int msgno, setno;
+
+ n = read(fd, &cat_hdr, sizeof(cat_hdr));
+ if (n < (ssize_t)sizeof(cat_hdr)) {
+ if (n == 0)
+ return; /* empty file */
+ else if (n == -1)
+ err(1, "header read");
+ else
+ errx(1, CORRUPT);
+ }
+ if (ntohl((uint32_t)cat_hdr.__magic) != _NLS_MAGIC)
+ errx(1, "%s: bad magic number (%#x)", CORRUPT, cat_hdr.__magic);
+
+ cat_hdr.__mem = ntohl(cat_hdr.__mem);
+
+ cat_hdr.__nsets = ntohl(cat_hdr.__nsets);
+ cat_hdr.__msg_hdr_offset = ntohl(cat_hdr.__msg_hdr_offset);
+ cat_hdr.__msg_txt_offset = ntohl(cat_hdr.__msg_txt_offset);
+ if ((cat_hdr.__mem < 0) ||
+ (cat_hdr.__msg_hdr_offset < 0) ||
+ (cat_hdr.__msg_txt_offset < 0) ||
+ (cat_hdr.__mem < (int32_t)(cat_hdr.__nsets * sizeof(struct _nls_set_hdr))) ||
+ (cat_hdr.__mem < cat_hdr.__msg_hdr_offset) ||
+ (cat_hdr.__mem < cat_hdr.__msg_txt_offset))
+ errx(1, "%s: catalog header", CORRUPT);
+
+ msgcat = xmalloc(cat_hdr.__mem);
+
+ n = read(fd, msgcat, cat_hdr.__mem);
+ if (n < cat_hdr.__mem) {
+ if (n == -1)
+ err(1, "data read");
+ else
+ errx(1, CORRUPT);
+ }
+
+ set_hdr = (struct _nls_set_hdr *)msgcat;
+ msg_hdr = (struct _nls_msg_hdr *)((char *)msgcat +
+ cat_hdr.__msg_hdr_offset);
+ strings = (char *)msgcat + cat_hdr.__msg_txt_offset;
+
+ setno = 0;
+ for (s = 0; s < cat_hdr.__nsets; s++, set_hdr++) {
+ set_hdr->__setno = ntohl(set_hdr->__setno);
+ if (set_hdr->__setno < setno)
+ errx(1, "%s: bad set number (%d)",
+ CORRUPT, set_hdr->__setno);
+ setno = set_hdr->__setno;
+
+ MCAddSet(setno);
+
+ set_hdr->__nmsgs = ntohl(set_hdr->__nmsgs);
+ set_hdr->__index = ntohl(set_hdr->__index);
+ if (set_hdr->__nmsgs < 0 || set_hdr->__index < 0)
+ errx(1, "%s: set header", CORRUPT);
+
+ /* Get the data */
+ msgno = 0;
+ for (m = 0; m < set_hdr->__nmsgs; m++, msg_hdr++) {
+ msg_hdr->__msgno = ntohl(msg_hdr->__msgno);
+ msg_hdr->__offset = ntohl(msg_hdr->__offset);
+ if (msg_hdr->__msgno < msgno)
+ errx(1, "%s: bad message number (%d)",
+ CORRUPT, msg_hdr->__msgno);
+ if ((msg_hdr->__offset < 0) ||
+ ((strings + msg_hdr->__offset) >
+ ((char *)msgcat + cat_hdr.__mem)))
+ errx(1, "%s: message header", CORRUPT);
+
+ msgno = msg_hdr->__msgno;
+ MCAddMsg(msgno, strings + msg_hdr->__offset);
+ }
+ }
+ free(msgcat);
+}
+
+/*
+ * Write message catalog.
+ *
+ * The message catalog is first converted from its internal to its
+ * external representation in a chunk of memory allocated for this
+ * purpose. Then the completed catalog is written. This approach
+ * avoids additional housekeeping variables and/or a lot of seeks
+ * that would otherwise be required.
+ */
+static void
+MCWriteCat(int fd)
+{
+ int nsets; /* number of sets */
+ int nmsgs; /* number of msgs */
+ int string_size; /* total size of string pool */
+ int msgcat_size; /* total size of message catalog */
+ void *msgcat; /* message catalog data */
+ struct _nls_cat_hdr *cat_hdr;
+ struct _nls_set_hdr *set_hdr;
+ struct _nls_msg_hdr *msg_hdr;
+ char *strings;
+ struct _setT *set;
+ struct _msgT *msg;
+ int msg_index;
+ int msg_offset;
+
+ /* determine number of sets, number of messages, and size of the
+ * string pool */
+ nsets = 0;
+ nmsgs = 0;
+ string_size = 0;
+
+ LIST_FOREACH(set, &sethead, entries) {
+ nsets++;
+
+ LIST_FOREACH(msg, &set->msghead, entries) {
+ nmsgs++;
+ string_size += strlen(msg->str) + 1;
+ }
+ }
+
+#ifdef DEBUG
+ printf("number of sets: %d\n", nsets);
+ printf("number of msgs: %d\n", nmsgs);
+ printf("string pool size: %d\n", string_size);
+#endif
+
+ /* determine size and then allocate buffer for constructing external
+ * message catalog representation */
+ msgcat_size = sizeof(struct _nls_cat_hdr)
+ + (nsets * sizeof(struct _nls_set_hdr))
+ + (nmsgs * sizeof(struct _nls_msg_hdr))
+ + string_size;
+
+ msgcat = xmalloc(msgcat_size);
+ memset(msgcat, '\0', msgcat_size);
+
+ /* fill in msg catalog header */
+ cat_hdr = (struct _nls_cat_hdr *) msgcat;
+ cat_hdr->__magic = htonl(_NLS_MAGIC);
+ cat_hdr->__nsets = htonl(nsets);
+ cat_hdr->__mem = htonl(msgcat_size - sizeof(struct _nls_cat_hdr));
+ cat_hdr->__msg_hdr_offset =
+ htonl(nsets * sizeof(struct _nls_set_hdr));
+ cat_hdr->__msg_txt_offset =
+ htonl(nsets * sizeof(struct _nls_set_hdr) +
+ nmsgs * sizeof(struct _nls_msg_hdr));
+
+ /* compute offsets for set & msg header tables and string pool */
+ set_hdr = (struct _nls_set_hdr *) ((char *) msgcat +
+ sizeof(struct _nls_cat_hdr));
+ msg_hdr = (struct _nls_msg_hdr *) ((char *) msgcat +
+ sizeof(struct _nls_cat_hdr) +
+ nsets * sizeof(struct _nls_set_hdr));
+ strings = (char *) msgcat +
+ sizeof(struct _nls_cat_hdr) +
+ nsets * sizeof(struct _nls_set_hdr) +
+ nmsgs * sizeof(struct _nls_msg_hdr);
+
+ msg_index = 0;
+ msg_offset = 0;
+ LIST_FOREACH(set, &sethead, entries) {
+
+ nmsgs = 0;
+ LIST_FOREACH(msg, &set->msghead, entries) {
+ int32_t msg_len = strlen(msg->str) + 1;
+
+ msg_hdr->__msgno = htonl(msg->msgId);
+ msg_hdr->__msglen = htonl(msg_len);
+ msg_hdr->__offset = htonl(msg_offset);
+
+ memcpy(strings, msg->str, msg_len);
+ strings += msg_len;
+ msg_offset += msg_len;
+
+ nmsgs++;
+ msg_hdr++;
+ }
+
+ set_hdr->__setno = htonl(set->setId);
+ set_hdr->__nmsgs = htonl(nmsgs);
+ set_hdr->__index = htonl(msg_index);
+ msg_index += nmsgs;
+ set_hdr++;
+ }
+
+ /* write out catalog. XXX: should this be done in small chunks? */
+ write(fd, msgcat, msgcat_size);
+}
+
+static void
+MCAddSet(int setId)
+{
+ struct _setT *p, *q;
+
+ if (setId <= 0) {
+ error("setId's must be greater than zero");
+ /* NOTREACHED */
+ }
+ if (setId > NL_SETMAX) {
+ error("setId exceeds limit");
+ /* NOTREACHED */
+ }
+
+ p = LIST_FIRST(&sethead);
+ q = NULL;
+ for (; p != NULL && p->setId < setId; q = p, p = LIST_NEXT(p, entries))
+ continue;
+
+ if (p && p->setId == setId) {
+ ;
+ } else {
+ p = xmalloc(sizeof(struct _setT));
+ memset(p, '\0', sizeof(struct _setT));
+ LIST_INIT(&p->msghead);
+
+ p->setId = setId;
+
+ if (q == NULL) {
+ LIST_INSERT_HEAD(&sethead, p, entries);
+ } else {
+ LIST_INSERT_AFTER(q, p, entries);
+ }
+ }
+
+ curSet = p;
+}
+
+static void
+MCAddMsg(int msgId, const char *str)
+{
+ struct _msgT *p, *q;
+
+ if (!curSet)
+ error("can't specify a message when no set exists");
+
+ if (msgId <= 0) {
+ error("msgId's must be greater than zero");
+ /* NOTREACHED */
+ }
+ if (msgId > NL_MSGMAX) {
+ error("msgID exceeds limit");
+ /* NOTREACHED */
+ }
+
+ p = LIST_FIRST(&curSet->msghead);
+ q = NULL;
+ for (; p != NULL && p->msgId < msgId; q = p, p = LIST_NEXT(p, entries))
+ continue;
+
+ if (p && p->msgId == msgId) {
+ free(p->str);
+ } else {
+ p = xmalloc(sizeof(struct _msgT));
+ memset(p, '\0', sizeof(struct _msgT));
+
+ if (q == NULL) {
+ LIST_INSERT_HEAD(&curSet->msghead, p, entries);
+ } else {
+ LIST_INSERT_AFTER(q, p, entries);
+ }
+ }
+
+ p->msgId = msgId;
+ p->str = xstrdup(str);
+}
+
+static void
+MCDelSet(int setId)
+{
+ struct _setT *set;
+ struct _msgT *msg;
+
+ if (setId <= 0) {
+ error("setId's must be greater than zero");
+ /* NOTREACHED */
+ }
+ if (setId > NL_SETMAX) {
+ error("setId exceeds limit");
+ /* NOTREACHED */
+ }
+
+ set = LIST_FIRST(&sethead);
+ for (; set != NULL && set->setId < setId; set = LIST_NEXT(set, entries))
+ continue;
+
+ if (set && set->setId == setId) {
+ LIST_REMOVE(set, entries);
+ while ((msg = LIST_FIRST(&set->msghead)) != NULL) {
+ LIST_REMOVE(msg, entries);
+ free(msg->str);
+ free(msg);
+ }
+ free(set);
+ return;
+ }
+ warning(NULL, "specified set doesn't exist");
+}
+
+static void
+MCDelMsg(int msgId)
+{
+ struct _msgT *msg;
+
+ if (!curSet)
+ error("you can't delete a message before defining the set");
+
+ msg = LIST_FIRST(&curSet->msghead);
+ for (; msg != NULL && msg->msgId < msgId; msg = LIST_NEXT(msg, entries))
+ continue;
+
+ if (msg && msg->msgId == msgId) {
+ LIST_REMOVE(msg, entries);
+ free(msg->str);
+ free(msg);
+ return;
+ }
+ warning(NULL, "specified msg doesn't exist");
+}
diff --git a/usr.bin/getconf/getconf.1 b/usr.bin/getconf/getconf.1
new file mode 100644
index 0000000..520a688
--- /dev/null
+++ b/usr.bin/getconf/getconf.1
@@ -0,0 +1,94 @@
+.\" $NetBSD: getconf.1,v 1.13 2014/04/13 01:45:34 snj Exp $
+.\"
+.\" Copyright (c) 1996 The NetBSD Foundation, Inc.
+.\" All rights reserved.
+.\"
+.\" This code is derived from software contributed to The NetBSD Foundation
+.\" by J.T. Conklin.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\" notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must reproduce the above copyright
+.\" notice, this list of conditions and the following disclaimer in the
+.\" documentation and/or other materials provided with the distribution.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
+.\" ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+.\" TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+.\" PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
+.\" BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+.\" CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+.\" SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+.\" INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+.\" CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+.\" ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+.\" POSSIBILITY OF SUCH DAMAGE.
+.\"
+.Dd August 9, 2011
+.Dt GETCONF 1
+.Os
+.Sh NAME
+.Nm getconf
+.Nd get configuration values
+.Sh SYNOPSIS
+.Nm
+.Ar system_var
+.Nm
+.Fl a
+.Nm
+.Ar path_var
+.Ar pathname
+.Nm
+.Fl a
+.Ar pathname
+.Sh DESCRIPTION
+The
+.Nm
+utility writes the current value of a configurable system limit or
+option variable to the standard output.
+.Pp
+The
+.Ar system_var
+argument specifies the system variable to be queried.
+The names of the system variables are from
+.Xr sysconf 3
+with the leading
+.Dq Li _SC_
+removed.
+.Pp
+The
+.Ar path_var
+argument specifies the pathname variable to be queried for the specified
+.Ar pathname
+argument.
+The names of the pathname variables are from
+.Xr pathconf 2
+with the leading
+.Dq Li _PC_
+removed.
+.Pp
+When invoked with the option
+.Fl a ,
+.Nm
+writes a list of all applicable variables and their values to the
+standard output, in the format
+.Do
+.Va name
+=
+.Va value
+.Dc .
+.Sh EXIT STATUS
+.Ex -std
+.Sh SEE ALSO
+.Xr pathconf 2 ,
+.Xr confstr 3 ,
+.Xr limits 3 ,
+.Xr sysconf 3
+.Sh STANDARDS
+The
+.Nm
+utility conforms to
+.St -p1003.2-92 .
diff --git a/usr.bin/getconf/getconf.c b/usr.bin/getconf/getconf.c
new file mode 100644
index 0000000..8de472b
--- /dev/null
+++ b/usr.bin/getconf/getconf.c
@@ -0,0 +1,342 @@
+/* $NetBSD: getconf.c,v 1.35 2013/12/19 19:11:50 rmind Exp $ */
+
+/*-
+ * Copyright (c) 1996, 1998 The NetBSD Foundation, Inc.
+ * All rights reserved.
+ *
+ * This code is derived from software contributed to The NetBSD Foundation
+ * by J.T. Conklin.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
+ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+#ifndef lint
+__RCSID("$NetBSD: getconf.c,v 1.35 2013/12/19 19:11:50 rmind Exp $");
+#endif /* not lint */
+
+#include <err.h>
+#include <errno.h>
+#include <limits.h>
+#include <locale.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+
+struct conf_variable
+{
+ const char *name;
+ enum { SYSCONF, CONFSTR, PATHCONF, CONSTANT } type;
+ long value;
+};
+
+static void print_longvar(const char *, long);
+static void print_strvar(const char *, const char *);
+static void printvar(const struct conf_variable *, const char *);
+static void usage(void) __dead;
+
+static const struct conf_variable conf_table[] =
+{
+ { "PATH", CONFSTR, _CS_PATH },
+
+ /* Utility Limit Minimum Values */
+ { "POSIX2_BC_BASE_MAX", CONSTANT, _POSIX2_BC_BASE_MAX },
+ { "POSIX2_BC_DIM_MAX", CONSTANT, _POSIX2_BC_DIM_MAX },
+ { "POSIX2_BC_SCALE_MAX", CONSTANT, _POSIX2_BC_SCALE_MAX },
+ { "POSIX2_BC_STRING_MAX", CONSTANT, _POSIX2_BC_STRING_MAX },
+ { "POSIX2_COLL_WEIGHTS_MAX", CONSTANT, _POSIX2_COLL_WEIGHTS_MAX },
+ { "POSIX2_EXPR_NEST_MAX", CONSTANT, _POSIX2_EXPR_NEST_MAX },
+ { "POSIX2_LINE_MAX", CONSTANT, _POSIX2_LINE_MAX },
+ { "POSIX2_RE_DUP_MAX", CONSTANT, _POSIX2_RE_DUP_MAX },
+ { "POSIX2_VERSION", CONSTANT, _POSIX2_VERSION },
+
+ /* POSIX.1 Minimum Values */
+ { "_POSIX_AIO_LISTIO_MAX", CONSTANT, _POSIX_AIO_LISTIO_MAX },
+ { "_POSIX_AIO_MAX", CONSTANT, _POSIX_AIO_MAX },
+ { "_POSIX_ARG_MAX", CONSTANT, _POSIX_ARG_MAX },
+ { "_POSIX_CHILD_MAX", CONSTANT, _POSIX_CHILD_MAX },
+ { "_POSIX_LINK_MAX", CONSTANT, _POSIX_LINK_MAX },
+ { "_POSIX_MAX_CANON", CONSTANT, _POSIX_MAX_CANON },
+ { "_POSIX_MAX_INPUT", CONSTANT, _POSIX_MAX_INPUT },
+ { "_POSIX_MQ_OPEN_MAX", CONSTANT, _POSIX_MQ_OPEN_MAX },
+ { "_POSIX_MQ_PRIO_MAX", CONSTANT, _POSIX_MQ_PRIO_MAX },
+ { "_POSIX_NAME_MAX", CONSTANT, _POSIX_NAME_MAX },
+ { "_POSIX_NGROUPS_MAX", CONSTANT, _POSIX_NGROUPS_MAX },
+ { "_POSIX_OPEN_MAX", CONSTANT, _POSIX_OPEN_MAX },
+ { "_POSIX_PATH_MAX", CONSTANT, _POSIX_PATH_MAX },
+ { "_POSIX_PIPE_BUF", CONSTANT, _POSIX_PIPE_BUF },
+ { "_POSIX_SSIZE_MAX", CONSTANT, _POSIX_SSIZE_MAX },
+ { "_POSIX_STREAM_MAX", CONSTANT, _POSIX_STREAM_MAX },
+ { "_POSIX_TZNAME_MAX", CONSTANT, _POSIX_TZNAME_MAX },
+
+ /* Symbolic Utility Limits */
+ { "BC_BASE_MAX", SYSCONF, _SC_BC_BASE_MAX },
+ { "BC_DIM_MAX", SYSCONF, _SC_BC_DIM_MAX },
+ { "BC_SCALE_MAX", SYSCONF, _SC_BC_SCALE_MAX },
+ { "BC_STRING_MAX", SYSCONF, _SC_BC_STRING_MAX },
+ { "COLL_WEIGHTS_MAX", SYSCONF, _SC_COLL_WEIGHTS_MAX },
+ { "EXPR_NEST_MAX", SYSCONF, _SC_EXPR_NEST_MAX },
+ { "LINE_MAX", SYSCONF, _SC_LINE_MAX },
+ { "RE_DUP_MAX", SYSCONF, _SC_RE_DUP_MAX },
+
+ /* Optional Facility Configuration Values */
+ { "_POSIX2_C_BIND", SYSCONF, _SC_2_C_BIND },
+ { "POSIX2_C_DEV", SYSCONF, _SC_2_C_DEV },
+ { "POSIX2_CHAR_TERM", SYSCONF, _SC_2_CHAR_TERM },
+ { "POSIX2_FORT_DEV", SYSCONF, _SC_2_FORT_DEV },
+ { "POSIX2_FORT_RUN", SYSCONF, _SC_2_FORT_RUN },
+ { "POSIX2_LOCALEDEF", SYSCONF, _SC_2_LOCALEDEF },
+ { "POSIX2_SW_DEV", SYSCONF, _SC_2_SW_DEV },
+ { "POSIX2_UPE", SYSCONF, _SC_2_UPE },
+
+ /* POSIX.1 Configurable System Variables */
+ { "AIO_LISTIO_MAX", SYSCONF, _SC_AIO_LISTIO_MAX },
+ { "AIO_MAX", SYSCONF, _SC_AIO_MAX },
+ { "ARG_MAX", SYSCONF, _SC_ARG_MAX },
+ { "CHILD_MAX", SYSCONF, _SC_CHILD_MAX },
+ { "CLK_TCK", SYSCONF, _SC_CLK_TCK },
+ { "MQ_OPEN_MAX", SYSCONF, _SC_MQ_OPEN_MAX },
+ { "MQ_PRIO_MAX", SYSCONF, _SC_MQ_PRIO_MAX },
+ { "NGROUPS_MAX", SYSCONF, _SC_NGROUPS_MAX },
+ { "OPEN_MAX", SYSCONF, _SC_OPEN_MAX },
+ { "STREAM_MAX", SYSCONF, _SC_STREAM_MAX },
+ { "TZNAME_MAX", SYSCONF, _SC_TZNAME_MAX },
+ { "_POSIX_JOB_CONTROL", SYSCONF, _SC_JOB_CONTROL },
+ { "_POSIX_SAVED_IDS", SYSCONF, _SC_SAVED_IDS },
+ { "_POSIX_VERSION", SYSCONF, _SC_VERSION },
+
+ { "LINK_MAX", PATHCONF, _PC_LINK_MAX },
+ { "MAX_CANON", PATHCONF, _PC_MAX_CANON },
+ { "MAX_INPUT", PATHCONF, _PC_MAX_INPUT },
+ { "NAME_MAX", PATHCONF, _PC_NAME_MAX },
+ { "PATH_MAX", PATHCONF, _PC_PATH_MAX },
+ { "PIPE_BUF", PATHCONF, _PC_PIPE_BUF },
+ { "_POSIX_CHOWN_RESTRICTED", PATHCONF, _PC_CHOWN_RESTRICTED },
+ { "_POSIX_NO_TRUNC", PATHCONF, _PC_NO_TRUNC },
+ { "_POSIX_VDISABLE", PATHCONF, _PC_VDISABLE },
+
+ /* POSIX.1b Configurable System Variables */
+ { "PAGESIZE", SYSCONF, _SC_PAGESIZE },
+ { "_POSIX_ASYNCHRONOUS_IO", SYSCONF, _SC_ASYNCHRONOUS_IO },
+ { "_POSIX_FSYNC", SYSCONF, _SC_FSYNC },
+ { "_POSIX_MAPPED_FILES", SYSCONF, _SC_MAPPED_FILES },
+ { "_POSIX_MEMLOCK", SYSCONF, _SC_MEMLOCK },
+ { "_POSIX_MEMLOCK_RANGE", SYSCONF, _SC_MEMLOCK_RANGE },
+ { "_POSIX_MEMORY_PROTECTION", SYSCONF, _SC_MEMORY_PROTECTION },
+ { "_POSIX_MESSAGE_PASSING", SYSCONF, _SC_MESSAGE_PASSING },
+ { "_POSIX_MONOTONIC_CLOCK", SYSCONF, _SC_MONOTONIC_CLOCK },
+ { "_POSIX_PRIORITY_SCHEDULING", SYSCONF, _SC_PRIORITY_SCHEDULING },
+ { "_POSIX_SEMAPHORES", SYSCONF, _SC_SEMAPHORES },
+ { "_POSIX_SHARED_MEMORY_OBJECTS", SYSCONF, _SC_SHARED_MEMORY_OBJECTS },
+ { "_POSIX_SYNCHRONIZED_IO", SYSCONF, _SC_SYNCHRONIZED_IO },
+ { "_POSIX_TIMERS", SYSCONF, _SC_TIMERS },
+
+ { "_POSIX_SYNC_IO", PATHCONF, _PC_SYNC_IO },
+
+ /* POSIX.1c Configurable System Variables */
+ { "LOGIN_NAME_MAX", SYSCONF, _SC_LOGIN_NAME_MAX },
+ { "_POSIX_THREADS", SYSCONF, _SC_THREADS },
+
+ /* POSIX.1j Configurable System Variables */
+ { "_POSIX_BARRIERS", SYSCONF, _SC_BARRIERS },
+ { "_POSIX_READER_WRITER_LOCKS", SYSCONF, _SC_READER_WRITER_LOCKS },
+ { "_POSIX_SPIN_LOCKS", SYSCONF, _SC_SPIN_LOCKS },
+
+ /* XPG4.2 Configurable System Variables */
+ { "IOV_MAX", SYSCONF, _SC_IOV_MAX },
+ { "PAGE_SIZE", SYSCONF, _SC_PAGE_SIZE },
+ { "_XOPEN_SHM", SYSCONF, _SC_XOPEN_SHM },
+
+ /* X/Open CAE Spec. Issue 5 Version 2 Configurable System Variables */
+ { "FILESIZEBITS", PATHCONF, _PC_FILESIZEBITS },
+
+ /* POSIX.1-2001 XSI Option Group Configurable System Variables */
+ { "ATEXIT_MAX", SYSCONF, _SC_ATEXIT_MAX },
+
+ /* POSIX.1-2001 TSF Configurable System Variables */
+ { "GETGR_R_SIZE_MAX", SYSCONF, _SC_GETGR_R_SIZE_MAX },
+ { "GETPW_R_SIZE_MAX", SYSCONF, _SC_GETPW_R_SIZE_MAX },
+
+#ifdef _NETBSD_SOURCE
+ /* Commonly provided extensions */
+ { "NPROCESSORS_CONF", SYSCONF, _SC_NPROCESSORS_CONF },
+ { "NPROCESSORS_ONLN", SYSCONF, _SC_NPROCESSORS_ONLN },
+#endif /* _NETBSD_SOURCE */
+
+ { NULL, CONSTANT, 0L }
+};
+
+static int a_flag = 0; /* list all variables */
+
+int
+main(int argc, char **argv)
+{
+ int ch;
+ const struct conf_variable *cp;
+ const char *varname, *pathname;
+ int found;
+
+ setprogname(argv[0]);
+ (void)setlocale(LC_ALL, "");
+
+ while ((ch = getopt(argc, argv, "a")) != -1) {
+ switch (ch) {
+ case 'a':
+ a_flag = 1;
+ break;
+ case '?':
+ default:
+ usage();
+ }
+ }
+ argc -= optind;
+ argv += optind;
+
+ if (!a_flag) {
+ if (argc == 0)
+ usage();
+ varname = argv[0];
+ argc--;
+ argv++;
+ } else
+ varname = NULL;
+
+ if (argc > 1)
+ usage();
+ pathname = argv[0]; /* may be NULL */
+
+ found = 0;
+ for (cp = conf_table; cp->name != NULL; cp++) {
+ if (a_flag || strcmp(varname, cp->name) == 0) {
+ /*LINTED weird expression*/
+ if ((cp->type == PATHCONF) == (pathname != NULL)) {
+ printvar(cp, pathname);
+ found = 1;
+ } else if (!a_flag)
+ errx(EXIT_FAILURE,
+ "%s: invalid variable type", cp->name);
+ }
+ }
+
+ if (!a_flag && !found)
+ errx(EXIT_FAILURE, "%s: unknown variable", varname);
+
+ (void)fflush(stdout);
+ return ferror(stdout) ? EXIT_FAILURE : EXIT_SUCCESS;
+}
+
+static void
+print_longvar(const char *name, long value)
+{
+ if (a_flag)
+ (void)printf("%s = %ld\n", name, value);
+ else
+ (void)printf("%ld\n", value);
+}
+
+static void
+print_strvar(const char *name, const char *sval)
+{
+ if (a_flag)
+ (void)printf("%s = %s\n", name, sval);
+ else
+ (void)printf("%s\n", sval);
+}
+
+static void
+printvar(const struct conf_variable *cp, const char *pathname)
+{
+ size_t slen;
+ char *sval;
+ long val;
+
+ switch (cp->type) {
+ case CONSTANT:
+ print_longvar(cp->name, cp->value);
+ break;
+
+ case CONFSTR:
+ errno = 0;
+ slen = confstr((int)cp->value, NULL, 0);
+ if (slen == 0) {
+ if (errno != 0)
+
+out: err(EXIT_FAILURE, "confstr(%ld)", cp->value);
+ else
+ print_strvar(cp->name, "undefined");
+ }
+
+ if ((sval = malloc(slen)) == NULL)
+ err(EXIT_FAILURE, "Can't allocate %zu bytes", slen);
+
+ errno = 0;
+ if (confstr((int)cp->value, sval, slen) == 0) {
+ if (errno != 0)
+ goto out;
+ else
+ print_strvar(cp->name, "undefined");
+ } else
+ print_strvar(cp->name, sval);
+
+ free(sval);
+ break;
+
+ case SYSCONF:
+ errno = 0;
+ if ((val = sysconf((int)cp->value)) == -1) {
+ if (errno != 0)
+ err(EXIT_FAILURE, "sysconf(%ld)", cp->value);
+ print_strvar(cp->name, "undefined");
+ } else
+ print_longvar(cp->name, val);
+ break;
+
+ case PATHCONF:
+ errno = 0;
+ if ((val = pathconf(pathname, (int)cp->value)) == -1) {
+ if (errno != 0) {
+ if (a_flag && errno == EINVAL) {
+ /* Just skip invalid variables */
+ return;
+ }
+ err(EXIT_FAILURE, "pathconf(%s, %ld)",
+ pathname, cp->value);
+ /* NOTREACHED */
+ }
+
+ print_strvar(cp->name, "undefined");
+ } else
+ print_longvar(cp->name, val);
+ break;
+ }
+}
+
+
+static void
+usage(void)
+{
+ const char *p = getprogname();
+ (void)fprintf(stderr, "Usage: %s system_var\n\t%s -a\n"
+ "\t%s path_var pathname\n\t%s -a pathname\n", p, p, p, p);
+ exit(EXIT_FAILURE);
+}
diff --git a/usr.bin/grep/fastgrep.c b/usr.bin/grep/fastgrep.c
new file mode 100644
index 0000000..2fcd864
--- /dev/null
+++ b/usr.bin/grep/fastgrep.c
@@ -0,0 +1,336 @@
+/* $OpenBSD: util.c,v 1.36 2007/10/02 17:59:18 otto Exp $ */
+/* $FreeBSD: head/usr.bin/grep/fastgrep.c 211496 2010-08-19 09:28:59Z des $ */
+
+/*-
+ * Copyright (c) 1999 James Howard and Dag-Erling Coïdan Smørgrav
+ * Copyright (C) 2008 Gabor Kovesdan <gabor@FreeBSD.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+/*
+ * XXX: This file is a speed up for grep to cover the defects of the
+ * regex library. These optimizations should practically be implemented
+ * there keeping this code clean. This is a future TODO, but for the
+ * meantime, we need to use this workaround.
+ */
+
+#if HAVE_NBTOOL_CONFIG_H
+#include "nbtool_config.h"
+#endif
+
+#include <sys/cdefs.h>
+__RCSID("$NetBSD: fastgrep.c,v 1.5 2011/04/18 03:27:40 joerg Exp $");
+
+#include <limits.h>
+#include <stdbool.h>
+#include <stdlib.h>
+#include <string.h>
+#include <wchar.h>
+#include <wctype.h>
+
+#include "grep.h"
+
+static inline int grep_cmp(const unsigned char *, const unsigned char *, size_t);
+static inline void grep_revstr(unsigned char *, int);
+
+void
+fgrepcomp(fastgrep_t *fg, const char *pat)
+{
+ unsigned int i;
+
+ /* Initialize. */
+ fg->len = strlen(pat);
+ fg->bol = false;
+ fg->eol = false;
+ fg->reversed = false;
+
+ fg->pattern = (unsigned char *)grep_strdup(pat);
+
+ /* Preprocess pattern. */
+ for (i = 0; i <= UCHAR_MAX; i++)
+ fg->qsBc[i] = fg->len;
+ for (i = 1; i < fg->len; i++)
+ fg->qsBc[fg->pattern[i]] = fg->len - i;
+}
+
+/*
+ * Returns: -1 on failure, 0 on success
+ */
+int
+fastcomp(fastgrep_t *fg, const char *pat)
+{
+ unsigned int i;
+ int firstHalfDot = -1;
+ int firstLastHalfDot = -1;
+ int hasDot = 0;
+ int lastHalfDot = 0;
+ int shiftPatternLen;
+
+ /* Initialize. */
+ fg->len = strlen(pat);
+ fg->bol = false;
+ fg->eol = false;
+ fg->reversed = false;
+ fg->word = wflag;
+
+ /* Remove end-of-line character ('$'). */
+ if (fg->len > 0 && pat[fg->len - 1] == '$') {
+ fg->eol = true;
+ fg->len--;
+ }
+
+ /* Remove beginning-of-line character ('^'). */
+ if (pat[0] == '^') {
+ fg->bol = true;
+ fg->len--;
+ pat++;
+ }
+
+ if (fg->len >= 14 &&
+ memcmp(pat, "[[:<:]]", 7) == 0 &&
+ memcmp(pat + fg->len - 7, "[[:>:]]", 7) == 0) {
+ fg->len -= 14;
+ pat += 7;
+ /* Word boundary is handled separately in util.c */
+ fg->word = true;
+ }
+
+ /*
+ * pat has been adjusted earlier to not include '^', '$' or
+ * the word match character classes at the beginning and ending
+ * of the string respectively.
+ */
+ fg->pattern = grep_malloc(fg->len + 1);
+ memcpy(fg->pattern, pat, fg->len);
+ fg->pattern[fg->len] = '\0';
+
+ /* Look for ways to cheat...er...avoid the full regex engine. */
+ for (i = 0; i < fg->len; i++) {
+ /* Can still cheat? */
+ if (fg->pattern[i] == '.') {
+ hasDot = i;
+ if (i < fg->len / 2) {
+ if (firstHalfDot < 0)
+ /* Closest dot to the beginning */
+ firstHalfDot = i;
+ } else {
+ /* Closest dot to the end of the pattern. */
+ lastHalfDot = i;
+ if (firstLastHalfDot < 0)
+ firstLastHalfDot = i;
+ }
+ } else {
+ /* Free memory and let others know this is empty. */
+ free(fg->pattern);
+ fg->pattern = NULL;
+ return (-1);
+ }
+ }
+
+ /*
+ * Determine if a reverse search would be faster based on the placement
+ * of the dots.
+ */
+ if ((!(lflag || cflag)) && ((!(fg->bol || fg->eol)) &&
+ ((lastHalfDot) && ((firstHalfDot < 0) ||
+ ((fg->len - (lastHalfDot + 1)) < (size_t)firstHalfDot)))) &&
+ !oflag && !color) {
+ fg->reversed = true;
+ hasDot = fg->len - (firstHalfDot < 0 ?
+ firstLastHalfDot : firstHalfDot) - 1;
+ grep_revstr(fg->pattern, fg->len);
+ }
+
+ /*
+ * Normal Quick Search would require a shift based on the position the
+ * next character after the comparison is within the pattern. With
+ * wildcards, the position of the last dot effects the maximum shift
+ * distance.
+ * The closer to the end the wild card is the slower the search. A
+ * reverse version of this algorithm would be useful for wildcards near
+ * the end of the string.
+ *
+ * Examples:
+ * Pattern Max shift
+ * ------- ---------
+ * this 5
+ * .his 4
+ * t.is 3
+ * th.s 2
+ * thi. 1
+ */
+
+ /* Adjust the shift based on location of the last dot ('.'). */
+ shiftPatternLen = fg->len - hasDot;
+
+ /* Preprocess pattern. */
+ for (i = 0; i <= (signed)UCHAR_MAX; i++)
+ fg->qsBc[i] = shiftPatternLen;
+ for (i = hasDot + 1; i < fg->len; i++) {
+ fg->qsBc[fg->pattern[i]] = fg->len - i;
+ }
+
+ /*
+ * Put pattern back to normal after pre-processing to allow for easy
+ * comparisons later.
+ */
+ if (fg->reversed)
+ grep_revstr(fg->pattern, fg->len);
+
+ return (0);
+}
+
+int
+grep_search(fastgrep_t *fg, const unsigned char *data, size_t len, regmatch_t *pmatch)
+{
+ unsigned int j;
+ int ret = REG_NOMATCH;
+
+ if (pmatch->rm_so == (ssize_t)len)
+ return (ret);
+
+ if (fg->bol && pmatch->rm_so != 0) {
+ pmatch->rm_so = len;
+ pmatch->rm_eo = len;
+ return (ret);
+ }
+
+ /* No point in going farther if we do not have enough data. */
+ if (len < fg->len)
+ return (ret);
+
+ /* Only try once at the beginning or ending of the line. */
+ if (fg->bol || fg->eol) {
+ /* Simple text comparison. */
+ /* Verify data is >= pattern length before searching on it. */
+ if (len >= fg->len) {
+ /* Determine where in data to start search at. */
+ j = fg->eol ? len - fg->len : 0;
+ if (!((fg->bol && fg->eol) && (len != fg->len)))
+ if (grep_cmp(fg->pattern, data + j,
+ fg->len) == -1) {
+ pmatch->rm_so = j;
+ pmatch->rm_eo = j + fg->len;
+ ret = 0;
+ }
+ }
+ } else if (fg->reversed) {
+ /* Quick Search algorithm. */
+ j = len;
+ do {
+ if (grep_cmp(fg->pattern, data + j - fg->len,
+ fg->len) == -1) {
+ pmatch->rm_so = j - fg->len;
+ pmatch->rm_eo = j;
+ ret = 0;
+ break;
+ }
+ /* Shift if within bounds, otherwise, we are done. */
+ if (j == fg->len)
+ break;
+ j -= fg->qsBc[data[j - fg->len - 1]];
+ } while (j >= fg->len);
+ } else {
+ /* Quick Search algorithm. */
+ j = pmatch->rm_so;
+ do {
+ if (grep_cmp(fg->pattern, data + j, fg->len) == -1) {
+ pmatch->rm_so = j;
+ pmatch->rm_eo = j + fg->len;
+ ret = 0;
+ break;
+ }
+
+ /* Shift if within bounds, otherwise, we are done. */
+ if (j + fg->len == len)
+ break;
+ else
+ j += fg->qsBc[data[j + fg->len]];
+ } while (j <= (len - fg->len));
+ }
+
+ return (ret);
+}
+
+/*
+ * Returns: i >= 0 on failure (position that it failed)
+ * -1 on success
+ */
+static inline int
+grep_cmp(const unsigned char *pat, const unsigned char *data, size_t len)
+{
+ size_t size;
+ wchar_t *wdata, *wpat;
+ unsigned int i;
+
+ if (iflag) {
+ if ((size = mbstowcs(NULL, (const char *)data, 0)) ==
+ ((size_t) - 1))
+ return (-1);
+
+ wdata = grep_malloc(size * sizeof(wint_t));
+
+ if (mbstowcs(wdata, (const char *)data, size) ==
+ ((size_t) - 1))
+ return (-1);
+
+ if ((size = mbstowcs(NULL, (const char *)pat, 0)) ==
+ ((size_t) - 1))
+ return (-1);
+
+ wpat = grep_malloc(size * sizeof(wint_t));
+
+ if (mbstowcs(wpat, (const char *)pat, size) == ((size_t) - 1))
+ return (-1);
+ for (i = 0; i < len; i++) {
+ if ((towlower(wpat[i]) == towlower(wdata[i])) ||
+ ((grepbehave != GREP_FIXED) && wpat[i] == L'.'))
+ continue;
+ free(wpat);
+ free(wdata);
+ return (i);
+ }
+ } else {
+ for (i = 0; i < len; i++) {
+ if ((pat[i] == data[i]) || ((grepbehave != GREP_FIXED) &&
+ pat[i] == '.'))
+ continue;
+ return (i);
+ }
+ }
+ return (-1);
+}
+
+static inline void
+grep_revstr(unsigned char *str, int len)
+{
+ int i;
+ char c;
+
+ for (i = 0; i < len / 2; i++) {
+ c = str[i];
+ str[i] = str[len - i - 1];
+ str[len - i - 1] = c;
+ }
+}
diff --git a/usr.bin/grep/file.c b/usr.bin/grep/file.c
new file mode 100644
index 0000000..ef057ba
--- /dev/null
+++ b/usr.bin/grep/file.c
@@ -0,0 +1,276 @@
+/* $NetBSD: file.c,v 1.10 2018/08/12 09:03:21 christos Exp $ */
+/* $FreeBSD: head/usr.bin/grep/file.c 211496 2010-08-19 09:28:59Z des $ */
+/* $OpenBSD: file.c,v 1.11 2010/07/02 20:48:48 nicm Exp $ */
+
+/*-
+ * Copyright (c) 1999 James Howard and Dag-Erling Coïdan Smørgrav
+ * Copyright (C) 2008-2010 Gabor Kovesdan <gabor@FreeBSD.org>
+ * Copyright (C) 2010 Dimitry Andric <dimitry@andric.com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#if HAVE_NBTOOL_CONFIG_H
+#include "nbtool_config.h"
+#endif
+
+#include <sys/cdefs.h>
+__RCSID("$NetBSD: file.c,v 1.10 2018/08/12 09:03:21 christos Exp $");
+
+#include <sys/param.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#include <err.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <stddef.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <wchar.h>
+#include <wctype.h>
+
+#include "grep.h"
+
+#define MAXBUFSIZ (32 * 1024)
+#define LNBUFBUMP 80
+
+#ifndef WITHOUT_GZIP
+static gzFile gzbufdesc;
+#endif
+#ifndef WITHOUT_BZ2
+static BZFILE* bzbufdesc;
+#endif
+
+static unsigned char buffer[MAXBUFSIZ];
+static unsigned char *bufpos;
+static size_t bufrem;
+
+static unsigned char *lnbuf;
+static size_t lnbuflen;
+
+static inline int
+grep_refill(struct file *f)
+{
+ ssize_t nr = -1;
+ int bzerr;
+
+ bufpos = buffer;
+ bufrem = 0;
+
+#ifndef WITHOUT_GZIP
+ if (filebehave == FILE_GZIP) {
+ nr = gzread(gzbufdesc, buffer, MAXBUFSIZ);
+ if (nr == -1)
+ return -1;
+ }
+#endif
+#ifndef WITHOUT_BZ2
+ if (filebehave == FILE_BZIP && bzbufdesc != NULL) {
+ nr = BZ2_bzRead(&bzerr, bzbufdesc, buffer, MAXBUFSIZ);
+ switch (bzerr) {
+ case BZ_OK:
+ case BZ_STREAM_END:
+ /* No problem, nr will be okay */
+ break;
+ case BZ_DATA_ERROR_MAGIC:
+ /*
+ * As opposed to gzread(), which simply returns the
+ * plain file data, if it is not in the correct
+ * compressed format, BZ2_bzRead() instead aborts.
+ *
+ * So, just restart at the beginning of the file again,
+ * and use plain reads from now on.
+ */
+ BZ2_bzReadClose(&bzerr, bzbufdesc);
+ bzbufdesc = NULL;
+ if (lseek(f->fd, 0, SEEK_SET) == -1)
+ return (-1);
+ nr = read(f->fd, buffer, MAXBUFSIZ);
+ break;
+ default:
+ /* Make sure we exit with an error */
+ nr = -1;
+ }
+ if (nr == -1)
+ return -1;
+ }
+#endif
+ if (nr == -1) {
+ nr = read(f->fd, buffer, MAXBUFSIZ);
+ }
+
+ if (nr < 0)
+ return (-1);
+
+ bufrem = nr;
+ return (0);
+}
+
+static inline int
+grep_lnbufgrow(size_t newlen)
+{
+
+ if (lnbuflen < newlen) {
+ lnbuf = grep_realloc(lnbuf, newlen);
+ lnbuflen = newlen;
+ }
+
+ return (0);
+}
+
+char *
+grep_fgetln(struct file *f, size_t *lenp)
+{
+ unsigned char *p;
+ char *ret;
+ size_t len;
+ size_t off;
+ ptrdiff_t diff;
+
+ /* Fill the buffer, if necessary */
+ if (bufrem == 0 && grep_refill(f) != 0)
+ goto error;
+
+ if (bufrem == 0) {
+ /* Return zero length to indicate EOF */
+ *lenp = 0;
+ return ((char *)bufpos);
+ }
+
+ /* Look for a newline in the remaining part of the buffer */
+ if ((p = memchr(bufpos, line_sep, bufrem)) != NULL) {
+ ++p; /* advance over newline */
+ ret = (char *)bufpos;
+ len = p - bufpos;
+ bufrem -= len;
+ bufpos = p;
+ *lenp = len;
+ return (ret);
+ }
+
+ /* We have to copy the current buffered data to the line buffer */
+ for (len = bufrem, off = 0; ; len += bufrem) {
+ /* Make sure there is room for more data */
+ if (grep_lnbufgrow(len + LNBUFBUMP))
+ goto error;
+ memcpy(lnbuf + off, bufpos, len - off);
+ off = len;
+ if (grep_refill(f) != 0)
+ goto error;
+ if (bufrem == 0)
+ /* EOF: return partial line */
+ break;
+ if ((p = memchr(bufpos, line_sep, bufrem)) == NULL)
+ continue;
+ /* got it: finish up the line (like code above) */
+ ++p;
+ diff = p - bufpos;
+ len += diff;
+ if (grep_lnbufgrow(len))
+ goto error;
+ memcpy(lnbuf + off, bufpos, diff);
+ bufrem -= diff;
+ bufpos = p;
+ break;
+ }
+ *lenp = len;
+ return ((char *)lnbuf);
+
+error:
+ *lenp = 0;
+ return (NULL);
+}
+
+static inline struct file *
+grep_file_init(struct file *f)
+{
+
+#ifndef WITHOUT_GZIP
+ if (filebehave == FILE_GZIP &&
+ (gzbufdesc = gzdopen(f->fd, "r")) == NULL)
+ goto error;
+#endif
+
+#ifndef WITHOUT_BZ2
+ if (filebehave == FILE_BZIP &&
+ (bzbufdesc = BZ2_bzdopen(f->fd, "r")) == NULL)
+ goto error;
+#endif
+
+ /* Fill read buffer, also catches errors early */
+ if (grep_refill(f) != 0)
+ goto error;
+
+ /* Check for binary stuff, if necessary */
+ if (!nulldataflag && binbehave != BINFILE_TEXT &&
+ memchr(bufpos, '\0', bufrem) != NULL)
+ f->binary = true;
+
+ return (f);
+error:
+ close(f->fd);
+ free(f);
+ return (NULL);
+}
+
+/*
+ * Opens a file for processing.
+ */
+struct file *
+grep_open(const char *path)
+{
+ struct file *f;
+
+ f = grep_malloc(sizeof *f);
+ memset(f, 0, sizeof *f);
+ if (path == NULL) {
+ /* Processing stdin implies --line-buffered. */
+ lbflag = true;
+ f->fd = STDIN_FILENO;
+ } else if ((f->fd = open(path, O_RDONLY)) == -1) {
+ free(f);
+ return (NULL);
+ }
+
+ return (grep_file_init(f));
+}
+
+/*
+ * Closes a file.
+ */
+void
+grep_close(struct file *f)
+{
+
+ close(f->fd);
+
+ /* Reset read buffer and line buffer */
+ bufpos = buffer;
+ bufrem = 0;
+
+ free(lnbuf);
+ lnbuf = NULL;
+ lnbuflen = 0;
+}
diff --git a/usr.bin/grep/grep.1 b/usr.bin/grep/grep.1
new file mode 100644
index 0000000..7446cad
--- /dev/null
+++ b/usr.bin/grep/grep.1
@@ -0,0 +1,486 @@
+.\" $NetBSD: grep.1,v 1.4 2012/04/08 22:00:38 wiz Exp $
+.\" $FreeBSD: head/usr.bin/grep/grep.1 210652 2010-07-30 14:05:20Z joel $
+.\" $OpenBSD: grep.1,v 1.38 2010/04/05 06:30:59 jmc Exp $
+.\" Copyright (c) 1980, 1990, 1993
+.\" The Regents of the University of California. All rights reserved.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\" notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must reproduce the above copyright
+.\" notice, this list of conditions and the following disclaimer in the
+.\" documentation and/or other materials provided with the distribution.
+.\" 3. Neither the name of the University nor the names of its contributors
+.\" may be used to endorse or promote products derived from this software
+.\" without specific prior written permission.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+.\" ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+.\" SUCH DAMAGE.
+.\"
+.\" @(#)grep.1 8.3 (Berkeley) 4/18/94
+.\"
+.Dd April 19, 2011
+.Dt GREP 1
+.Os
+.Sh NAME
+.Nm grep , egrep , fgrep ,
+.Nm zgrep , zegrep , zfgrep
+.Nd file pattern searcher
+.Sh SYNOPSIS
+.Nm grep
+.Op Fl abcdDEFGHhIiJLlmnOopqRSsUVvwxZz
+.Op Fl A Ar num
+.Op Fl B Ar num
+.Op Fl C Ns Op Ar num
+.Op Fl e Ar pattern
+.Op Fl f Ar file
+.Op Fl Fl binary-files Ns = Ns Ar value
+.Op Fl Fl color Ns Op = Ns Ar when
+.Op Fl Fl colour Ns Op = Ns Ar when
+.Op Fl Fl context Ns Op = Ns Ar num
+.Op Fl Fl decompress
+.Op Fl Fl label
+.Op Fl Fl line-buffered
+.Op Ar pattern
+.Op Ar
+.Sh DESCRIPTION
+The
+.Nm grep
+utility searches any given input files,
+selecting lines that match one or more patterns.
+By default, a pattern matches an input line if the regular expression
+(RE) in the pattern matches the input line
+without its trailing newline.
+An empty expression matches every line.
+Each input line that matches at least one of the patterns is written
+to the standard output.
+.Pp
+.Nm grep
+is used for simple patterns and
+basic regular expressions
+.Pq BREs ;
+.Nm egrep
+can handle extended regular expressions
+.Pq EREs .
+See
+.Xr re_format 7
+for more information on regular expressions.
+.Nm fgrep
+is quicker than both
+.Nm grep
+and
+.Nm egrep ,
+but can only handle fixed patterns
+(i.e. it does not interpret regular expressions).
+Patterns may consist of one or more lines,
+allowing any of the pattern lines to match a portion of the input.
+.Pp
+.Nm zgrep ,
+.Nm zegrep ,
+and
+.Nm zfgrep
+act like
+.Nm grep ,
+.Nm egrep ,
+and
+.Nm fgrep ,
+respectively, but accept input files compressed with the
+.Xr compress 1
+or
+.Xr gzip 1
+compression utilities.
+.Pp
+The following options are available:
+.Bl -tag -width indent
+.It Fl A Ar num , Fl Fl after-context Ns = Ns Ar num
+Print
+.Ar num
+lines of trailing context after each match.
+See also the
+.Fl B
+and
+.Fl C
+options.
+.It Fl a , Fl Fl text
+Treat all files as ASCII text.
+Normally
+.Nm
+will simply print
+.Dq Binary file ... matches
+if files contain binary characters.
+Use of this option forces
+.Nm
+to output lines matching the specified pattern.
+.It Fl B Ar num , Fl Fl before-context Ns = Ns Ar num
+Print
+.Ar num
+lines of leading context before each match.
+See also the
+.Fl A
+and
+.Fl C
+options.
+.It Fl b , Fl Fl byte-offset
+The offset in bytes of a matched pattern is
+displayed in front of the respective matched line.
+.It Fl C Ns Op Ar num , Fl Fl context Ns = Ns Ar num
+Print
+.Ar num
+lines of leading and trailing context surrounding each match.
+The default is 2 and is equivalent to
+.Fl A
+.Ar 2
+.Fl B
+.Ar 2 .
+Note:
+no whitespace may be given between the option and its argument.
+.It Fl c , Fl Fl count
+Only a count of selected lines is written to standard output.
+.It Fl Fl colour Ns = Ns Op Ar when , Fl Fl color Ns = Ns Op Ar when
+Mark up the matching text with the expression stored in
+.Ev GREP_COLOR
+environment variable.
+The possible values of when can be `never', `always' or `auto'.
+.It Fl D Ar action , Fl Fl devices Ns = Ns Ar action
+Specify the demanded action for devices, FIFOs and sockets.
+The default action is `read', which means, that they are read
+as if they were normal files.
+If the action is set to `skip', devices will be silently skipped.
+.It Fl d Ar action , Fl Fl directories Ns = Ns Ar action
+Specify the demanded action for directories.
+It is `read' by default, which means that the directories
+are read in the same manner as normal files.
+Other possible values are `skip' to silently ignore the
+directories, and `recurse' to read them recursively, which
+has the same effect as the
+.Fl R
+and
+.Fl r
+option.
+.It Fl E , Fl Fl extended-regexp
+Interpret
+.Ar pattern
+as an extended regular expression
+(i.e. force
+.Nm grep
+to behave as
+.Nm egrep ) .
+.It Fl e Ar pattern , Fl Fl regexp Ns = Ns Ar pattern
+Specify a pattern used during the search of the input:
+an input line is selected if it matches any of the specified patterns.
+This option is most useful when multiple
+.Fl e
+options are used to specify multiple patterns,
+or when a pattern begins with a dash
+.Pq Sq - .
+.It Fl Fl exclude
+If specified, it excludes files matching the given
+filename pattern from the search.
+Note that
+.Fl Fl exclude
+patterns take priority over
+.Fl Fl include
+patterns, and if no
+.Fl Fl include
+pattern is specified, all files are searched that are
+not excluded.
+Patterns are matched to the full path specified,
+not only to the filename component.
+.It Fl Fl exclude-dir
+If
+.Fl R
+is specified, it excludes directories matching the
+given filename pattern from the search.
+Note that
+.Fl Fl exclude-dir
+patterns take priority over
+.Fl Fl include-dir
+patterns, and if no
+.Fl Fl include-dir
+pattern is specified, all directories are searched that are
+not excluded.
+.It Fl F , Fl Fl fixed-strings
+Interpret
+.Ar pattern
+as a set of fixed strings
+(i.e. force
+.Nm grep
+to behave as
+.Nm fgrep ) .
+.It Fl f Ar file , Fl Fl file Ns = Ns Ar file
+Read one or more newline separated patterns from
+.Ar file .
+Empty pattern lines match every input line.
+Newlines are not considered part of a pattern.
+If
+.Ar file
+is empty, nothing is matched.
+.It Fl G , Fl Fl basic-regexp
+Interpret
+.Ar pattern
+as a basic regular expression
+(i.e. force
+.Nm grep
+to behave as traditional
+.Nm grep ) .
+.It Fl H
+Always print filename headers with output lines.
+.It Fl h , Fl Fl no-filename
+Never print filename headers
+.Pq i.e. filenames
+with output lines.
+.It Fl Fl help
+Print a brief help message.
+.It Fl I
+Ignore binary files.
+This option is equivalent to
+.Fl Fl binary-file Ns = Ns Ar without-match
+option.
+.It Fl i , Fl Fl ignore-case
+Perform case insensitive matching.
+By default,
+.Nm grep
+is case sensitive.
+.It Fl Fl include
+If specified, only files matching the
+given filename pattern are searched.
+Note that
+.Fl Fl exclude
+patterns take priority over
+.Fl Fl include
+patterns.
+Patterns are matched to the full path specified,
+not only to the filename component.
+.It Fl Fl include-dir
+If
+.Fl R
+is specified, only directories matching the
+given filename pattern are searched.
+Note that
+.Fl Fl exclude-dir
+patterns take priority over
+.Fl Fl include-dir
+patterns.
+.It Fl J, Fl Fl bz2decompress
+Decompress the
+.Xr bzip2 1
+compressed file before looking for the text.
+.It Fl L , Fl Fl files-without-match
+Only the names of files not containing selected lines are written to
+standard output.
+Pathnames are listed once per file searched.
+If the standard input is searched, the string
+.Dq (standard input)
+is written.
+.It Fl l , Fl Fl files-with-matches
+Only the names of files containing selected lines are written to
+standard output.
+.Nm grep
+will only search a file until a match has been found,
+making searches potentially less expensive.
+Pathnames are listed once per file searched.
+If the standard input is searched, the string
+.Dq (standard input)
+is written.
+.It Fl Fl mmap
+Use
+.Xr mmap 2
+instead of
+.Xr read 2
+to read input, which can result in better performance under some
+circumstances but can cause undefined behaviour.
+.It Fl m Ar num, Fl Fl max-count Ns = Ns Ar num
+Stop reading the file after
+.Ar num
+matches.
+.It Fl n , Fl Fl line-number
+Each output line is preceded by its relative line number in the file,
+starting at line 1.
+The line number counter is reset for each file processed.
+This option is ignored if
+.Fl c ,
+.Fl L ,
+.Fl l ,
+or
+.Fl q
+is
+specified.
+.It Fl O
+If
+.Fl R
+is specified, follow symbolic links only if they were explicitly listed
+on the command line.
+The default is not to follow symbolic links.
+.It Fl o, Fl Fl only-matching
+Prints only the matching part of the lines.
+.It Fl p
+If
+.Fl R
+is specified, no symbolic links are followed.
+This is the default.
+.It Fl q , Fl Fl quiet , Fl Fl silent
+Quiet mode:
+suppress normal output.
+.Nm grep
+will only search a file until a match has been found,
+making searches potentially less expensive.
+.It Fl R , Fl r , Fl Fl recursive
+Recursively search subdirectories listed.
+.It Fl S
+If
+.Fl R
+is specified, all symbolic links are followed.
+The default is not to follow symbolic links.
+.It Fl s , Fl Fl no-messages
+Silent mode.
+Nonexistent and unreadable files are ignored
+(i.e. their error messages are suppressed).
+.It Fl U , Fl Fl binary
+Search binary files, but do not attempt to print them.
+.It Fl V , Fl Fl version
+Display version information and exit.
+.It Fl v , Fl Fl invert-match
+Selected lines are those
+.Em not
+matching any of the specified patterns.
+.It Fl w , Fl Fl word-regexp
+The expression is searched for as a word (as if surrounded by
+.Sq [[:<:]]
+and
+.Sq [[:>:]] ;
+see
+.Xr re_format 7 ) .
+.It Fl x , Fl Fl line-regexp
+Only input lines selected against an entire fixed string or regular
+expression are considered to be matching lines.
+.It Fl y
+Equivalent to
+.Fl i .
+Obsoleted.
+.It Fl Z , Fl Fl null
+Prints a zero-byte after the file name.
+.It Fl z , Fl Fl null-data
+Use the zero byte (ASCII NUL) as line separator.
+.It Fl Fl binary-files Ns = Ns Ar value
+Controls searching and printing of binary files.
+Options are
+.Ar binary ,
+the default: search binary files but do not print them;
+.Ar without-match :
+do not search binary files;
+and
+.Ar text :
+treat all files as text.
+.It Fl Fl decompress
+Detect input files compressed with
+.Xr bzip2 1
+or
+.Xr gzip 1
+and decompress them dynamically.
+This makes
+.Nm grep
+behave like
+.Nm zgrep .
+.It Fl Fl line-buffered
+Force output to be line buffered.
+By default, output is line buffered when standard output is a terminal
+and block buffered otherwise.
+.Pp
+.El
+If no file arguments are specified, the standard input is used.
+.Sh EXIT STATUS
+The
+.Nm grep
+utility exits with one of the following values:
+.Pp
+.Bl -tag -width flag -compact
+.It Li 0
+One or more lines were selected.
+.It Li 1
+No lines were selected.
+.It Li \*(Gt1
+An error occurred.
+.El
+.Sh EXAMPLES
+To find all occurrences of the word
+.Sq patricia
+in a file:
+.Pp
+.Dl $ grep 'patricia' myfile
+.Pp
+To find all occurrences of the pattern
+.Ql .Pp
+at the beginning of a line:
+.Pp
+.Dl $ grep '^\e.Pp' myfile
+.Pp
+The apostrophes ensure the entire expression is evaluated by
+.Nm grep
+instead of by the user's shell.
+The caret
+.Ql ^
+matches the null string at the beginning of a line,
+and the
+.Ql \e
+escapes the
+.Ql \&. ,
+which would otherwise match any character.
+.Pp
+To find all lines in a file which do not contain the words
+.Sq foo
+or
+.Sq bar :
+.Pp
+.Dl $ grep -v -e 'foo' -e 'bar' myfile
+.Pp
+A simple example of an extended regular expression:
+.Pp
+.Dl $ egrep '19|20|25' calendar
+.Pp
+Peruses the file
+.Sq calendar
+looking for either 19, 20, or 25.
+.Sh SEE ALSO
+.Xr ed 1 ,
+.Xr ex 1 ,
+.Xr gzip 1 ,
+.Xr sed 1 ,
+.Xr re_format 7
+.Sh STANDARDS
+The
+.Nm
+utility is compliant with the
+.St -p1003.1-2008
+specification.
+.Pp
+The flags
+.Op Fl AaBbCDdGHhIJLmoPRSUVwZ
+are extensions to that specification, and the behaviour of the
+.Fl f
+flag when used with an empty pattern file is left undefined.
+.Pp
+All long options are provided for compatibility with
+GNU versions of this utility.
+.Pp
+Historic versions of the
+.Nm grep
+utility also supported the flags
+.Op Fl ruy .
+This implementation supports those options;
+however, their use is strongly discouraged.
+.Sh HISTORY
+The
+.Nm grep
+command first appeared in
+.At v6 .
diff --git a/usr.bin/grep/grep.c b/usr.bin/grep/grep.c
new file mode 100644
index 0000000..bad2a73
--- /dev/null
+++ b/usr.bin/grep/grep.c
@@ -0,0 +1,722 @@
+/* $NetBSD: grep.c,v 1.15 2018/08/12 09:03:21 christos Exp $ */
+/* $FreeBSD: head/usr.bin/grep/grep.c 211519 2010-08-19 22:55:17Z delphij $ */
+/* $OpenBSD: grep.c,v 1.42 2010/07/02 22:18:03 tedu Exp $ */
+
+/*-
+ * Copyright (c) 1999 James Howard and Dag-Erling Coïdan Smørgrav
+ * Copyright (C) 2008-2009 Gabor Kovesdan <gabor@FreeBSD.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#if HAVE_NBTOOL_CONFIG_H
+#include "nbtool_config.h"
+#endif
+
+#include <sys/cdefs.h>
+__RCSID("$NetBSD: grep.c,v 1.15 2018/08/12 09:03:21 christos Exp $");
+
+#include <sys/stat.h>
+#include <sys/types.h>
+
+#include <ctype.h>
+#include <err.h>
+#include <errno.h>
+#include <getopt.h>
+#include <limits.h>
+#include <libgen.h>
+#include <locale.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "grep.h"
+
+#ifndef WITHOUT_NLS
+#include <nl_types.h>
+nl_catd catalog;
+#endif
+
+/*
+ * Default messags to use when NLS is disabled or no catalogue
+ * is found.
+ */
+const char *errstr[] = {
+ "",
+/* 1*/ "(standard input)",
+/* 2*/ "cannot read bzip2 compressed file",
+/* 3*/ "unknown %s option",
+/* 4*/ "usage: %s [-abcDEFGHhIiJLlmnOoPqRSsUVvwxZz] [-A num] [-B num] [-C[num]]\n",
+/* 5*/ "\t[-e pattern] [-f file] [--binary-files=value] [--color=when]\n",
+/* 6*/ "\t[--context[=num]] [--directories=action] [--label] [--line-buffered]\n",
+/* 7*/ "\t[pattern] [file ...]\n",
+/* 8*/ "Binary file %s matches\n",
+/* 9*/ "%s (BSD grep) %s\n",
+};
+
+/* Flags passed to regcomp() and regexec() */
+int cflags = 0;
+int eflags = REG_STARTEND;
+
+/* Searching patterns */
+unsigned int patterns, pattern_sz;
+char **pattern;
+regex_t *r_pattern;
+fastgrep_t *fg_pattern;
+
+/* Filename exclusion/inclusion patterns */
+unsigned int fpatterns, fpattern_sz;
+unsigned int dpatterns, dpattern_sz;
+struct epat *dpattern, *fpattern;
+
+/* For regex errors */
+char re_error[RE_ERROR_BUF + 1];
+
+/* Command-line flags */
+unsigned long long Aflag; /* -A x: print x lines trailing each match */
+unsigned long long Bflag; /* -B x: print x lines leading each match */
+bool Hflag; /* -H: always print file name */
+bool Lflag; /* -L: only show names of files with no matches */
+bool bflag; /* -b: show block numbers for each match */
+bool cflag; /* -c: only show a count of matching lines */
+bool hflag; /* -h: don't print filename headers */
+bool iflag; /* -i: ignore case */
+bool lflag; /* -l: only show names of files with matches */
+bool mflag; /* -m x: stop reading the files after x matches */
+unsigned long long mcount; /* count for -m */
+bool nflag; /* -n: show line numbers in front of matching lines */
+bool oflag; /* -o: print only matching part */
+bool qflag; /* -q: quiet mode (don't output anything) */
+bool sflag; /* -s: silent mode (ignore errors) */
+bool vflag; /* -v: only show non-matching lines */
+bool wflag; /* -w: pattern must start and end on word boundaries */
+bool xflag; /* -x: pattern must match entire line */
+bool lbflag; /* --line-buffered */
+bool nullflag; /* --null */
+bool nulldataflag; /* --null-data */
+unsigned char line_sep = '\n'; /* 0 for --null-data */
+char *label; /* --label */
+const char *color; /* --color */
+int grepbehave = GREP_BASIC; /* -EFGP: type of the regex */
+int binbehave = BINFILE_BIN; /* -aIU: handling of binary files */
+int filebehave = FILE_STDIO; /* -JZ: normal, gzip or bzip2 file */
+int devbehave = DEV_READ; /* -D: handling of devices */
+int dirbehave = DIR_READ; /* -dRr: handling of directories */
+int linkbehave = LINK_READ; /* -OpS: handling of symlinks */
+
+bool dexclude, dinclude; /* --exclude-dir and --include-dir */
+bool fexclude, finclude; /* --exclude and --include */
+
+enum {
+ BIN_OPT = CHAR_MAX + 1,
+ COLOR_OPT,
+ DECOMPRESS_OPT,
+ HELP_OPT,
+ MMAP_OPT,
+ LINEBUF_OPT,
+ LABEL_OPT,
+ R_EXCLUDE_OPT,
+ R_INCLUDE_OPT,
+ R_DEXCLUDE_OPT,
+ R_DINCLUDE_OPT
+};
+
+static inline const char *init_color(const char *);
+
+/* Housekeeping */
+int tail; /* lines left to print */
+bool notfound; /* file not found */
+
+extern char *__progname;
+
+/*
+ * Prints usage information and returns 2.
+ */
+__dead static void
+usage(void)
+{
+ fprintf(stderr, getstr(4), __progname);
+ fprintf(stderr, "%s", getstr(5));
+ fprintf(stderr, "%s", getstr(6));
+ fprintf(stderr, "%s", getstr(7));
+ exit(2);
+}
+
+static const char optstr[] =
+ "0123456789A:B:C:D:EFGHIJLOPSRUVZabcd:e:f:hilm:nopqrsuvwxyz";
+
+struct option long_options[] =
+{
+ {"binary-files", required_argument, NULL, BIN_OPT},
+#ifndef WITHOUT_GZIP
+ {"decompress", no_argument, NULL, DECOMPRESS_OPT},
+#endif
+ {"help", no_argument, NULL, HELP_OPT},
+ {"mmap", no_argument, NULL, MMAP_OPT},
+ {"line-buffered", no_argument, NULL, LINEBUF_OPT},
+ {"label", required_argument, NULL, LABEL_OPT},
+ {"color", optional_argument, NULL, COLOR_OPT},
+ {"colour", optional_argument, NULL, COLOR_OPT},
+ {"exclude", required_argument, NULL, R_EXCLUDE_OPT},
+ {"include", required_argument, NULL, R_INCLUDE_OPT},
+ {"exclude-dir", required_argument, NULL, R_DEXCLUDE_OPT},
+ {"include-dir", required_argument, NULL, R_DINCLUDE_OPT},
+ {"after-context", required_argument, NULL, 'A'},
+ {"text", no_argument, NULL, 'a'},
+ {"before-context", required_argument, NULL, 'B'},
+ {"byte-offset", no_argument, NULL, 'b'},
+ {"context", optional_argument, NULL, 'C'},
+ {"count", no_argument, NULL, 'c'},
+ {"devices", required_argument, NULL, 'D'},
+ {"directories", required_argument, NULL, 'd'},
+ {"extended-regexp", no_argument, NULL, 'E'},
+ {"regexp", required_argument, NULL, 'e'},
+ {"fixed-strings", no_argument, NULL, 'F'},
+ {"file", required_argument, NULL, 'f'},
+ {"basic-regexp", no_argument, NULL, 'G'},
+ {"no-filename", no_argument, NULL, 'h'},
+ {"with-filename", no_argument, NULL, 'H'},
+ {"ignore-case", no_argument, NULL, 'i'},
+#ifndef WITHOUT_BZ2
+ {"bz2decompress", no_argument, NULL, 'J'},
+#endif
+ {"files-with-matches", no_argument, NULL, 'l'},
+ {"files-without-match", no_argument, NULL, 'L'},
+ {"max-count", required_argument, NULL, 'm'},
+ {"line-number", no_argument, NULL, 'n'},
+ {"only-matching", no_argument, NULL, 'o'},
+ {"quiet", no_argument, NULL, 'q'},
+ {"silent", no_argument, NULL, 'q'},
+ {"recursive", no_argument, NULL, 'r'},
+ {"no-messages", no_argument, NULL, 's'},
+ {"binary", no_argument, NULL, 'U'},
+ {"unix-byte-offsets", no_argument, NULL, 'u'},
+ {"invert-match", no_argument, NULL, 'v'},
+ {"version", no_argument, NULL, 'V'},
+ {"word-regexp", no_argument, NULL, 'w'},
+ {"line-regexp", no_argument, NULL, 'x'},
+ {"null", no_argument, NULL, 'Z'},
+ {"null-data", no_argument, NULL, 'z'},
+ {NULL, no_argument, NULL, 0}
+};
+
+/*
+ * Adds a searching pattern to the internal array.
+ */
+static void
+add_pattern(char *pat, size_t len)
+{
+
+ /* TODO: Check for empty patterns and shortcut */
+
+ /* Increase size if necessary */
+ if (patterns == pattern_sz) {
+ pattern_sz *= 2;
+ pattern = grep_realloc(pattern, ++pattern_sz *
+ sizeof(*pattern));
+ }
+ if (len > 0 && pat[len - 1] == '\n')
+ --len;
+ /* pat may not be NUL-terminated */
+ pattern[patterns] = grep_malloc(len + 1);
+ memcpy(pattern[patterns], pat, len);
+ pattern[patterns][len] = '\0';
+ ++patterns;
+}
+
+/*
+ * Adds a file include/exclude pattern to the internal array.
+ */
+static void
+add_fpattern(const char *pat, int mode)
+{
+
+ /* Increase size if necessary */
+ if (fpatterns == fpattern_sz) {
+ fpattern_sz *= 2;
+ fpattern = grep_realloc(fpattern, ++fpattern_sz *
+ sizeof(struct epat));
+ }
+ fpattern[fpatterns].pat = grep_strdup(pat);
+ fpattern[fpatterns].mode = mode;
+ ++fpatterns;
+}
+
+/*
+ * Adds a directory include/exclude pattern to the internal array.
+ */
+static void
+add_dpattern(const char *pat, int mode)
+{
+
+ /* Increase size if necessary */
+ if (dpatterns == dpattern_sz) {
+ dpattern_sz *= 2;
+ dpattern = grep_realloc(dpattern, ++dpattern_sz *
+ sizeof(struct epat));
+ }
+ dpattern[dpatterns].pat = grep_strdup(pat);
+ dpattern[dpatterns].mode = mode;
+ ++dpatterns;
+}
+
+/*
+ * Reads searching patterns from a file and adds them with add_pattern().
+ */
+static void
+read_patterns(const char *fn)
+{
+ FILE *f;
+ char *line;
+ size_t len;
+ ssize_t rlen;
+
+ if ((f = fopen(fn, "r")) == NULL)
+ err(2, "%s", fn);
+ line = NULL;
+ len = 0;
+ while ((rlen = getline(&line, &len, f)) != -1)
+ add_pattern(line, *line == '\n' ? 0 : (size_t)rlen);
+ free(line);
+ if (ferror(f))
+ err(2, "%s", fn);
+ fclose(f);
+}
+
+static inline const char *
+init_color(const char *d)
+{
+ char *c;
+
+ c = getenv("GREP_COLOR");
+ return (c != NULL ? c : d);
+}
+
+int
+main(int argc, char *argv[])
+{
+ char **aargv, **eargv, *eopts;
+ char *ep;
+ unsigned long long l;
+ unsigned int aargc, eargc, i, j;
+ int c, lastc, needpattern, newarg, prevoptind;
+
+ setlocale(LC_ALL, "");
+
+#ifndef WITHOUT_NLS
+ catalog = catopen("grep", NL_CAT_LOCALE);
+#endif
+
+ /* Check what is the program name of the binary. In this
+ way we can have all the funcionalities in one binary
+ without the need of scripting and using ugly hacks. */
+ switch (__progname[0]) {
+ case 'e':
+ grepbehave = GREP_EXTENDED;
+ break;
+ case 'f':
+ grepbehave = GREP_FIXED;
+ break;
+ case 'g':
+ grepbehave = GREP_BASIC;
+ break;
+#ifndef WITHOUT_GZIP
+ case 'z':
+ filebehave = FILE_GZIP;
+ switch(__progname[1]) {
+ case 'e':
+ grepbehave = GREP_EXTENDED;
+ break;
+ case 'f':
+ grepbehave = GREP_FIXED;
+ break;
+ case 'g':
+ grepbehave = GREP_BASIC;
+ break;
+ }
+ break;
+#endif
+ }
+
+ lastc = '\0';
+ newarg = 1;
+ prevoptind = 1;
+ needpattern = 1;
+
+ eopts = getenv("GREP_OPTIONS");
+
+ /* support for extra arguments in GREP_OPTIONS */
+ eargc = 0;
+ if (eopts != NULL) {
+ char *str;
+
+ /* make an estimation of how many extra arguments we have */
+ for (j = 0; j < strlen(eopts); j++)
+ if (eopts[j] == ' ')
+ eargc++;
+
+ eargv = (char **)grep_malloc(sizeof(char *) * (eargc + 1));
+
+ eargc = 0;
+ /* parse extra arguments */
+ while ((str = strsep(&eopts, " ")) != NULL)
+ eargv[eargc++] = grep_strdup(str);
+
+ aargv = (char **)grep_calloc(eargc + argc + 1,
+ sizeof(char *));
+
+ aargv[0] = argv[0];
+ for (i = 0; i < eargc; i++)
+ aargv[i + 1] = eargv[i];
+ for (j = 1; j < (unsigned int)argc; j++, i++)
+ aargv[i + 1] = argv[j];
+
+ aargc = eargc + argc;
+ } else {
+ aargv = argv;
+ aargc = argc;
+ }
+
+ while (((c = getopt_long(aargc, aargv, optstr, long_options, NULL)) !=
+ -1)) {
+ switch (c) {
+ case '0': case '1': case '2': case '3': case '4':
+ case '5': case '6': case '7': case '8': case '9':
+ if (newarg || !isdigit(lastc))
+ Aflag = 0;
+ else if (Aflag > LLONG_MAX / 10) {
+ errno = ERANGE;
+ err(2, NULL);
+ }
+ Aflag = Bflag = (Aflag * 10) + (c - '0');
+ break;
+ case 'C':
+ if (optarg == NULL) {
+ Aflag = Bflag = 2;
+ break;
+ }
+ /* FALLTHROUGH */
+ case 'A':
+ /* FALLTHROUGH */
+ case 'B':
+ errno = 0;
+ l = strtoull(optarg, &ep, 10);
+ if (((errno == ERANGE) && (l == ULLONG_MAX)) ||
+ ((errno == EINVAL) && (l == 0)))
+ err(2, NULL);
+ else if (ep[0] != '\0') {
+ errno = EINVAL;
+ err(2, NULL);
+ }
+ if (c == 'A')
+ Aflag = l;
+ else if (c == 'B')
+ Bflag = l;
+ else
+ Aflag = Bflag = l;
+ break;
+ case 'a':
+ binbehave = BINFILE_TEXT;
+ break;
+ case 'b':
+ bflag = true;
+ break;
+ case 'c':
+ cflag = true;
+ break;
+ case 'D':
+ if (strcasecmp(optarg, "skip") == 0)
+ devbehave = DEV_SKIP;
+ else if (strcasecmp(optarg, "read") == 0)
+ devbehave = DEV_READ;
+ else
+ errx(2, getstr(3), "--devices");
+ break;
+ case 'd':
+ if (strcasecmp("recurse", optarg) == 0) {
+ Hflag = true;
+ dirbehave = DIR_RECURSE;
+ } else if (strcasecmp("skip", optarg) == 0)
+ dirbehave = DIR_SKIP;
+ else if (strcasecmp("read", optarg) == 0)
+ dirbehave = DIR_READ;
+ else
+ errx(2, getstr(3), "--directories");
+ break;
+ case 'E':
+ grepbehave = GREP_EXTENDED;
+ break;
+ case 'e':
+ add_pattern(optarg, strlen(optarg));
+ needpattern = 0;
+ break;
+ case 'F':
+ grepbehave = GREP_FIXED;
+ break;
+ case 'f':
+ read_patterns(optarg);
+ needpattern = 0;
+ break;
+ case 'G':
+ grepbehave = GREP_BASIC;
+ break;
+ case 'H':
+ Hflag = true;
+ break;
+ case 'h':
+ Hflag = false;
+ hflag = true;
+ break;
+ case 'I':
+ binbehave = BINFILE_SKIP;
+ break;
+ case 'i':
+ case 'y':
+ iflag = true;
+ cflags |= REG_ICASE;
+ break;
+#ifndef WITHOUT_BZ2
+ case 'J':
+ filebehave = FILE_BZIP;
+ break;
+#endif
+ case 'L':
+ lflag = false;
+ Lflag = true;
+ break;
+ case 'l':
+ Lflag = false;
+ lflag = true;
+ break;
+ case 'm':
+ mflag = true;
+ errno = 0;
+ mcount = strtoull(optarg, &ep, 10);
+ if (((errno == ERANGE) && (mcount == ULLONG_MAX)) ||
+ ((errno == EINVAL) && (mcount == 0)))
+ err(2, NULL);
+ else if (ep[0] != '\0') {
+ errno = EINVAL;
+ err(2, NULL);
+ }
+ break;
+ case 'n':
+ nflag = true;
+ break;
+ case 'O':
+ linkbehave = LINK_EXPLICIT;
+ break;
+ case 'o':
+ oflag = true;
+ break;
+ case 'p':
+ linkbehave = LINK_SKIP;
+ break;
+ case 'q':
+ qflag = true;
+ break;
+ case 'S':
+ linkbehave = LINK_READ;
+ break;
+ case 'R':
+ case 'r':
+ dirbehave = DIR_RECURSE;
+ Hflag = true;
+ break;
+ case 's':
+ sflag = true;
+ break;
+ case 'U':
+ binbehave = BINFILE_BIN;
+ break;
+ case 'u':
+ case MMAP_OPT:
+ /* noop, compatibility */
+ break;
+ case 'V':
+ printf(getstr(9), __progname, VERSION);
+ exit(0);
+ case 'v':
+ vflag = true;
+ break;
+ case 'w':
+ wflag = true;
+ break;
+ case 'x':
+ xflag = true;
+ break;
+ case 'Z':
+ nullflag = true;
+ break;
+ case 'z':
+ nulldataflag = true;
+ line_sep = '\0';
+ break;
+ case BIN_OPT:
+ if (strcasecmp("binary", optarg) == 0)
+ binbehave = BINFILE_BIN;
+ else if (strcasecmp("without-match", optarg) == 0)
+ binbehave = BINFILE_SKIP;
+ else if (strcasecmp("text", optarg) == 0)
+ binbehave = BINFILE_TEXT;
+ else
+ errx(2, getstr(3), "--binary-files");
+ break;
+ case COLOR_OPT:
+ color = NULL;
+ if (optarg == NULL || strcasecmp("auto", optarg) == 0 ||
+ strcasecmp("tty", optarg) == 0 ||
+ strcasecmp("if-tty", optarg) == 0) {
+ char *term;
+
+ term = getenv("TERM");
+ if (isatty(STDOUT_FILENO) && term != NULL &&
+ strcasecmp(term, "dumb") != 0)
+ color = init_color("01;31");
+ } else if (strcasecmp("always", optarg) == 0 ||
+ strcasecmp("yes", optarg) == 0 ||
+ strcasecmp("force", optarg) == 0) {
+ color = init_color("01;31");
+ } else if (strcasecmp("never", optarg) != 0 &&
+ strcasecmp("none", optarg) != 0 &&
+ strcasecmp("no", optarg) != 0)
+ errx(2, getstr(3), "--color");
+ break;
+#ifndef WITHOUT_GZIP
+ case DECOMPRESS_OPT:
+ filebehave = FILE_GZIP;
+ break;
+#endif
+ case LABEL_OPT:
+ label = optarg;
+ break;
+ case LINEBUF_OPT:
+ lbflag = true;
+ break;
+ case R_INCLUDE_OPT:
+ finclude = true;
+ add_fpattern(optarg, INCL_PAT);
+ break;
+ case R_EXCLUDE_OPT:
+ fexclude = true;
+ add_fpattern(optarg, EXCL_PAT);
+ break;
+ case R_DINCLUDE_OPT:
+ dinclude = true;
+ add_dpattern(optarg, INCL_PAT);
+ break;
+ case R_DEXCLUDE_OPT:
+ dexclude = true;
+ add_dpattern(optarg, EXCL_PAT);
+ break;
+ case HELP_OPT:
+ default:
+ usage();
+ }
+ lastc = c;
+ newarg = optind != prevoptind;
+ prevoptind = optind;
+ }
+ aargc -= optind;
+ aargv += optind;
+
+ /* Fail if we don't have any pattern */
+ if (aargc == 0 && needpattern)
+ usage();
+
+ /* Process patterns from command line */
+ if (aargc != 0 && needpattern) {
+ add_pattern(*aargv, strlen(*aargv));
+ --aargc;
+ ++aargv;
+ }
+
+ switch (grepbehave) {
+ case GREP_FIXED:
+ case GREP_BASIC:
+ break;
+ case GREP_EXTENDED:
+ cflags |= REG_EXTENDED;
+ break;
+ default:
+ /* NOTREACHED */
+ usage();
+ }
+
+ fg_pattern = grep_calloc(patterns, sizeof(*fg_pattern));
+ r_pattern = grep_calloc(patterns, sizeof(*r_pattern));
+/*
+ * XXX: fgrepcomp() and fastcomp() are workarounds for regexec() performance.
+ * Optimizations should be done there.
+ */
+ /* Check if cheating is allowed (always is for fgrep). */
+ if (grepbehave == GREP_FIXED) {
+ for (i = 0; i < patterns; ++i)
+ fgrepcomp(&fg_pattern[i], pattern[i]);
+ } else {
+ for (i = 0; i < patterns; ++i) {
+ if (fastcomp(&fg_pattern[i], pattern[i])) {
+ /* Fall back to full regex library */
+ c = regcomp(&r_pattern[i], pattern[i], cflags);
+ if (c != 0) {
+ regerror(c, &r_pattern[i], re_error,
+ RE_ERROR_BUF);
+ errx(2, "%s", re_error);
+ }
+ }
+ }
+ }
+
+ if (lbflag) {
+#ifdef _IOLBF
+ setvbuf(stdout, NULL, _IOLBF, 0);
+#else
+ setlinebuf(stdout);
+#endif
+ }
+
+ if ((aargc == 0 || aargc == 1) && !Hflag)
+ hflag = true;
+
+ if (aargc == 0)
+ exit(!procfile("-"));
+
+ if (dirbehave == DIR_RECURSE)
+ c = grep_tree(aargv);
+ else
+ for (c = 0; aargc--; ++aargv) {
+ if ((finclude || fexclude) && !file_matching(*aargv))
+ continue;
+ c+= procfile(*aargv);
+ }
+
+#ifndef WITHOUT_NLS
+ catclose(catalog);
+#endif
+
+ /* Find out the correct return value according to the
+ results and the command line option. */
+ exit(c ? (notfound ? (qflag ? 0 : 2) : 0) : (notfound ? 2 : 1));
+}
diff --git a/usr.bin/grep/grep.h b/usr.bin/grep/grep.h
new file mode 100644
index 0000000..b7ef7fa
--- /dev/null
+++ b/usr.bin/grep/grep.h
@@ -0,0 +1,162 @@
+/* $NetBSD: grep.h,v 1.10 2018/08/12 09:03:21 christos Exp $ */
+/* $OpenBSD: grep.h,v 1.15 2010/04/05 03:03:55 tedu Exp $ */
+/* $FreeBSD: head/usr.bin/grep/grep.h 211496 2010-08-19 09:28:59Z des $ */
+
+/*-
+ * Copyright (c) 1999 James Howard and Dag-Erling Coïdan Smørgrav
+ * Copyright (c) 2008-2009 Gabor Kovesdan <gabor@FreeBSD.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#ifndef WITHOUT_BZ2
+#include <bzlib.h>
+#endif
+#include <limits.h>
+#include <regex.h>
+#include <stdbool.h>
+#include <stdio.h>
+#ifndef WITHOUT_GZIP
+#include <zlib.h>
+#endif
+
+#ifdef WITHOUT_NLS
+#define getstr(n) errstr[n]
+#else
+#include <nl_types.h>
+
+extern nl_catd catalog;
+#define getstr(n) catgets(catalog, 1, n, errstr[n])
+#endif
+
+extern const char *errstr[];
+
+#define VERSION "2.5.1-FreeBSD"
+
+#define GREP_FIXED 0
+#define GREP_BASIC 1
+#define GREP_EXTENDED 2
+
+#define BINFILE_BIN 0
+#define BINFILE_SKIP 1
+#define BINFILE_TEXT 2
+
+#define FILE_STDIO 0
+#define FILE_GZIP 1
+#define FILE_BZIP 2
+
+#define DIR_READ 0
+#define DIR_SKIP 1
+#define DIR_RECURSE 2
+
+#define DEV_READ 0
+#define DEV_SKIP 1
+
+#define LINK_READ 0
+#define LINK_EXPLICIT 1
+#define LINK_SKIP 2
+
+#define EXCL_PAT 0
+#define INCL_PAT 1
+
+#define MAX_LINE_MATCHES 32
+
+struct file {
+ int fd;
+ bool binary;
+};
+
+struct str {
+ off_t off;
+ size_t len;
+ char *dat;
+ char *file;
+ int line_no;
+};
+
+struct epat {
+ char *pat;
+ int mode;
+};
+
+typedef struct {
+ size_t len;
+ unsigned char *pattern;
+ int qsBc[UCHAR_MAX + 1];
+ /* flags */
+ bool bol;
+ bool eol;
+ bool reversed;
+ bool word;
+} fastgrep_t;
+
+/* Flags passed to regcomp() and regexec() */
+extern int cflags, eflags;
+
+/* Command line flags */
+extern bool Eflag, Fflag, Gflag, Hflag, Lflag,
+ bflag, cflag, hflag, iflag, lflag, mflag, nflag, oflag,
+ qflag, sflag, vflag, wflag, xflag;
+extern bool dexclude, dinclude, fexclude, finclude, lbflag, nullflag, nulldataflag;
+extern unsigned char line_sep;
+extern unsigned long long Aflag, Bflag, mcount;
+extern char *label;
+extern const char *color;
+extern int binbehave, devbehave, dirbehave, filebehave, grepbehave, linkbehave;
+
+extern bool notfound;
+extern int tail;
+extern unsigned int dpatterns, fpatterns, patterns;
+extern char **pattern;
+extern struct epat *dpattern, *fpattern;
+extern regex_t *er_pattern, *r_pattern;
+extern fastgrep_t *fg_pattern;
+
+/* For regex errors */
+#define RE_ERROR_BUF 512
+extern char re_error[RE_ERROR_BUF + 1]; /* Seems big enough */
+
+/* util.c */
+bool file_matching(const char *fname);
+int procfile(const char *fn);
+int grep_tree(char **argv);
+void *grep_malloc(size_t size);
+void *grep_calloc(size_t nmemb, size_t size);
+void *grep_realloc(void *ptr, size_t size);
+char *grep_strdup(const char *str);
+void printline(struct str *line, int sep, regmatch_t *matches, int m);
+
+/* queue.c */
+void enqueue(struct str *x);
+void printqueue(void);
+void clearqueue(void);
+
+/* file.c */
+void grep_close(struct file *f);
+struct file *grep_open(const char *path);
+char *grep_fgetln(struct file *f, size_t *len);
+
+/* fastgrep.c */
+int fastcomp(fastgrep_t *, const char *);
+void fgrepcomp(fastgrep_t *, const char *);
+int grep_search(fastgrep_t *, const unsigned char *, size_t, regmatch_t *);
diff --git a/usr.bin/grep/nls/C.msg b/usr.bin/grep/nls/C.msg
new file mode 100644
index 0000000..d07aea6
--- /dev/null
+++ b/usr.bin/grep/nls/C.msg
@@ -0,0 +1,13 @@
+$ $FreeBSD: head/usr.bin/grep/nls/C.msg 210622 2010-07-29 18:02:57Z gabor $
+$
+$set 1
+$quote "
+1 "(standard input)"
+2 "cannot read bzip2 compressed file"
+3 "unknown %s option"
+4 "usage: %s [-abcDEFGHhIiJLlmnOoPqRSsUVvwxZz] [-A num] [-B num] [-C[num]]\n"
+5 "\t[-e pattern] [-f file] [--binary-files=value] [--color=when]\n"
+6 "\t[--context[=num]] [--directories=action] [--label] [--line-buffered]\n"
+7 "\t[pattern] [file ...]\n"
+8 "Binary file %s matches\n"
+9 "%s (BSD grep) %s\n"
diff --git a/usr.bin/grep/nls/es_ES.ISO8859-1.msg b/usr.bin/grep/nls/es_ES.ISO8859-1.msg
new file mode 100644
index 0000000..441ba01
--- /dev/null
+++ b/usr.bin/grep/nls/es_ES.ISO8859-1.msg
@@ -0,0 +1,14 @@
+$ $NetBSD: es_ES.ISO8859-1.msg,v 1.2 2011/04/18 22:46:48 joerg Exp $
+$ $FreeBSD: head/usr.bin/grep/nls/es_ES.ISO8859-1.msg 210622 2010-07-29 18:02:57Z gabor $
+$
+$set 1
+$quote "
+1 "(entrada estándar)"
+2 "no se puede leer el fichero comprimido bzip2"
+3 "opción desconocida de %s"
+4 "uso: %s [-abcDEFGHhIiJLlmnOoPqRSsUVvwxZz] [-A no] [-B no] [-C[no]]\n"
+5 "\t[-e pauta] [-f fichero] [--binary-files=valor] [--color=cuando]\n"
+6 "\t[--context[=no]] [--directories=acción] [--label] [--line-buffered]\n"
+7 "\t[pauta] [fichero ...]\n"
+8 "fichero binario %s se ajusta\n"
+9 "%s (BSD grep) %s\n"
diff --git a/usr.bin/grep/nls/gl_ES.ISO8859-1.msg b/usr.bin/grep/nls/gl_ES.ISO8859-1.msg
new file mode 100644
index 0000000..ab50cea
--- /dev/null
+++ b/usr.bin/grep/nls/gl_ES.ISO8859-1.msg
@@ -0,0 +1,14 @@
+$ $NetBSD: gl_ES.ISO8859-1.msg,v 1.2 2011/04/18 22:46:48 joerg Exp $
+$ $FreeBSD: head/usr.bin/grep/nls/gl_ES.ISO8859-1.msg 210622 2010-07-29 18:02:57Z gabor $
+$
+$set 1
+$quote "
+1 "(entrada estándar)"
+2 "non se pode ler o ficheiro comprimido bzip2"
+3 "opción descoñecida de %s"
+4 "uso: %s [-abcDEFGHhIiJLlmnOoPqRSsUVvwxZz] [-A no] [-B no] [-C[no]]\n"
+5 "\t[-e pauta] [-f ficheiro] [--binary-files=valor] [--color=cando]\n"
+6 "\t[--context[=no]] [--directories=acción] [--label] [--line-buffered]\n"
+7 "\t[pauta] [ficheiro ...]\n"
+8 "ficheiro binario %s conforma\n"
+9 "%s (BSD grep) %s\n"
diff --git a/usr.bin/grep/nls/hu_HU.ISO8859-2.msg b/usr.bin/grep/nls/hu_HU.ISO8859-2.msg
new file mode 100644
index 0000000..69dc2ec
--- /dev/null
+++ b/usr.bin/grep/nls/hu_HU.ISO8859-2.msg
@@ -0,0 +1,14 @@
+$ $NetBSD: hu_HU.ISO8859-2.msg,v 1.2 2011/04/18 22:46:48 joerg Exp $
+$ $FreeBSD: head/usr.bin/grep/nls/hu_HU.ISO8859-2.msg 210622 2010-07-29 18:02:57Z gabor $
+$
+$set 1
+$quote "
+1 "(szabványos bemenet)"
+2 "bzip2 tömörített fájl nem olvasható"
+3 "ismeretlen %s opció"
+4 "használat: %s [-abcDEFGHhIiJLlmnOoPqRSsUVvwxZz] [-A szám] [-B szám] [-C[szám]]\n"
+5 "\t[-e minta] [-f fájl] [--binary-files=érték] [--color=mikor]\n"
+6 "\t[--context[=szám]] [--directories=mûvelet] [--label] [--line-buffered]\n"
+7 "\t[minta] [fájl ...]\n"
+8 "%s bináris fájl illeszkedik\n"
+9 "%s (BSD grep) %s\n"
diff --git a/usr.bin/grep/nls/ja_JP.SJIS.msg b/usr.bin/grep/nls/ja_JP.SJIS.msg
new file mode 100644
index 0000000..99357b6
--- /dev/null
+++ b/usr.bin/grep/nls/ja_JP.SJIS.msg
@@ -0,0 +1,14 @@
+$ $NetBSD: ja_JP.SJIS.msg,v 1.2 2011/04/18 22:46:48 joerg Exp $
+$ $FreeBSD: head/usr.bin/grep/nls/ja_JP.SJIS.msg 210622 2010-07-29 18:02:57Z gabor $
+$
+$set 1
+$quote "
+1 "(•W€“ü—Í)"
+2 "bzip2 ˆ³kƒtƒ@ƒCƒ‹‚ð“Ç‚Ýž‚Þ‚±‚Æ‚ª‚Å‚«‚Ü‚¹‚ñ"
+3 "%s ƒIƒvƒVƒ‡ƒ“‚ÌŽw’è’l‚ÉŒë‚肪‚ ‚è‚Ü‚·"
+4 "Žg‚¢•û: %s [-abcDEFGHhIiJLlmnOoPqRSsUVvwxZz] [-A ”Žš] [-B ”Žš] [-C[”Žš]]\n"
+5 "\t[-e ƒpƒ^[ƒ“] [-f ƒtƒ@ƒCƒ‹–¼] [--binary-files=’l] [--color=’l]\n"
+6 "\t[--context[=”Žš]] [--directories=“®ì] [--label] [--line-buffered]\n"
+7 "\t[ƒpƒ^[ƒ“] [ƒtƒ@ƒCƒ‹–¼ ...]\n"
+8 "ƒoƒCƒiƒŠƒtƒ@ƒCƒ‹ %s ‚Ƀ}ƒbƒ`‚µ‚Ü‚µ‚½\n"
+9 "%s (BSD grep) %s\n"
diff --git a/usr.bin/grep/nls/ja_JP.UTF-8.msg b/usr.bin/grep/nls/ja_JP.UTF-8.msg
new file mode 100644
index 0000000..3d4aed3
--- /dev/null
+++ b/usr.bin/grep/nls/ja_JP.UTF-8.msg
@@ -0,0 +1,14 @@
+$ $NetBSD: ja_JP.UTF-8.msg,v 1.2 2011/04/18 22:46:48 joerg Exp $
+$ $FreeBSD: head/usr.bin/grep/nls/ja_JP.UTF-8.msg 210622 2010-07-29 18:02:57Z gabor $
+$
+$set 1
+$quote "
+1 "(標準入力)"
+2 "bzip2 圧縮ファイルを読ã¿è¾¼ã‚€ã“ã¨ãŒã§ãã¾ã›ã‚“"
+3 "%s オプションã®æŒ‡å®šå€¤ã«èª¤ã‚ŠãŒã‚ã‚Šã¾ã™"
+4 "使ã„æ–¹: %s [-abcDEFGHhIiJLlmnOoPqRSsUVvwxZz] [-A æ•°å­—] [-B æ•°å­—] [-C[æ•°å­—]]\n"
+5 "\t[-e パターン] [-f ファイルå] [--binary-files=値] [--color=値]\n"
+6 "\t[--context[=数字]] [--directories=動作] [--label] [--line-buffered]\n"
+7 "\t[パターン] [ファイルå ...]\n"
+8 "ãƒã‚¤ãƒŠãƒªãƒ•ã‚¡ã‚¤ãƒ« %s ã«ãƒžãƒƒãƒã—ã¾ã—ãŸ\n"
+9 "%s (BSD grep) %s\n"
diff --git a/usr.bin/grep/nls/ja_JP.eucJP.msg b/usr.bin/grep/nls/ja_JP.eucJP.msg
new file mode 100644
index 0000000..08e0e7d
--- /dev/null
+++ b/usr.bin/grep/nls/ja_JP.eucJP.msg
@@ -0,0 +1,14 @@
+$ $NetBSD: ja_JP.eucJP.msg,v 1.2 2011/04/18 22:46:48 joerg Exp $
+$ $FreeBSD: head/usr.bin/grep/nls/ja_JP.eucJP.msg 210622 2010-07-29 18:02:57Z gabor $
+$
+$set 1
+$quote "
+1 "(ɸ½àÆþÎÏ)"
+2 "bzip2 °µ½Ì¥Õ¥¡¥¤¥ë¤òÆɤ߹þ¤à¤³¤È¤¬¤Ç¤­¤Þ¤»¤ó"
+3 "%s ¥ª¥×¥·¥ç¥ó¤Î»ØÄêÃͤ˸í¤ê¤¬¤¢¤ê¤Þ¤¹"
+4 "»È¤¤Êý: %s [-abcDEFGHhIiJLlmnOoPqRSsUVvwxZz] [-A ¿ô»ú] [-B ¿ô»ú] [-C[¿ô»ú]]\n"
+5 "\t[-e ¥Ñ¥¿¡¼¥ó] [-f ¥Õ¥¡¥¤¥ë̾] [--binary-files=ÃÍ] [--color=ÃÍ]\n"
+6 "\t[--context[=¿ô»ú]] [--directories=Æ°ºî] [--label] [--line-buffered]\n"
+7 "\t[¥Ñ¥¿¡¼¥ó] [¥Õ¥¡¥¤¥ë̾ ...]\n"
+8 "¥Ð¥¤¥Ê¥ê¥Õ¥¡¥¤¥ë %s ¤Ë¥Þ¥Ã¥Á¤·¤Þ¤·¤¿\n"
+9 "%s (BSD grep) %s\n"
diff --git a/usr.bin/grep/nls/pt_BR.ISO8859-1.msg b/usr.bin/grep/nls/pt_BR.ISO8859-1.msg
new file mode 100644
index 0000000..7b6c20e
--- /dev/null
+++ b/usr.bin/grep/nls/pt_BR.ISO8859-1.msg
@@ -0,0 +1,14 @@
+$ $NetBSD: pt_BR.ISO8859-1.msg,v 1.2 2011/04/18 22:46:48 joerg Exp $
+$ $FreeBSD: head/usr.bin/grep/nls/pt_BR.ISO8859-1.msg 210622 2010-07-29 18:02:57Z gabor $
+$
+$set 1
+$quote "
+1 "(entrada padrão)"
+2 "não se posso ler o fichero comprimido bzip2"
+3 "opcão não conhecida de %s"
+4 "uso: %s [-abcDEFGHhIiJLlmnOoPqRSsUVvwxZz] [-A num] [-B num] [-C[num]]\n"
+5 "\t[-e padrão] [-f arquivo] [--binary-files=valor] [--color=quando]\n"
+6 "\t[--context[=num]] [--directories=ação] [--label] [--line-buffered]\n"
+7 "\t[padrão] [arquivo ...]\n"
+8 "arquivo binário %s casa com o padrão\n"
+9 "%s (BSD grep) %s\n"
diff --git a/usr.bin/grep/nls/ru_RU.KOI8-R.msg b/usr.bin/grep/nls/ru_RU.KOI8-R.msg
new file mode 100644
index 0000000..4c69f73
--- /dev/null
+++ b/usr.bin/grep/nls/ru_RU.KOI8-R.msg
@@ -0,0 +1,14 @@
+$ $NetBSD: ru_RU.KOI8-R.msg,v 1.2 2011/04/18 22:46:48 joerg Exp $
+$ $FreeBSD: head/usr.bin/grep/nls/ru_RU.KOI8-R.msg 210622 2010-07-29 18:02:57Z gabor $
+$
+$set 1
+$quote "
+1 "(ÓÔÁÎÄÁÒÔÎÙÊ ××ÏÄ)"
+2 "ÎÅ ÍÏÇÕ ÐÒÏÞÉÔÁÔØ ÓÖÁÔÙÊ × bzip2 ÆÁÊÌ"
+3 "ÎÅÉÚ×ÅÓÔÎÙÊ ËÌÀÞ %s"
+4 "ÉÓÐÏÌØÚÏ×ÁÎÉÅ: %s [-abcDEFGHhIiJLlmnOoPqRSsUVvwxZz] [-A ÞÉÓ] [-B ÞÉÓ] [-C[ÞÉÓ]]\n"
+5 "\t[-e ÛÁÂÌÏÎ] [-f ÆÁÊÌ] [--binary-files=ÚÎÁÞÅÎÉÅ] [--color=ËÏÇÄÁ]\n"
+6 "\t[--context[=ÞÉÓ]] [--directories=ÄÅÊÓÔ×ÉÅ] [--label] [--line-buffered]\n"
+7 "\t[ÛÁÂÌÏÎ] [ÆÁÊÌ ...]\n"
+8 "Ä×ÏÉÞÎÙÊ ÆÁÊÌ %s ÓÏ×ÐÁÄÁÅÔ\n"
+9 "%s (BSD grep) %s\n"
diff --git a/usr.bin/grep/nls/uk_UA.UTF-8.msg b/usr.bin/grep/nls/uk_UA.UTF-8.msg
new file mode 100644
index 0000000..e6b9f54
--- /dev/null
+++ b/usr.bin/grep/nls/uk_UA.UTF-8.msg
@@ -0,0 +1,13 @@
+$ $NetBSD: uk_UA.UTF-8.msg,v 1.2 2011/04/18 22:46:48 joerg Exp $
+$ $FreeBSD: head/usr.bin/grep/nls/uk_UA.UTF-8.msg 210927 2010-08-06 10:34:48Z gabor $
+$set 1
+$quote "
+1 "(Ñтандартний ввід)"
+2 "не можу прочитати ÑтиÑнутий bzip2 файл"
+3 "невiдома Ð¾Ð¿Ñ†Ñ–Ñ %s"
+4 "викориÑтаннÑ: %s [-abcDEFGHhIiJLlmnOoPqRSsUVvwxZz] [-A чиÑ] [-B чиÑ] [-C[чиÑ]]\n"
+5 "\t[-e шаблон] [-f файл] [--binary-files=значеннÑ] [--color=коли]\n"
+6 "\t[--context[=чиÑ] [--directories=діÑ] [--label] [--line-buffered]\n"
+7 "\t[шаблон] [файл ...]\n"
+8 "двійковий файл %s Ñпівпадає\n"
+9 "%s (BSD grep) %s\n"
diff --git a/usr.bin/grep/nls/zh_CN.UTF-8.msg b/usr.bin/grep/nls/zh_CN.UTF-8.msg
new file mode 100644
index 0000000..e9d40cd
--- /dev/null
+++ b/usr.bin/grep/nls/zh_CN.UTF-8.msg
@@ -0,0 +1,14 @@
+$ $NetBSD: zh_CN.UTF-8.msg,v 1.2 2011/04/18 22:46:48 joerg Exp $
+$ $FreeBSD: head/usr.bin/grep/nls/zh_CN.UTF-8.msg 212927 2010-09-20 19:42:52Z delphij $
+$
+$set 1
+$quote "
+1 "(标准输入)"
+2 "è¯»å– bzip2 压缩文件时出错"
+3 "选项 %s 无法识别"
+4 "用法: %s [-abcDEFGHhIiJLlmnOoPqRSsUVvwxZz] [-A 行数] [-B 行数] [-C[行数]]\n"
+5 "\t[-e 模å¼] [-f 文件] [--binary-files=值] [--color=何时]\n"
+6 "\t[--context[=行数]] [--directories=动作] [--label] [--line-buffered]\n"
+7 "\t[模å¼] [文件å ...]\n"
+8 "二进制文件 %s 包å«æ¨¡å¼\n"
+9 "%s (BSD grep) %s\n"
diff --git a/usr.bin/grep/queue.c b/usr.bin/grep/queue.c
new file mode 100644
index 0000000..e3c6be1
--- /dev/null
+++ b/usr.bin/grep/queue.c
@@ -0,0 +1,116 @@
+/* $NetBSD: queue.c,v 1.5 2011/08/31 16:24:57 plunky Exp $ */
+/* $FreeBSD: head/usr.bin/grep/queue.c 211496 2010-08-19 09:28:59Z des $ */
+/*-
+ * Copyright (c) 1999 James Howard and Dag-Erling Coïdan Smørgrav
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+/*
+ * A really poor man's queue. It does only what it has to and gets out of
+ * Dodge. It is used in place of <sys/queue.h> to get a better performance.
+ */
+
+#if HAVE_NBTOOL_CONFIG_H
+#include "nbtool_config.h"
+#endif
+
+#include <sys/cdefs.h>
+__RCSID("$NetBSD: queue.c,v 1.5 2011/08/31 16:24:57 plunky Exp $");
+
+#include <sys/param.h>
+#include <sys/queue.h>
+
+#include <stdlib.h>
+#include <string.h>
+
+#include "grep.h"
+
+struct qentry {
+ STAILQ_ENTRY(qentry) list;
+ struct str data;
+};
+
+static STAILQ_HEAD(, qentry) queue = STAILQ_HEAD_INITIALIZER(queue);
+static unsigned long long count;
+
+static struct qentry *dequeue(void);
+
+void
+enqueue(struct str *x)
+{
+ struct qentry *item;
+
+ item = grep_malloc(sizeof(struct qentry));
+ item->data.dat = grep_malloc(sizeof(char) * x->len);
+ item->data.len = x->len;
+ item->data.line_no = x->line_no;
+ item->data.off = x->off;
+ memcpy(item->data.dat, x->dat, x->len);
+ item->data.file = x->file;
+
+ STAILQ_INSERT_TAIL(&queue, item, list);
+
+ if (++count > Bflag) {
+ item = dequeue();
+ free(item->data.dat);
+ free(item);
+ }
+}
+
+static struct qentry *
+dequeue(void)
+{
+ struct qentry *item;
+
+ item = STAILQ_FIRST(&queue);
+ if (item == NULL)
+ return (NULL);
+
+ STAILQ_REMOVE_HEAD(&queue, list);
+ --count;
+ return (item);
+}
+
+void
+printqueue(void)
+{
+ struct qentry *item;
+
+ while ((item = dequeue()) != NULL) {
+ printline(&item->data, '-', NULL, 0);
+ free(item->data.dat);
+ free(item);
+ }
+}
+
+void
+clearqueue(void)
+{
+ struct qentry *item;
+
+ while ((item = dequeue()) != NULL) {
+ free(item->data.dat);
+ free(item);
+ }
+}
diff --git a/usr.bin/grep/util.c b/usr.bin/grep/util.c
new file mode 100644
index 0000000..a3c9e4c
--- /dev/null
+++ b/usr.bin/grep/util.c
@@ -0,0 +1,500 @@
+/* $NetBSD: util.c,v 1.19 2018/02/05 22:14:26 mrg Exp $ */
+/* $FreeBSD: head/usr.bin/grep/util.c 211496 2010-08-19 09:28:59Z des $ */
+/* $OpenBSD: util.c,v 1.39 2010/07/02 22:18:03 tedu Exp $ */
+
+/*-
+ * Copyright (c) 1999 James Howard and Dag-Erling Coïdan Smørgrav
+ * Copyright (C) 2008-2010 Gabor Kovesdan <gabor@FreeBSD.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#if HAVE_NBTOOL_CONFIG_H
+#include "nbtool_config.h"
+#endif
+
+#include <sys/cdefs.h>
+__RCSID("$NetBSD: util.c,v 1.19 2018/02/05 22:14:26 mrg Exp $");
+
+#include <sys/stat.h>
+#include <sys/types.h>
+
+#include <ctype.h>
+#include <err.h>
+#include <errno.h>
+#include <fnmatch.h>
+#include <fts.h>
+#include <libgen.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <wchar.h>
+#include <wctype.h>
+
+#include "grep.h"
+
+static bool first, first_global = true;
+static unsigned long long since_printed;
+
+static int procline(struct str *l, int);
+
+bool
+file_matching(const char *fname)
+{
+ char *fname_base, *fname_copy;
+ unsigned int i;
+ bool ret;
+
+ ret = finclude ? false : true;
+ fname_copy = grep_strdup(fname);
+ fname_base = basename(fname_copy);
+
+ for (i = 0; i < fpatterns; ++i) {
+ if (fnmatch(fpattern[i].pat, fname, 0) == 0 ||
+ fnmatch(fpattern[i].pat, fname_base, 0) == 0) {
+ if (fpattern[i].mode == EXCL_PAT) {
+ free(fname_copy);
+ return (false);
+ } else
+ ret = true;
+ }
+ }
+ free(fname_copy);
+ return (ret);
+}
+
+static inline bool
+dir_matching(const char *dname)
+{
+ unsigned int i;
+ bool ret;
+
+ ret = dinclude ? false : true;
+
+ for (i = 0; i < dpatterns; ++i) {
+ if (dname != NULL &&
+ fnmatch(dname, dpattern[i].pat, 0) == 0) {
+ if (dpattern[i].mode == EXCL_PAT)
+ return (false);
+ else
+ ret = true;
+ }
+ }
+ return (ret);
+}
+
+/*
+ * Processes a directory when a recursive search is performed with
+ * the -R option. Each appropriate file is passed to procfile().
+ */
+int
+grep_tree(char **argv)
+{
+ FTS *fts;
+ FTSENT *p;
+ char *d, *dir = NULL;
+ int c, fts_flags;
+ bool ok;
+
+ c = fts_flags = 0;
+
+ switch(linkbehave) {
+ case LINK_EXPLICIT:
+ fts_flags = FTS_COMFOLLOW;
+ break;
+ case LINK_SKIP:
+ fts_flags = FTS_PHYSICAL;
+ break;
+ default:
+ fts_flags = FTS_LOGICAL;
+
+ }
+
+ fts_flags |= FTS_NOSTAT | FTS_NOCHDIR;
+
+ if (!(fts = fts_open(argv, fts_flags, NULL)))
+ err(2, "fts_open");
+ while ((p = fts_read(fts)) != NULL) {
+ switch (p->fts_info) {
+ case FTS_DNR:
+ /* FALLTHROUGH */
+ case FTS_ERR:
+ errx(2, "%s: %s", p->fts_path, strerror(p->fts_errno));
+ break;
+ case FTS_D:
+ /* FALLTHROUGH */
+ case FTS_DP:
+ break;
+ case FTS_DC:
+ /* Print a warning for recursive directory loop */
+ warnx("warning: %s: recursive directory loop",
+ p->fts_path);
+ break;
+ default:
+ /* Check for file exclusion/inclusion */
+ ok = true;
+ if (dexclude || dinclude) {
+ if ((d = strrchr(p->fts_path, '/')) != NULL) {
+ dir = grep_malloc(sizeof(char) *
+ (d - p->fts_path + 1));
+ memcpy(dir, p->fts_path,
+ d - p->fts_path);
+ dir[d - p->fts_path] = '\0';
+ }
+ ok = dir_matching(dir);
+ free(dir);
+ dir = NULL;
+ }
+ if (fexclude || finclude)
+ ok &= file_matching(p->fts_path);
+
+ if (ok)
+ c += procfile(p->fts_path);
+ break;
+ }
+ }
+
+ fts_close(fts);
+ return (c);
+}
+
+/*
+ * Opens a file and processes it. Each file is processed line-by-line
+ * passing the lines to procline().
+ */
+int
+procfile(const char *fn)
+{
+ struct file *f;
+ struct stat sb;
+ struct str ln;
+ mode_t s;
+ int c, t;
+
+ if (mflag && (mcount <= 0))
+ return (0);
+
+ if (strcmp(fn, "-") == 0) {
+ fn = label != NULL ? label : getstr(1);
+ f = grep_open(NULL);
+ } else {
+ if (!stat(fn, &sb)) {
+ /* Check if we need to process the file */
+ s = sb.st_mode & S_IFMT;
+ if (s == S_IFDIR && dirbehave == DIR_SKIP)
+ return (0);
+ if ((s == S_IFIFO || s == S_IFCHR || s == S_IFBLK
+ || s == S_IFSOCK) && devbehave == DEV_SKIP)
+ return (0);
+ }
+ f = grep_open(fn);
+ }
+ if (f == NULL) {
+ if (!sflag)
+ warn("%s", fn);
+ if (errno == ENOENT)
+ notfound = true;
+ return (0);
+ }
+
+ ln.file = grep_malloc(strlen(fn) + 1);
+ strcpy(ln.file, fn);
+ ln.line_no = 0;
+ ln.len = 0;
+ tail = 0;
+ ln.off = -1;
+
+ for (first = true, c = 0; c == 0 || !(lflag || qflag); ) {
+ ln.off += ln.len + 1;
+ if ((ln.dat = grep_fgetln(f, &ln.len)) == NULL || ln.len == 0)
+ break;
+ if (ln.len > 0 && ln.dat[ln.len - 1] == line_sep)
+ --ln.len;
+ ln.line_no++;
+
+ /* Return if we need to skip a binary file */
+ if (f->binary && binbehave == BINFILE_SKIP) {
+ grep_close(f);
+ free(ln.file);
+ free(f);
+ return (0);
+ }
+ /* Process the file line-by-line */
+ t = procline(&ln, f->binary);
+ c += t;
+
+ /* Count the matches if we have a match limit */
+ if (mflag) {
+ mcount -= t;
+ if (mcount <= 0)
+ break;
+ }
+ }
+ if (Bflag > 0)
+ clearqueue();
+ grep_close(f);
+
+ if (cflag) {
+ if (!hflag)
+ printf("%s:", ln.file);
+ printf("%u%c", c, line_sep);
+ }
+ if (lflag && !qflag && c != 0)
+ printf("%s%c", fn, line_sep);
+ if (Lflag && !qflag && c == 0)
+ printf("%s%c", fn, line_sep);
+ if (c && !cflag && !lflag && !Lflag &&
+ binbehave == BINFILE_BIN && f->binary && !qflag)
+ printf(getstr(8), fn);
+
+ free(ln.file);
+ free(f);
+ return (c);
+}
+
+#define iswword(x) (iswalnum((x)) || (x) == L'_')
+
+/*
+ * Processes a line comparing it with the specified patterns. Each pattern
+ * is looped to be compared along with the full string, saving each and every
+ * match, which is necessary to colorize the output and to count the
+ * matches. The matching lines are passed to printline() to display the
+ * appropriate output.
+ */
+static int
+procline(struct str *l, int nottext)
+{
+ regmatch_t matches[MAX_LINE_MATCHES];
+ regmatch_t pmatch;
+ size_t st = 0;
+ unsigned int i;
+ int c = 0, m = 0, r = 0;
+
+ /* Loop to process the whole line */
+ while (st <= l->len) {
+ pmatch.rm_so = st;
+ pmatch.rm_eo = l->len;
+
+ /* Loop to compare with all the patterns */
+ for (i = 0; i < patterns; i++) {
+/*
+ * XXX: grep_search() is a workaround for speed up and should be
+ * removed in the future. See fastgrep.c.
+ */
+ if (fg_pattern[i].pattern) {
+ r = grep_search(&fg_pattern[i],
+ (unsigned char *)l->dat,
+ l->len, &pmatch);
+ r = (r == 0) ? 0 : REG_NOMATCH;
+ st = pmatch.rm_eo;
+ } else {
+ r = regexec(&r_pattern[i], l->dat, 1,
+ &pmatch, eflags);
+ r = (r == 0) ? 0 : REG_NOMATCH;
+ st = pmatch.rm_eo;
+ }
+ if (r == REG_NOMATCH)
+ continue;
+ /* Check for full match */
+ if (xflag &&
+ (pmatch.rm_so != 0 ||
+ (size_t)pmatch.rm_eo != l->len))
+ continue;
+ /* Check for whole word match */
+ if (fg_pattern[i].word && pmatch.rm_so != 0) {
+ wchar_t wbegin, wend;
+
+ wbegin = wend = L' ';
+ if (pmatch.rm_so != 0 &&
+ sscanf(&l->dat[pmatch.rm_so - 1],
+ "%lc", &wbegin) != 1)
+ continue;
+ if ((size_t)pmatch.rm_eo != l->len &&
+ sscanf(&l->dat[pmatch.rm_eo],
+ "%lc", &wend) != 1)
+ continue;
+ if (iswword(wbegin) || iswword(wend))
+ continue;
+ }
+ c = 1;
+ if (m < MAX_LINE_MATCHES)
+ matches[m++] = pmatch;
+ /* matches - skip further patterns */
+ if ((color != NULL && !oflag) || qflag || lflag)
+ break;
+ }
+
+ if (vflag) {
+ c = !c;
+ break;
+ }
+ /* One pass if we are not recording matches */
+ if ((color != NULL && !oflag) || qflag || lflag)
+ break;
+
+ if (st == (size_t)pmatch.rm_so)
+ break; /* No matches */
+ }
+
+ if (c && binbehave == BINFILE_BIN && nottext)
+ return (c); /* Binary file */
+
+ /* Dealing with the context */
+ if ((tail || c) && !cflag && !qflag && !lflag && !Lflag) {
+ if (c) {
+ if ((Aflag || Bflag) && !first_global &&
+ (first || since_printed > Bflag))
+ printf("--\n");
+ tail = Aflag;
+ if (Bflag > 0)
+ printqueue();
+ printline(l, ':', matches, m);
+ } else {
+ printline(l, '-', matches, m);
+ tail--;
+ }
+ first = false;
+ first_global = false;
+ since_printed = 0;
+ } else {
+ if (Bflag)
+ enqueue(l);
+ since_printed++;
+ }
+ return (c);
+}
+
+/*
+ * Safe malloc() for internal use.
+ */
+void *
+grep_malloc(size_t size)
+{
+ void *ptr;
+
+ if ((ptr = malloc(size)) == NULL)
+ err(2, "malloc");
+ return (ptr);
+}
+
+/*
+ * Safe calloc() for internal use.
+ */
+void *
+grep_calloc(size_t nmemb, size_t size)
+{
+ void *ptr;
+
+ if ((ptr = calloc(nmemb, size)) == NULL)
+ err(2, "calloc");
+ return (ptr);
+}
+
+/*
+ * Safe realloc() for internal use.
+ */
+void *
+grep_realloc(void *ptr, size_t size)
+{
+
+ if ((ptr = realloc(ptr, size)) == NULL)
+ err(2, "realloc");
+ return (ptr);
+}
+
+/*
+ * Safe strdup() for internal use.
+ */
+char *
+grep_strdup(const char *str)
+{
+ char *ret;
+
+ if ((ret = strdup(str)) == NULL)
+ err(2, "strdup");
+ return (ret);
+}
+
+/*
+ * Prints a matching line according to the command line options.
+ */
+void
+printline(struct str *line, int sep, regmatch_t *matches, int m)
+{
+ size_t a = 0;
+ int i, n = 0;
+
+ if (!hflag) {
+ if (nullflag == 0)
+ fputs(line->file, stdout);
+ else {
+ printf("%s", line->file);
+ putchar(0);
+ }
+ ++n;
+ }
+ if (nflag) {
+ if (n > 0)
+ putchar(sep);
+ printf("%d", line->line_no);
+ ++n;
+ }
+ if (bflag) {
+ if (n > 0)
+ putchar(sep);
+ printf("%lld", (long long)line->off);
+ ++n;
+ }
+ if (n)
+ putchar(sep);
+ /* --color and -o */
+ if ((oflag || color) && m > 0) {
+ for (i = 0; i < m; i++) {
+ if (!oflag)
+ fwrite(line->dat + a, matches[i].rm_so - a, 1,
+ stdout);
+ if (color)
+ fprintf(stdout, "\33[%sm\33[K", color);
+
+ fwrite(line->dat + matches[i].rm_so,
+ matches[i].rm_eo - matches[i].rm_so, 1,
+ stdout);
+
+ if (color)
+ fprintf(stdout, "\33[m\33[K");
+ a = matches[i].rm_eo;
+ if (oflag)
+ putchar('\n');
+ }
+ if (!oflag) {
+ if (line->len - a > 0)
+ fwrite(line->dat + a, line->len - a, 1, stdout);
+ putchar(line_sep);
+ }
+ } else {
+ fwrite(line->dat, line->len, 1, stdout);
+ putchar(line_sep);
+ }
+}
diff --git a/usr.bin/head/head.1 b/usr.bin/head/head.1
new file mode 100644
index 0000000..a7fa0bc
--- /dev/null
+++ b/usr.bin/head/head.1
@@ -0,0 +1,95 @@
+.\" $NetBSD: head.1,v 1.14 2017/07/04 06:59:34 wiz Exp $
+.\"
+.\" Copyright (c) 1980, 1990, 1993
+.\" The Regents of the University of California. All rights reserved.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\" notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must reproduce the above copyright
+.\" notice, this list of conditions and the following disclaimer in the
+.\" documentation and/or other materials provided with the distribution.
+.\" 3. Neither the name of the University nor the names of its contributors
+.\" may be used to endorse or promote products derived from this software
+.\" without specific prior written permission.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+.\" ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+.\" SUCH DAMAGE.
+.\"
+.\" from: @(#)head.1 8.1 (Berkeley) 6/6/93
+.\"
+.Dd May 4, 2004
+.Dt HEAD 1
+.Os
+.Sh NAME
+.Nm head
+.Nd display first lines of a file
+.Sh SYNOPSIS
+.Nm
+.Op Fl qv
+.Op Fl n Ar count
+.Op Fl c Ar byte_count
+.Op Ar file ...
+.Sh DESCRIPTION
+This filter displays the first
+.Ar count
+lines of each of the specified files, or of the standard input if no
+files are specified.
+If
+.Ar count
+is omitted it defaults to 10.
+If
+.Fl c Ar byte_count
+is specified,
+.Nm
+counts bytes instead of lines.
+.Pp
+If more than a single file is specified, or the
+.Fl v
+option is used, each file is preceded by a header consisting of the string
+.Dq ==> XXX \*[Le]=
+where
+.Dq XXX
+is the name of the file.
+The
+.Fl q
+flag disables the printing of the header in all cases.
+.Sh EXIT STATUS
+.Ex -std head
+.Sh COMPATIBILITY
+The historic command line syntax of
+.Nm
+is supported by this implementation.
+.Pp
+This command is mostly compatible with GNU extensions to
+.Nm .
+.Sh SEE ALSO
+.Xr tail 1
+.Sh STANDARDS
+The
+.Nm
+utility conforms to
+.St -p1003.2-92 .
+.Sh HISTORY
+The
+.Nm
+utility appeared in
+.Bx 3.0 .
+It was enhanced to include the
+.Fl c ,
+.Fl q ,
+and
+.Fl v
+options for
+.Nx 2.1 .
diff --git a/usr.bin/head/head.c b/usr.bin/head/head.c
new file mode 100644
index 0000000..1d24d48
--- /dev/null
+++ b/usr.bin/head/head.c
@@ -0,0 +1,204 @@
+/* $NetBSD: head.c,v 1.24 2016/05/12 01:56:44 nonaka Exp $ */
+
+/*
+ * Copyright (c) 1980, 1987, 1992, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+#ifndef lint
+__COPYRIGHT("@(#) Copyright (c) 1980, 1987, 1992, 1993\
+ The Regents of the University of California. All rights reserved.");
+#endif /* not lint */
+
+#ifndef lint
+#if 0
+static char sccsid[] = "@(#)head.c 8.2 (Berkeley) 5/4/95";
+#else
+__RCSID("$NetBSD: head.c,v 1.24 2016/05/12 01:56:44 nonaka Exp $");
+#endif
+#endif /* not lint */
+
+#include <sys/types.h>
+
+#include <ctype.h>
+#include <err.h>
+#include <errno.h>
+#include <inttypes.h>
+#include <limits.h>
+#include <locale.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+/*
+ * head - give the first few lines of a stream or of each of a set of files
+ *
+ * Bill Joy UCB August 24, 1977
+ */
+
+static void head(FILE *, intmax_t, intmax_t);
+static void obsolete(char *[]);
+__dead static void usage(void);
+
+
+int
+main(int argc, char *argv[])
+{
+ int ch;
+ FILE *fp;
+ int first;
+ intmax_t linecnt;
+ intmax_t bytecnt;
+ char *ep;
+ int eval = 0;
+ int qflag = 0;
+ int vflag = 0;
+
+ (void)setlocale(LC_ALL, "");
+ obsolete(argv);
+ linecnt = 10;
+ bytecnt = 0;
+ while ((ch = getopt(argc, argv, "c:n:qv")) != -1)
+ switch(ch) {
+ case 'c':
+ errno = 0;
+ bytecnt = strtoimax(optarg, &ep, 10);
+ if ((bytecnt == INTMAX_MAX && errno == ERANGE) ||
+ *ep || bytecnt <= 0)
+ errx(1, "illegal byte count -- %s", optarg);
+ break;
+
+ case 'n':
+ errno = 0;
+ linecnt = strtoimax(optarg, &ep, 10);
+ if ((linecnt == INTMAX_MAX && errno == ERANGE) ||
+ *ep || linecnt <= 0)
+ errx(1, "illegal line count -- %s", optarg);
+ break;
+
+ case 'q':
+ qflag = 1;
+ vflag = 0;
+ break;
+
+ case 'v':
+ qflag = 0;
+ vflag = 1;
+ break;
+
+ case '?':
+ default:
+ usage();
+ }
+ argc -= optind;
+ argv += optind;
+
+ if (*argv)
+ for (first = 1; *argv; ++argv) {
+ if ((fp = fopen(*argv, "r")) == NULL) {
+ warn("%s", *argv);
+ eval = 1;
+ continue;
+ }
+ if (vflag || (qflag == 0 && argc > 1)) {
+ (void)printf("%s==> %s <==\n",
+ first ? "" : "\n", *argv);
+ first = 0;
+ }
+ head(fp, linecnt, bytecnt);
+ (void)fclose(fp);
+ }
+ else
+ head(stdin, linecnt, bytecnt);
+ exit(eval);
+}
+
+static void
+head(FILE *fp, intmax_t cnt, intmax_t bytecnt)
+{
+ char buf[65536];
+ size_t len, rv, rv2;
+ int ch;
+
+ if (bytecnt) {
+ while (bytecnt) {
+ len = sizeof(buf);
+ if (bytecnt > (intmax_t)sizeof(buf))
+ len = sizeof(buf);
+ else
+ len = bytecnt;
+ rv = fread(buf, 1, len, fp);
+ if (rv == 0)
+ break; /* Distinguish EOF and error? */
+ rv2 = fwrite(buf, 1, rv, stdout);
+ if (rv2 != rv) {
+ if (feof(stdout))
+ errx(1, "EOF on stdout");
+ else
+ err(1, "failure writing to stdout");
+ }
+ bytecnt -= rv;
+ }
+ } else {
+ while ((ch = getc(fp)) != EOF) {
+ if (putchar(ch) == EOF)
+ err(1, "stdout");
+ if (ch == '\n' && --cnt == 0)
+ break;
+ }
+ }
+}
+
+static void
+obsolete(char *argv[])
+{
+ char *ap;
+
+ while ((ap = *++argv)) {
+ /* Return if "--" or not "-[0-9]*". */
+ if (ap[0] != '-' || ap[1] == '-' ||
+ !isdigit((unsigned char)ap[1]))
+ return;
+ if ((ap = malloc(strlen(*argv) + 2)) == NULL)
+ err(1, NULL);
+ ap[0] = '-';
+ ap[1] = 'n';
+ (void)strcpy(ap + 2, *argv + 1);
+ *argv = ap;
+ }
+}
+
+static void
+usage(void)
+{
+
+ (void)fprintf(stderr, "usage: %s [-n lines] [file ...]\n",
+ getprogname());
+ exit(1);
+}
diff --git a/usr.bin/iconv/iconv.1 b/usr.bin/iconv/iconv.1
new file mode 100644
index 0000000..69a4dcc
--- /dev/null
+++ b/usr.bin/iconv/iconv.1
@@ -0,0 +1,122 @@
+.\" $NetBSD: iconv.1,v 1.5 2013/05/12 12:25:12 rodent Exp $
+.\"
+.\" Copyright (c)2003 Citrus Project,
+.\" All rights reserved.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\" notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must reproduce the above copyright
+.\" notice, this list of conditions and the following disclaimer in the
+.\" documentation and/or other materials provided with the distribution.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+.\" ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+.\" SUCH DAMAGE.
+.\"
+.Dd March 20, 2008
+.Dt ICONV 1
+.Os
+.\" ----------------------------------------------------------------------
+.Sh NAME
+.Nm iconv
+.Nd codeset conversion utility
+.\" ----------------------------------------------------------------------
+.Sh SYNOPSIS
+.Nm
+.Op Fl cs
+.Fl f
+.Ar from_name
+.Fl t
+.Ar to_name
+.Op Ar file ...
+.Nm
+.Fl f
+.Ar from_name
+.Op Fl cs
+.Op Fl t Ar to_name
+.Op Ar file ...
+.Nm
+.Fl t
+.Ar to_name
+.Op Fl cs
+.Op Fl f Ar from_name
+.Op Ar file ...
+.Nm
+.Fl l
+.\" ----------------------------------------------------------------------
+.Sh DESCRIPTION
+The
+.Nm
+utility converts the codeset of
+.Ar file
+(or from standard input if no file is specified) from codeset
+.Ar from_name
+to codeset
+.Ar to_name
+and outputs the
+converted text on standard output.
+.Pp
+The following options are available:
+.Bl -tag -width 0123
+.It Fl c
+Prevent output of any invalid characters.
+By default,
+.Nm
+outputs an
+.Dq invalid character
+specified by the
+.Ar to_name
+codeset when it encounters a character which is valid in the
+.Ar from_name
+codeset but does not have a corresponding character in the
+.Ar to_name
+codeset.
+.It Fl f
+Specifies the source codeset name as
+.Ar from_name .
+.It Fl l
+Lists available codeset names.
+Note that not all combinations of
+.Ar from_name
+and
+.Ar to_name
+are valid.
+.It Fl s
+Silent.
+By default,
+.Nm
+outputs the number of
+.Dq invalid characters
+to standard error if they exist.
+This option prevents this behaviour.
+.It Fl t
+Specifies the destination codeset name as
+.Ar to_name .
+.El
+.\" ----------------------------------------------------------------------
+.Sh EXIT STATUS
+.Ex -std iconv
+.\" ----------------------------------------------------------------------
+.Sh SEE ALSO
+.Xr iconv 3
+.\" ----------------------------------------------------------------------
+.Sh STANDARDS
+.Nm
+conforms to
+.St -p1003.1-2001 .
+.\" ----------------------------------------------------------------------
+.Sh HISTORY
+.Nm
+first appeared in
+.Nx 2.0 .
diff --git a/usr.bin/iconv/iconv.c b/usr.bin/iconv/iconv.c
new file mode 100644
index 0000000..dd9a5d4
--- /dev/null
+++ b/usr.bin/iconv/iconv.c
@@ -0,0 +1,236 @@
+/* $NetBSD: iconv.c,v 1.19 2013/10/07 02:00:46 dholland Exp $ */
+
+/*-
+ * Copyright (c)2003 Citrus Project,
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+#if defined(LIBC_SCCS) && !defined(lint)
+__RCSID("$NetBSD: iconv.c,v 1.19 2013/10/07 02:00:46 dholland Exp $");
+#endif /* LIBC_SCCS and not lint */
+
+#include <err.h>
+#include <errno.h>
+#include <iconv.h>
+#include <langinfo.h>
+#include <locale.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <util.h>
+
+static void usage(void) __dead;
+static int scmp(const void *, const void *);
+static void show_codesets(void);
+static void do_conv(const char *, FILE *, const char *, const char *, int, int);
+
+static void
+usage(void)
+{
+ (void)fprintf(stderr,
+ "Usage:\t%1$s [-cs] -f <from_code> -t <to_code> [file ...]\n"
+ "\t%1$s -f <from_code> [-cs] [-t <to_code>] [file ...]\n"
+ "\t%1$s -t <to_code> [-cs] [-f <from_code>] [file ...]\n"
+ "\t%1$s -l\n", getprogname());
+ exit(1);
+}
+
+/*
+ * qsort() helper function
+ */
+static int
+scmp(const void *v1, const void *v2)
+{
+ const char * const *s1 = v1;
+ const char * const *s2 = v2;
+
+ return strcasecmp(*s1, *s2);
+}
+
+static void
+show_codesets(void)
+{
+ char **list;
+ size_t sz, i;
+
+ if (__iconv_get_list(&list, &sz))
+ err(EXIT_FAILURE, "__iconv_get_list()");
+
+ qsort(list, sz, sizeof(char *), scmp);
+
+ for (i = 0; i < sz; i++)
+ (void)printf("%s\n", list[i]);
+
+ __iconv_free_list(list, sz);
+}
+
+#define INBUFSIZE 1024
+#define OUTBUFSIZE (INBUFSIZE * 2)
+/*ARGSUSED*/
+static void
+do_conv(const char *fn, FILE *fp, const char *from, const char *to, int silent,
+ int hide_invalid)
+{
+ char inbuf[INBUFSIZE], outbuf[OUTBUFSIZE], *out;
+ const char *in;
+ size_t inbytes, outbytes, ret, invalids;
+ iconv_t cd;
+ uint32_t flags = 0;
+ int serrno;
+
+ if (hide_invalid)
+ flags |= __ICONV_F_HIDE_INVALID;
+ cd = iconv_open(to, from);
+ if (cd == (iconv_t)-1)
+ err(EXIT_FAILURE, "iconv_open(%s, %s)", to, from);
+
+ invalids = 0;
+ while ((inbytes = fread(inbuf, 1, INBUFSIZE, fp)) > 0) {
+ in = inbuf;
+ while (inbytes > 0) {
+ size_t inval;
+
+ out = outbuf;
+ outbytes = OUTBUFSIZE;
+ ret = __iconv(cd, &in, &inbytes, &out, &outbytes,
+ flags, &inval);
+ serrno = errno;
+ invalids += inval;
+ if (outbytes < OUTBUFSIZE)
+ (void)fwrite(outbuf, 1, OUTBUFSIZE - outbytes,
+ stdout);
+ errno = serrno;
+ if (ret == (size_t)-1 && errno != E2BIG) {
+ /*
+ * XXX: iconv(3) is bad interface.
+ * invalid character count is lost here.
+ * instead, we just provide __iconv function.
+ */
+ if (errno != EINVAL || in == inbuf)
+ err(EXIT_FAILURE, "iconv()");
+
+ /* incomplete input character */
+ (void)memmove(inbuf, in, inbytes);
+ ret = fread(inbuf + inbytes, 1,
+ INBUFSIZE - inbytes, fp);
+ if (ret == 0) {
+ fflush(stdout);
+ if (feof(fp))
+ errx(EXIT_FAILURE,
+ "unexpected end of file; "
+ "the last character is "
+ "incomplete.");
+ else
+ err(EXIT_FAILURE, "fread()");
+ }
+ in = inbuf;
+ inbytes += ret;
+ }
+ }
+ }
+ /* reset the shift state of the output buffer */
+ outbytes = OUTBUFSIZE;
+ out = outbuf;
+ ret = iconv(cd, NULL, NULL, &out, &outbytes);
+ if (ret == (size_t)-1)
+ err(EXIT_FAILURE, "iconv()");
+ if (outbytes < OUTBUFSIZE)
+ (void)fwrite(outbuf, 1, OUTBUFSIZE - outbytes, stdout);
+
+ if (invalids > 0 && !silent)
+ warnx("warning: invalid characters: %lu",
+ (unsigned long)invalids);
+
+ iconv_close(cd);
+}
+
+int
+main(int argc, char **argv)
+{
+ int ch, i;
+ int opt_l = 0, opt_s = 0, opt_c = 0;
+ char *opt_f = NULL, *opt_t = NULL;
+ FILE *fp;
+
+ setlocale(LC_ALL, "");
+ setprogname(argv[0]);
+
+ while ((ch = getopt(argc, argv, "cslf:t:")) != EOF) {
+ switch (ch) {
+ case 'c':
+ opt_c = 1;
+ break;
+ case 's':
+ opt_s = 1;
+ break;
+ case 'l':
+ /* list */
+ opt_l = 1;
+ break;
+ case 'f':
+ /* from */
+ opt_f = estrdup(optarg);
+ break;
+ case 't':
+ /* to */
+ opt_t = estrdup(optarg);
+ break;
+ default:
+ usage();
+ }
+ }
+ argc -= optind;
+ argv += optind;
+ if (opt_l) {
+ if (argc > 0 || opt_s || opt_f != NULL || opt_t != NULL) {
+ warnx("-l is not allowed with other flags.");
+ usage();
+ }
+ show_codesets();
+ } else {
+ if (opt_f == NULL) {
+ if (opt_t == NULL)
+ usage();
+ opt_f = nl_langinfo(CODESET);
+ } else if (opt_t == NULL)
+ opt_t = nl_langinfo(CODESET);
+
+ if (argc == 0)
+ do_conv("<stdin>", stdin, opt_f, opt_t, opt_s, opt_c);
+ else {
+ for (i = 0; i < argc; i++) {
+ fp = fopen(argv[i], "r");
+ if (fp == NULL)
+ err(EXIT_FAILURE, "Cannot open `%s'",
+ argv[i]);
+ do_conv(argv[i], fp, opt_f, opt_t, opt_s,
+ opt_c);
+ (void)fclose(fp);
+ }
+ }
+ }
+ return EXIT_SUCCESS;
+}
diff --git a/usr.bin/id/groups.1 b/usr.bin/id/groups.1
new file mode 100644
index 0000000..684f0d5
--- /dev/null
+++ b/usr.bin/id/groups.1
@@ -0,0 +1,59 @@
+.\" $NetBSD: groups.1,v 1.11 2017/07/04 07:00:30 wiz Exp $
+.\"
+.\" Copyright (c) 1991, 1993
+.\" The Regents of the University of California. All rights reserved.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\" notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must reproduce the above copyright
+.\" notice, this list of conditions and the following disclaimer in the
+.\" documentation and/or other materials provided with the distribution.
+.\" 3. Neither the name of the University nor the names of its contributors
+.\" may be used to endorse or promote products derived from this software
+.\" without specific prior written permission.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+.\" ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+.\" SUCH DAMAGE.
+.\"
+.\" from: @(#)groups.1 8.1 (Berkeley) 6/6/93
+.\"
+.Dd June 6, 1993
+.Dt GROUPS 1
+.Os
+.Sh NAME
+.Nm groups
+.Nd show group memberships
+.Sh SYNOPSIS
+.Nm
+.Op Ar user
+.Sh DESCRIPTION
+The
+.Nm
+utility has been obsoleted by the
+.Xr id 1
+utility, and is equivalent to
+.Dq Nm id Fl Gn Op Ar user .
+The command
+.Dq Nm id Fl p
+is suggested for normal interactive use.
+.Pp
+The
+.Nm
+utility displays the groups to which you (or the optionally specified user)
+belong.
+.Sh EXIT STATUS
+.Ex -std groups
+.Sh SEE ALSO
+.Xr id 1
diff --git a/usr.bin/id/id.1 b/usr.bin/id/id.1
new file mode 100644
index 0000000..ad66b62
--- /dev/null
+++ b/usr.bin/id/id.1
@@ -0,0 +1,136 @@
+.\" $NetBSD: id.1,v 1.14 2017/07/04 07:01:07 wiz Exp $
+.\"
+.\" Copyright (c) 1991, 1993, 1994
+.\" The Regents of the University of California. All rights reserved.
+.\"
+.\" This code is derived from software contributed to Berkeley by
+.\" the Institute of Electrical and Electronics Engineers, Inc.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\" notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must reproduce the above copyright
+.\" notice, this list of conditions and the following disclaimer in the
+.\" documentation and/or other materials provided with the distribution.
+.\" 3. Neither the name of the University nor the names of its contributors
+.\" may be used to endorse or promote products derived from this software
+.\" without specific prior written permission.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+.\" ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+.\" SUCH DAMAGE.
+.\"
+.\" @(#)id.1 8.2 (Berkeley) 5/5/94
+.\"
+.Dd May 5, 1994
+.Dt ID 1
+.Os
+.Sh NAME
+.Nm id
+.Nd return user identity
+.Sh SYNOPSIS
+.Nm
+.Op Ar user
+.Nm
+.Fl G Op Fl n
+.Op Ar user
+.Nm
+.Fl g Op Fl nr
+.Op Ar user
+.Nm
+.Fl p
+.Op Ar user
+.Nm
+.Fl u Op Fl nr
+.Op Ar user
+.Sh DESCRIPTION
+The
+.Nm
+utility displays the user and group names and numeric IDs, of the
+calling process, to the standard output.
+If the real and effective IDs are different, both are displayed,
+otherwise only the real ID is displayed.
+.Pp
+If a
+.Ar user
+(login name or user ID)
+is specified, the user and group IDs of that user are displayed.
+In this case, the real and effective IDs are assumed to be the same.
+.Pp
+The options are as follows:
+.Bl -tag -width Ds
+.It Fl G
+Display the different group IDs (effective, real and supplementary)
+as white-space separated numbers, in no particular order.
+.It Fl g
+Display the effective group ID as a number.
+.It Fl n
+Display the name of the user or group ID for the
+.Fl G ,
+.Fl g
+and
+.Fl u
+options instead of the number.
+If any of the ID numbers cannot be mapped into names, the number will be
+displayed as usual.
+.It Fl p
+Make the output human-readable.
+If the user name returned by
+.Xr getlogin 2
+is different from the login name referenced by the user ID, the name
+returned by
+.Xr getlogin 2
+is displayed, preceded by the keyword ``login''.
+The user ID as a name is displayed, preceded by the keyword ``uid''.
+If the effective user ID is different from the real user ID, the real user
+ID is displayed as a name, preceded by the keyword ``euid''.
+If the effective group ID is different from the real group ID, the real group
+ID is displayed as a name, preceded by the keyword ``rgid''.
+The list of groups to which the user belongs is then displayed as names,
+preceded by the keyword ``groups''.
+Each display is on a separate line.
+.It Fl r
+Display the real ID for the
+.Fl g
+and
+.Fl u
+options instead of the effective ID.
+.It Fl u
+Display the effective user ID as a number.
+.El
+.Sh EXIT STATUS
+.Ex -std id
+.Sh SEE ALSO
+.Xr who 1
+.Sh STANDARDS
+The
+.Nm
+function is expected to conform to
+.St -p1003.2 .
+.Sh HISTORY
+The
+historic
+.Xr groups 1
+command is equivalent to
+.Dq Nm id Fl Gn Op Ar user .
+.Pp
+The
+historic
+.Xr whoami 1
+command is equivalent to
+.Dq Nm id Fl un .
+.Pp
+The
+.Nm
+command first appeared in
+.Bx 4.4 .
diff --git a/usr.bin/id/id.c b/usr.bin/id/id.c
new file mode 100644
index 0000000..720cc49
--- /dev/null
+++ b/usr.bin/id/id.c
@@ -0,0 +1,370 @@
+/*-
+ * Copyright (c) 1991, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+#ifndef lint
+__COPYRIGHT("@(#) Copyright (c) 1991, 1993\
+ The Regents of the University of California. All rights reserved.");
+#endif /* not lint */
+
+#ifndef lint
+#if 0
+static char sccsid[] = "@(#)id.c 8.3 (Berkeley) 4/28/95";
+#else
+__RCSID("$NetBSD: id.c,v 1.32 2011/09/16 15:39:26 joerg Exp $");
+#endif
+#endif /* not lint */
+
+#include <sys/param.h>
+
+#include <err.h>
+#include <errno.h>
+#include <grp.h>
+#include <pwd.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+static void current(void);
+static void pretty(struct passwd *);
+static void group(struct passwd *, int);
+__dead static void usage(void);
+static void user(struct passwd *);
+static struct passwd *who(char *);
+
+static int maxgroups;
+static gid_t *groups;
+
+int
+main(int argc, char *argv[])
+{
+ struct group *gr;
+ struct passwd *pw;
+ int ch, id;
+ int Gflag, gflag, nflag, pflag, rflag, uflag;
+ const char *opts;
+
+ Gflag = gflag = nflag = pflag = rflag = uflag = 0;
+
+ if (strcmp(getprogname(), "groups") == 0) {
+ Gflag = 1;
+ nflag = 1;
+ opts = "";
+ if (argc > 2)
+ usage();
+ } else if (strcmp(getprogname(), "whoami") == 0) {
+ uflag = 1;
+ nflag = 1;
+ opts = "";
+ if (argc > 1)
+ usage();
+ } else
+ opts = "Ggnpru";
+
+ while ((ch = getopt(argc, argv, opts)) != -1)
+ switch (ch) {
+ case 'G':
+ Gflag = 1;
+ break;
+ case 'g':
+ gflag = 1;
+ break;
+ case 'n':
+ nflag = 1;
+ break;
+ case 'p':
+ pflag = 1;
+ break;
+ case 'r':
+ rflag = 1;
+ break;
+ case 'u':
+ uflag = 1;
+ break;
+ case '?':
+ default:
+ usage();
+ }
+ argc -= optind;
+ argv += optind;
+
+ switch (Gflag + gflag + pflag + uflag) {
+ case 1:
+ break;
+ case 0:
+ if (!nflag && !rflag)
+ break;
+ /* FALLTHROUGH */
+ default:
+ usage();
+ }
+
+ if (strcmp(opts, "") != 0 && argc > 1)
+ usage();
+
+ pw = *argv ? who(*argv) : NULL;
+
+ maxgroups = sysconf(_SC_NGROUPS_MAX);
+ if ((groups = malloc((maxgroups + 1) * sizeof(gid_t))) == NULL)
+ err(1, NULL);
+
+ if (gflag) {
+ id = pw ? pw->pw_gid : rflag ? getgid() : getegid();
+ if (nflag && (gr = getgrgid(id)))
+ (void)printf("%s\n", gr->gr_name);
+ else
+ (void)printf("%u\n", id);
+ goto done;
+ }
+
+ if (uflag) {
+ id = pw ? pw->pw_uid : rflag ? getuid() : geteuid();
+ if (nflag && (pw = getpwuid(id)))
+ (void)printf("%s\n", pw->pw_name);
+ else
+ (void)printf("%u\n", id);
+ goto done;
+ }
+
+ if (Gflag) {
+ group(pw, nflag);
+ goto done;
+ }
+
+ if (pflag) {
+ pretty(pw);
+ goto done;
+ }
+
+ if (pw)
+ user(pw);
+ else
+ current();
+done:
+ free(groups);
+
+ return 0;
+}
+
+static void
+pretty(struct passwd *pw)
+{
+ struct group *gr;
+ u_int eid, rid;
+ char *login;
+
+ if (pw) {
+ (void)printf("uid\t%s\n", pw->pw_name);
+ (void)printf("groups\t");
+ group(pw, 1);
+ } else {
+ if ((login = getlogin()) == NULL)
+ err(1, "getlogin");
+
+ pw = getpwuid(rid = getuid());
+ if (pw == NULL || strcmp(login, pw->pw_name))
+ (void)printf("login\t%s\n", login);
+ if (pw)
+ (void)printf("uid\t%s\n", pw->pw_name);
+ else
+ (void)printf("uid\t%u\n", rid);
+
+ if ((eid = geteuid()) != rid) {
+ if ((pw = getpwuid(eid)) != NULL)
+ (void)printf("euid\t%s\n", pw->pw_name);
+ else
+ (void)printf("euid\t%u\n", eid);
+ }
+ if ((rid = getgid()) != (eid = getegid())) {
+ if ((gr = getgrgid(rid)) != NULL)
+ (void)printf("rgid\t%s\n", gr->gr_name);
+ else
+ (void)printf("rgid\t%u\n", rid);
+ }
+ (void)printf("groups\t");
+ group(NULL, 1);
+ }
+}
+
+static void
+current(void)
+{
+ struct group *gr;
+ struct passwd *pw;
+ gid_t gid, egid, lastid;
+ uid_t uid, euid;
+ int cnt, ngroups;
+ const char *fmt;
+
+ uid = getuid();
+ (void)printf("uid=%ju", (uintmax_t)uid);
+ if ((pw = getpwuid(uid)) != NULL)
+ (void)printf("(%s)", pw->pw_name);
+ gid = getgid();
+ (void)printf(" gid=%ju", (uintmax_t)gid);
+ if ((gr = getgrgid(gid)) != NULL)
+ (void)printf("(%s)", gr->gr_name);
+ if ((euid = geteuid()) != uid) {
+ (void)printf(" euid=%ju", (uintmax_t)euid);
+ if ((pw = getpwuid(euid)) != NULL)
+ (void)printf("(%s)", pw->pw_name);
+ }
+ if ((egid = getegid()) != gid) {
+ (void)printf(" egid=%ju", (uintmax_t)egid);
+ if ((gr = getgrgid(egid)) != NULL)
+ (void)printf("(%s)", gr->gr_name);
+ }
+ if ((ngroups = getgroups(maxgroups, groups)) != 0) {
+ for (fmt = " groups=%ju", lastid = -1, cnt = 0; cnt < ngroups;
+ fmt = ",%ju", lastid = gid, cnt++) {
+ gid = groups[cnt];
+ if (lastid == gid)
+ continue;
+ (void)printf(fmt, (uintmax_t)gid);
+ if ((gr = getgrgid(gid)) != NULL)
+ (void)printf("(%s)", gr->gr_name);
+ }
+ }
+ (void)printf("\n");
+}
+
+static void
+user(struct passwd *pw)
+{
+ struct group *gr;
+ const char *fmt;
+ int cnt, id, lastid, ngroups;
+ gid_t *glist = groups;
+
+ id = pw->pw_uid;
+ (void)printf("uid=%u(%s)", id, pw->pw_name);
+ (void)printf(" gid=%lu", (u_long)pw->pw_gid);
+ if ((gr = getgrgid(pw->pw_gid)) != NULL)
+ (void)printf("(%s)", gr->gr_name);
+ ngroups = maxgroups + 1;
+ if (getgrouplist(pw->pw_name, pw->pw_gid, glist, &ngroups) == -1) {
+ glist = malloc(ngroups * sizeof(gid_t));
+ (void) getgrouplist(pw->pw_name, pw->pw_gid, glist, &ngroups);
+ }
+ for (fmt = " groups=%u", lastid = -1, cnt = 0; cnt < ngroups;
+ fmt=",%u", lastid = id, cnt++) {
+ id = glist[cnt];
+ if (lastid == id)
+ continue;
+ (void)printf(fmt, id);
+ if ((gr = getgrgid(id)) != NULL)
+ (void)printf("(%s)", gr->gr_name);
+ }
+ (void)printf("\n");
+ if (glist != groups)
+ free(glist);
+}
+
+static void
+group(struct passwd *pw, int nflag)
+{
+ struct group *gr;
+ int cnt, ngroups;
+ gid_t id, lastid;
+ const char *fmt;
+ gid_t *glist = groups;
+
+ if (pw) {
+ ngroups = maxgroups;
+ if (getgrouplist(pw->pw_name, pw->pw_gid, glist, &ngroups)
+ == -1) {
+ glist = malloc(ngroups * sizeof(gid_t));
+ (void) getgrouplist(pw->pw_name, pw->pw_gid, glist,
+ &ngroups);
+ }
+ } else {
+ glist[0] = getgid();
+ ngroups = getgroups(maxgroups, glist + 1) + 1;
+ }
+ fmt = nflag ? "%s" : "%u";
+ for (lastid = -1, cnt = 0; cnt < ngroups; ++cnt) {
+ if (lastid == (id = glist[cnt]) || (cnt && id == glist[0]))
+ continue;
+ if (nflag) {
+ if ((gr = getgrgid(id)) != NULL)
+ (void)printf(fmt, gr->gr_name);
+ else
+ (void)printf(*fmt == ' ' ? " %u" : "%u",
+ id);
+ fmt = " %s";
+ } else {
+ (void)printf(fmt, id);
+ fmt = " %u";
+ }
+ lastid = id;
+ }
+ (void)printf("\n");
+ if (glist != groups)
+ free(glist);
+}
+
+static struct passwd *
+who(char *u)
+{
+ struct passwd *pw;
+ long id;
+ char *ep;
+
+ /*
+ * Translate user argument into a pw pointer. First, try to
+ * get it as specified. If that fails, try it as a number.
+ */
+ if ((pw = getpwnam(u)) != NULL)
+ return pw;
+ id = strtol(u, &ep, 10);
+ if (*u && !*ep && (pw = getpwuid(id)))
+ return pw;
+ errx(1, "%s: No such user", u);
+ /* NOTREACHED */
+ return NULL;
+}
+
+static void
+usage(void)
+{
+
+ if (strcmp(getprogname(), "groups") == 0) {
+ (void)fprintf(stderr, "usage: groups [user]\n");
+ } else if (strcmp(getprogname(), "whoami") == 0) {
+ (void)fprintf(stderr, "usage: whoami\n");
+ } else {
+ (void)fprintf(stderr, "usage: id [user]\n");
+ (void)fprintf(stderr, " id -G [-n] [user]\n");
+ (void)fprintf(stderr, " id -g [-nr] [user]\n");
+ (void)fprintf(stderr, " id -p [user]\n");
+ (void)fprintf(stderr, " id -u [-nr] [user]\n");
+ }
+ exit(1);
+}
diff --git a/usr.bin/id/whoami.1 b/usr.bin/id/whoami.1
new file mode 100644
index 0000000..53d9405
--- /dev/null
+++ b/usr.bin/id/whoami.1
@@ -0,0 +1,58 @@
+.\" $NetBSD: whoami.1,v 1.10 2016/10/02 21:06:18 abhinav Exp $
+.\"
+.\" Copyright (c) 1991, 1993
+.\" The Regents of the University of California. All rights reserved.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\" notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must reproduce the above copyright
+.\" notice, this list of conditions and the following disclaimer in the
+.\" documentation and/or other materials provided with the distribution.
+.\" 3. Neither the name of the University nor the names of its contributors
+.\" may be used to endorse or promote products derived from this software
+.\" without specific prior written permission.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+.\" ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+.\" SUCH DAMAGE.
+.\"
+.\" from: @(#)whoami.1 8.1 (Berkeley) 6/6/93
+.\" $NetBSD: whoami.1,v 1.10 2016/10/02 21:06:18 abhinav Exp $
+.\"
+.Dd June 6, 1993
+.Dt WHOAMI 1
+.Os
+.Sh NAME
+.Nm whoami
+.Nd display effective user id
+.Sh SYNOPSIS
+.Nm
+.Sh DESCRIPTION
+The
+.Nm
+utility has been obsoleted by the
+.Xr id 1
+utility, and is equivalent to
+.Dq Nm id Fl un .
+The command
+.Dq Nm id Fl p
+is suggested for normal interactive use.
+.Pp
+The
+.Nm
+utility displays your effective user ID as a name.
+.Sh EXIT STATUS
+.Ex -std whoami
+.Sh SEE ALSO
+.Xr id 1
diff --git a/usr.bin/ipcrm/ipcrm.1 b/usr.bin/ipcrm/ipcrm.1
new file mode 100644
index 0000000..2e2cad3
--- /dev/null
+++ b/usr.bin/ipcrm/ipcrm.1
@@ -0,0 +1,97 @@
+.\" $NetBSD: ipcrm.1,v 1.11 2008/06/01 10:25:29 wiz Exp $
+.\"
+.\" Copyright (c) 1994 Adam Glass
+.\" All rights reserved.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\" notice, this list of conditions and the following disclaimer.
+.\" 2. The name of the Author may not be used to endorse or promote products
+.\" derived from this software without specific prior written permission.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY Adam Glass ``AS IS'' AND
+.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+.\" ARE DISCLAIMED. IN NO EVENT SHALL Adam Glass BE LIABLE
+.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+.\" SUCH DAMAGE.
+.\"
+.\" $NetBSD: ipcrm.1,v 1.11 2008/06/01 10:25:29 wiz Exp $
+.\"
+.Dd May 31, 2008
+.Dt IPCRM 1
+.Os
+.Sh NAME
+.Nm ipcrm
+.Nd remove the specified message queues, semaphore sets, and shared memory segments
+.Sh SYNOPSIS
+.Nm
+.Op Fl M Ar shmkey
+.Op Fl m Ar shmid
+.Op Fl Q Ar msgkey
+.Op Fl q Ar msqid
+.Op Fl S Ar semkey
+.Op Fl s Ar semid
+.Ar ...
+.Sh DESCRIPTION
+.Nm
+removes the specified message queues, semaphores, and shared memory
+segments.
+These System V IPC objects can be specified by their
+creation ID or any associated key.
+.Pp
+The following options are used to specify which IPC objects will be removed.
+Any number and combination of these options can be used:
+.Bl -tag -width indent
+.It Fl M Ar shmkey
+Mark the shared memory segment associated with key
+.Ar shmkey
+for removal.
+This marked segment will be destroyed after the last detach.
+.It Fl m Ar shmid
+Mark the shared memory segment associated with ID
+.Ar shmid
+for removal.
+This marked segment will be destroyed after the last detach.
+.It Fl Q Ar msgkey
+Remove the message queue associated with key
+.Ar msgkey
+from the system.
+.It Fl q Ar msqid
+Remove the message queue associated with the ID
+.Ar msqid
+from the system.
+.It Fl S Ar semkey
+Remove the semaphore set associated with key
+.Ar semkey
+from the system.
+.It Fl s Ar semid
+Removes the semaphore set associated with ID
+.Ar semid
+from the system.
+.El
+.Pp
+If the
+.Ar id
+or
+.Ar key
+argument is
+.Dq all
+then all entries of the appropriate type are removed.
+.Pp
+The identifiers and keys associated with these System V IPC objects can be
+determined by using
+.Xr ipcs 1 .
+.Sh SEE ALSO
+.Xr ipcs 1 ,
+.Xr shmat 2 ,
+.Xr shmctl 2 ,
+.Xr shmdt 2 ,
+.Xr shmget 2
diff --git a/usr.bin/ipcrm/ipcrm.c b/usr.bin/ipcrm/ipcrm.c
new file mode 100644
index 0000000..b4d3706
--- /dev/null
+++ b/usr.bin/ipcrm/ipcrm.c
@@ -0,0 +1,313 @@
+/* $NetBSD: ipcrm.c,v 1.16 2009/01/18 01:06:42 lukem Exp $ */
+
+/*
+ * Copyright (c) 1994 Adam Glass
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. All advertising materials mentioning features or use of this software
+ * must display the following acknowledgement:
+ * This product includes software developed by Adam Glass.
+ * 4. The name of the Author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY Adam Glass ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL Adam Glass BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/types.h>
+#include <sys/ipc.h>
+#include <sys/msg.h>
+#include <sys/sem.h>
+#include <sys/shm.h>
+
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <ctype.h>
+#include <err.h>
+#include <signal.h>
+#include <sys/sysctl.h>
+
+#define IPC_TO_STR(x) (x == 'Q' ? "msq" : (x == 'M' ? "shm" : "sem"))
+#define IPC_TO_STRING(x) (x == 'Q' ? "message queue" : \
+ (x == 'M' ? "shared memory segment" : "semaphore"))
+
+static sig_atomic_t signaled;
+
+static void usage(void) __dead;
+static int msgrm(key_t, int);
+static int shmrm(key_t, int);
+static int semrm(key_t, int);
+static int msgrmall(void);
+static int shmrmall(void);
+static int semrmall(void);
+static void not_configured(int);
+
+static void
+usage(void)
+{
+ (void)fprintf(stderr, "Usage: %s [-M shmkey] [-m shmid] [-Q msgkey]\n",
+ getprogname());
+ (void)fprintf(stderr, "\t[-q msqid] [-S semkey] [-s semid] ...\n");
+ exit(1);
+}
+
+static int
+msgrm(key_t key, int id)
+{
+ if (key) {
+ id = msgget(key, 0);
+ if (id == -1)
+ return -1;
+ }
+ return msgctl(id, IPC_RMID, NULL);
+}
+
+static int
+shmrm(key_t key, int id)
+{
+ if (key) {
+ id = shmget(key, 0, 0);
+ if (id == -1)
+ return -1;
+ }
+ return shmctl(id, IPC_RMID, NULL);
+}
+
+static int
+semrm(key_t key, int id)
+{
+
+ if (key) {
+ id = semget(key, 0, 0);
+ if (id == -1)
+ return -1;
+ }
+ return semctl(id, 0, IPC_RMID, NULL);
+}
+
+static int
+msgrmall(void)
+{
+ int mib[4];
+ struct msg_sysctl_info *msgsi;
+ int32_t i;
+ size_t len;
+ int result = 0;
+
+ mib[0] = CTL_KERN;
+ mib[1] = KERN_SYSVIPC;
+ mib[2] = KERN_SYSVIPC_INFO;
+ mib[3] = KERN_SYSVIPC_MSG_INFO;
+
+ if (sysctl(mib, 4, NULL, &len, NULL, 0) == -1)
+ err(1, "sysctl(KERN_SYSVIPC_MSG_INFO)");
+
+ if ((msgsi = malloc(len)) == NULL)
+ err(1, "malloc");
+ if (sysctl(mib, 4, msgsi, &len, NULL, 0) == -1) {
+ free(msgsi);
+ err(1, "sysctl(KERN_SYSVIPC_MSG_INFO)");
+ }
+
+ for (i = 0; i < msgsi->msginfo.msgmni; i++) {
+ struct msgid_ds_sysctl *msgptr = &msgsi->msgids[i];
+ if (msgptr->msg_qbytes != 0)
+ result -= msgrm((key_t)0,
+ (int)IXSEQ_TO_IPCID(i, msgptr->msg_perm));
+ }
+ free(msgsi);
+ return result;
+}
+
+static int
+shmrmall(void)
+{
+ int mib[4];
+ struct shm_sysctl_info *shmsi;
+ size_t i, len;
+ int result = 0;
+
+ mib[0] = CTL_KERN;
+ mib[1] = KERN_SYSVIPC;
+ mib[2] = KERN_SYSVIPC_INFO;
+ mib[3] = KERN_SYSVIPC_SHM_INFO;
+
+ if (sysctl(mib, 4, NULL, &len, NULL, 0) == -1)
+ err(1, "sysctl(KERN_SYSVIPC_SHM_INFO)");
+
+ if ((shmsi = malloc(len)) == NULL)
+ err(1, "malloc");
+ if (sysctl(mib, 4, shmsi, &len, NULL, 0) == -1) {
+ free(shmsi);
+ err(1, "sysctl(KERN_SYSVIPC_SHM_INFO)");
+ }
+
+ for (i = 0; i < shmsi->shminfo.shmmni; i++) {
+ struct shmid_ds_sysctl *shmptr = &shmsi->shmids[i];
+ if (shmptr->shm_perm.mode & 0x0800)
+ result -= shmrm((key_t)0,
+ (int)IXSEQ_TO_IPCID(i, shmptr->shm_perm));
+ }
+ free(shmsi);
+ return result;
+}
+
+static int
+semrmall(void)
+{
+ int mib[4];
+ struct sem_sysctl_info *semsi;
+ size_t len;
+ int32_t i;
+ int result = 0;
+
+ mib[0] = CTL_KERN;
+ mib[1] = KERN_SYSVIPC;
+ mib[2] = KERN_SYSVIPC_INFO;
+ mib[3] = KERN_SYSVIPC_SEM_INFO;
+
+ if (sysctl(mib, 4, NULL, &len, NULL, 0) == -1)
+ err(1, "sysctl(KERN_SYSVIPC_SEM_INFO)");
+
+ if ((semsi = malloc(len)) == NULL)
+ err(1, "malloc");
+ if (sysctl(mib, 4, semsi, &len, NULL, 0) == -1) {
+ free(semsi);
+ err(1, "sysctl(KERN_SYSVIPC_SEM_INFO)");
+ }
+
+ for (i = 0; i < semsi->seminfo.semmni; i++) {
+ struct semid_ds_sysctl *semptr = &semsi->semids[i];
+ if ((semptr->sem_perm.mode & SEM_ALLOC) != 0)
+ result -= semrm((key_t)0,
+ (int)IXSEQ_TO_IPCID(i, semptr->sem_perm));
+ }
+ free(semsi);
+ return result;
+}
+
+static void
+/*ARGSUSED*/
+not_configured(int n)
+{
+ signaled++;
+}
+
+int
+main(int argc, char *argv[])
+{
+ int c, result, errflg, target_id;
+ key_t target_key;
+
+ setprogname(argv[0]);
+ errflg = 0;
+ (void)signal(SIGSYS, not_configured);
+ while ((c = getopt(argc, argv, "q:m:s:Q:M:S:")) != -1) {
+ signaled = 0;
+ target_id = 0;
+ target_key = 0;
+ result = 0;
+
+ if (optarg != NULL && strcmp(optarg, "all") == 0) {
+ switch (c) {
+ case 'm':
+ case 'M':
+ result = shmrmall();
+ break;
+ case 'q':
+ case 'Q':
+ result = msgrmall();
+ break;
+ case 's':
+ case 'S':
+ result = semrmall();
+ break;
+ default:
+ usage();
+ }
+ } else {
+ switch (c) {
+ case 'q':
+ case 'm':
+ case 's':
+ target_id = atoi(optarg);
+ break;
+ case 'Q':
+ case 'M':
+ case 'S':
+ target_key = atol(optarg);
+ if (target_key == IPC_PRIVATE) {
+ warnx("can't remove private %ss",
+ IPC_TO_STRING(c));
+ continue;
+ }
+ break;
+ default:
+ usage();
+ }
+ switch (c) {
+ case 'q':
+ result = msgrm((key_t)0, target_id);
+ break;
+ case 'm':
+ result = shmrm((key_t)0, target_id);
+ break;
+ case 's':
+ result = semrm((key_t)0, target_id);
+ break;
+ case 'Q':
+ result = msgrm(target_key, 0);
+ break;
+ case 'M':
+ result = shmrm(target_key, 0);
+ break;
+ case 'S':
+ result = semrm(target_key, 0);
+ break;
+ }
+ }
+ if (result < 0) {
+ if (!signaled) {
+ if (target_id) {
+ warn("%sid(%d): ",
+ IPC_TO_STR(toupper(c)), target_id);
+ errflg++;
+ } else if (target_key) {
+ warn("%skey(%ld): ", IPC_TO_STR(c),
+ (long)target_key);
+ errflg++;
+ }
+ } else {
+ errflg++;
+ warnx("%ss are not configured in "
+ "the running kernel",
+ IPC_TO_STRING(toupper(c)));
+ }
+ }
+ }
+
+ if (optind != argc) {
+ warnx("Unknown argument: %s", argv[optind]);
+ usage();
+ }
+ return errflg;
+}
diff --git a/usr.bin/ipcs/ipcs.1 b/usr.bin/ipcs/ipcs.1
new file mode 100644
index 0000000..86bd34e
--- /dev/null
+++ b/usr.bin/ipcs/ipcs.1
@@ -0,0 +1,141 @@
+.\" $NetBSD: ipcs.1,v 1.20 2014/06/11 14:57:55 joerg Exp $
+.\"
+.\" Copyright (c) 1994 SigmaSoft, Th. Lockert
+.\" All rights reserved.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\" notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must reproduce the above copyright
+.\" notice, this list of conditions and the following disclaimer in the
+.\" documentation and/or other materials provided with the distribution.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+.\" IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+.\" OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+.\" IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+.\" INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+.\" NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+.\" DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+.\" THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+.\" (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+.\" THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+.\"
+.Dd June 11, 2014
+.Dt IPCS 1
+.Os
+.Sh NAME
+.Nm ipcs
+.Nd report System V interprocess communication facilities status
+.Sh SYNOPSIS
+.Nm
+.Op Fl abcmopqstMQST
+.Sh DESCRIPTION
+The
+.Nm
+program provides information on System V interprocess communication
+(IPC) facilities on the system.
+.Pp
+The options are as follows:
+.Bl -tag -width Ds
+.It Fl a
+Show the maximum amount of information possible when
+displaying active semaphores, message queues,
+and shared memory segments.
+(This is shorthand for specifying the
+.Fl b ,
+.Fl c ,
+.Fl o ,
+.Fl p ,
+and
+.Fl t
+options.)
+.It Fl b
+Show the maximum allowed sizes for active semaphores, message queues,
+and shared memory segments.
+The
+.Dq maximum allowed size
+is the maximum number of bytes in a message on a message queue,
+the size of a shared memory segment,
+or the number of semaphores in a set of semaphores.
+.It Fl c
+Show the creator's name and group for active semaphores, message queues,
+and shared memory segments.
+.It Fl m
+Display information about active shared memory segments.
+.It Fl o
+Show outstanding usage for active message queues,
+and shared memory segments.
+The
+.Dq outstanding usage
+is the number of messages in a message queue, or the number
+of processes attached to a shared memory segment.
+.It Fl p
+Show the process ID information for active semaphores, message queues,
+and shared memory segments.
+The
+.Dq process ID information
+is the last process to send a message to or receive a message from
+a message queue,
+the process that created a semaphore, or the last process to attach
+or detach a shared memory segment.
+.It Fl q
+Display information about active message queues.
+.It Fl s
+Display information about active semaphores.
+.It Fl t
+Show access times for active semaphores, message queues,
+and shared memory segments.
+The access times is the time
+of the last control operation on an IPC object,
+the last send or receive of a message,
+the last attach or detach of a shared memory segment,
+or the last operation on a semaphore.
+.It Fl M
+Display system information about shared memory.
+.It Fl Q
+Display system information about messages queues.
+.It Fl S
+Display system information about semaphores.
+.It Fl T
+Display system information about shared memory, message queues
+and semaphores.
+(This is shorthand for specifying the
+.Fl M ,
+.Fl Q ,
+and
+.Fl S
+options.)
+.El
+.Pp
+If none of the
+.Fl M ,
+.Fl m ,
+.Fl Q ,
+.Fl q ,
+.Fl S ,
+.Fl s ,
+or
+.Fl T
+options are specified, information about all active IPC facilities is
+listed.
+.Sh RESTRICTIONS
+System data structures may change while
+.Nm
+is running; the output of
+.Nm
+is not guaranteed to be consistent.
+.Sh SEE ALSO
+.Xr ipcrm 1 ,
+.Xr shmat 2 ,
+.Xr shmctl 2 ,
+.Xr shmdt 2 ,
+.Xr shmget 2
+.Sh AUTHORS
+.An Thorsten Lockert Aq Mt tholo@sigmasoft.com
+.Sh BUGS
+This manual page is woefully incomplete, because it does not
+at all attempt to explain the information printed by
+.Nm .
diff --git a/usr.bin/ipcs/ipcs.c b/usr.bin/ipcs/ipcs.c
new file mode 100644
index 0000000..8fbae90
--- /dev/null
+++ b/usr.bin/ipcs/ipcs.c
@@ -0,0 +1,678 @@
+/* $NetBSD: ipcs.c,v 1.43 2014/06/11 14:57:55 joerg Exp $ */
+
+/*-
+ * Copyright (c) 2000 The NetBSD Foundation, Inc.
+ * All rights reserved.
+ *
+ * This code is derived from software contributed to The NetBSD Foundation
+ * by Simon Burge.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
+ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/*
+ * Copyright (c) 1994 SigmaSoft, Th. Lockert <tholo@sigmasoft.com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
+ * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
+ * THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+#include <sys/param.h>
+#include <sys/sysctl.h>
+#include <sys/inttypes.h>
+#include <sys/ipc.h>
+#include <sys/sem.h>
+#include <sys/shm.h>
+#include <sys/msg.h>
+
+#include <err.h>
+#include <fcntl.h>
+#include <grp.h>
+#include <limits.h>
+#include <paths.h>
+#include <pwd.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <unistd.h>
+
+#define SHMINFO 1
+#define SHMTOTAL 2
+#define MSGINFO 4
+#define MSGTOTAL 8
+#define SEMINFO 16
+#define SEMTOTAL 32
+
+#define BIGGEST 1
+#define CREATOR 2
+#define OUTSTANDING 4
+#define PID 8
+#define TIME 16
+
+static int display = 0;
+static int option = 0;
+
+static void cvt_time(time_t, char *, size_t);
+static char *fmt_perm(u_short);
+static void msg_sysctl(void);
+static void sem_sysctl(void);
+static void shm_sysctl(void);
+static void show_msginfo(time_t, time_t, time_t, int, u_int64_t, mode_t,
+ uid_t, gid_t, uid_t, gid_t, u_int64_t, u_int64_t, u_int64_t, pid_t, pid_t);
+static void show_msginfo_hdr(void);
+static void show_msgtotal(struct msginfo *);
+static void show_seminfo_hdr(void);
+static void show_seminfo(time_t, time_t, int, u_int64_t, mode_t, uid_t,
+ gid_t, uid_t, gid_t, int16_t);
+static void show_semtotal(struct seminfo *);
+static void show_shminfo(time_t, time_t, time_t, int, u_int64_t, mode_t,
+ uid_t, gid_t, uid_t, gid_t, u_int32_t, u_int64_t, pid_t, pid_t);
+static void show_shminfo_hdr(void);
+static void show_shmtotal(struct shminfo *);
+static void usage(void) __dead;
+static void unconfsem(void);
+static void unconfmsg(void);
+static void unconfshm(void);
+
+static void
+unconfsem(void)
+{
+ warnx("SVID semaphores facility not configured in the system");
+}
+
+static void
+unconfmsg(void)
+{
+ warnx("SVID messages facility not configured in the system");
+}
+
+static void
+unconfshm(void)
+{
+ warnx("SVID shared memory facility not configured in the system");
+}
+
+static char *
+fmt_perm(u_short mode)
+{
+ static char buffer[12];
+
+ buffer[0] = '-';
+ buffer[1] = '-';
+ buffer[2] = ((mode & 0400) ? 'r' : '-');
+ buffer[3] = ((mode & 0200) ? 'w' : '-');
+ buffer[4] = ((mode & 0100) ? 'a' : '-');
+ buffer[5] = ((mode & 0040) ? 'r' : '-');
+ buffer[6] = ((mode & 0020) ? 'w' : '-');
+ buffer[7] = ((mode & 0010) ? 'a' : '-');
+ buffer[8] = ((mode & 0004) ? 'r' : '-');
+ buffer[9] = ((mode & 0002) ? 'w' : '-');
+ buffer[10] = ((mode & 0001) ? 'a' : '-');
+ buffer[11] = '\0';
+ return (&buffer[0]);
+}
+
+static void
+cvt_time(time_t t, char *buf, size_t buflen)
+{
+ struct tm *tm;
+
+ if (t == 0)
+ (void)strlcpy(buf, "no-entry", buflen);
+ else {
+ tm = localtime(&t);
+ (void)snprintf(buf, buflen, "%2d:%02d:%02d",
+ tm->tm_hour, tm->tm_min, tm->tm_sec);
+ }
+}
+int
+main(int argc, char *argv[])
+{
+ int i;
+ time_t now;
+
+ while ((i = getopt(argc, argv, "MmQqSsabcoptT")) != -1)
+ switch (i) {
+ case 'M':
+ display |= SHMTOTAL;
+ break;
+ case 'm':
+ display |= SHMINFO;
+ break;
+ case 'Q':
+ display |= MSGTOTAL;
+ break;
+ case 'q':
+ display |= MSGINFO;
+ break;
+ case 'S':
+ display |= SEMTOTAL;
+ break;
+ case 's':
+ display |= SEMINFO;
+ break;
+ case 'T':
+ display |= SHMTOTAL | MSGTOTAL | SEMTOTAL;
+ break;
+ case 'a':
+ option |= BIGGEST | CREATOR | OUTSTANDING | PID | TIME;
+ break;
+ case 'b':
+ option |= BIGGEST;
+ break;
+ case 'c':
+ option |= CREATOR;
+ break;
+ case 'o':
+ option |= OUTSTANDING;
+ break;
+ case 'p':
+ option |= PID;
+ break;
+ case 't':
+ option |= TIME;
+ break;
+ default:
+ usage();
+ }
+
+ if (argc - optind > 0)
+ usage();
+
+ (void)time(&now);
+ (void)printf("IPC status from <running system> as of %s\n",
+ /* and extra \n from ctime(3) */
+ ctime(&now));
+
+ if (display == 0)
+ display = SHMINFO | MSGINFO | SEMINFO;
+
+ if (display & (MSGINFO | MSGTOTAL))
+ msg_sysctl();
+ if (display & (SHMINFO | SHMTOTAL))
+ shm_sysctl();
+ if (display & (SEMINFO | SEMTOTAL))
+ sem_sysctl();
+ return 0;
+}
+
+static void
+show_msgtotal(struct msginfo *msginfo)
+{
+ (void)printf("msginfo:\n");
+ (void)printf("\tmsgmax: %6d\t(max characters in a message)\n",
+ msginfo->msgmax);
+ (void)printf("\tmsgmni: %6d\t(# of message queues)\n",
+ msginfo->msgmni);
+ (void)printf("\tmsgmnb: %6d\t(max characters in a message queue)\n",
+ msginfo->msgmnb);
+ (void)printf("\tmsgtql: %6d\t(max # of messages in system)\n",
+ msginfo->msgtql);
+ (void)printf("\tmsgssz: %6d\t(size of a message segment)\n",
+ msginfo->msgssz);
+ (void)printf("\tmsgseg: %6d\t(# of message segments in system)\n\n",
+ msginfo->msgseg);
+}
+
+static void
+show_shmtotal(struct shminfo *shminfo)
+{
+ (void)printf("shminfo:\n");
+ (void)printf("\tshmmax: %" PRIu64 "\t(max shared memory segment size)\n",
+ shminfo->shmmax);
+ (void)printf("\tshmmin: %7d\t(min shared memory segment size)\n",
+ shminfo->shmmin);
+ (void)printf("\tshmmni: %7d\t(max number of shared memory identifiers)\n",
+ shminfo->shmmni);
+ (void)printf("\tshmseg: %7d\t(max shared memory segments per process)\n",
+ shminfo->shmseg);
+ (void)printf("\tshmall: %7d\t(max amount of shared memory in pages)\n\n",
+ shminfo->shmall);
+}
+
+static void
+show_semtotal(struct seminfo *seminfo)
+{
+ (void)printf("seminfo:\n");
+ (void)printf("\tsemmap: %6d\t(# of entries in semaphore map)\n",
+ seminfo->semmap);
+ (void)printf("\tsemmni: %6d\t(# of semaphore identifiers)\n",
+ seminfo->semmni);
+ (void)printf("\tsemmns: %6d\t(# of semaphores in system)\n",
+ seminfo->semmns);
+ (void)printf("\tsemmnu: %6d\t(# of undo structures in system)\n",
+ seminfo->semmnu);
+ (void)printf("\tsemmsl: %6d\t(max # of semaphores per id)\n",
+ seminfo->semmsl);
+ (void)printf("\tsemopm: %6d\t(max # of operations per semop call)\n",
+ seminfo->semopm);
+ (void)printf("\tsemume: %6d\t(max # of undo entries per process)\n",
+ seminfo->semume);
+ (void)printf("\tsemusz: %6d\t(size in bytes of undo structure)\n",
+ seminfo->semusz);
+ (void)printf("\tsemvmx: %6d\t(semaphore maximum value)\n",
+ seminfo->semvmx);
+ (void)printf("\tsemaem: %6d\t(adjust on exit max value)\n\n",
+ seminfo->semaem);
+}
+
+static void
+show_msginfo_hdr(void)
+{
+ (void)printf("Message Queues:\n");
+ (void)printf("T ID KEY MODE OWNER GROUP");
+ if (option & CREATOR)
+ (void)printf(" CREATOR CGROUP");
+ if (option & OUTSTANDING)
+ (void)printf(" CBYTES QNUM");
+ if (option & BIGGEST)
+ (void)printf(" QBYTES");
+ if (option & PID)
+ (void)printf(" LSPID LRPID");
+ if (option & TIME)
+ (void)printf(" STIME RTIME CTIME");
+ (void)printf("\n");
+}
+
+static void
+show_msginfo(time_t s_time, time_t r_time, time_t c_time, int ipcid,
+ u_int64_t key,
+ mode_t mode, uid_t uid, gid_t gid, uid_t cuid, gid_t cgid,
+ u_int64_t cbytes, u_int64_t qnum, u_int64_t qbytes, pid_t lspid,
+ pid_t lrpid)
+{
+ char s_time_buf[100], r_time_buf[100], c_time_buf[100];
+
+ if (option & TIME) {
+ cvt_time(s_time, s_time_buf, sizeof(s_time_buf));
+ cvt_time(r_time, r_time_buf, sizeof(r_time_buf));
+ cvt_time(c_time, c_time_buf, sizeof(c_time_buf));
+ }
+
+ (void)printf("q %9d %10lld %s %8s %8s", ipcid, (long long)key, fmt_perm(mode),
+ user_from_uid(uid, 0), group_from_gid(gid, 0));
+
+ if (option & CREATOR)
+ (void)printf(" %8s %8s", user_from_uid(cuid, 0),
+ group_from_gid(cgid, 0));
+
+ if (option & OUTSTANDING)
+ (void)printf(" %6lld %5lld", (long long)cbytes, (long long)qnum);
+
+ if (option & BIGGEST)
+ (void)printf(" %6lld", (long long)qbytes);
+
+ if (option & PID)
+ (void)printf(" %5d %5d", lspid, lrpid);
+
+ if (option & TIME)
+ (void)printf(" %s %s %s", s_time_buf, r_time_buf, c_time_buf);
+
+ (void)printf("\n");
+}
+
+static void
+show_shminfo_hdr(void)
+{
+ (void)printf("Shared Memory:\n");
+ (void)printf("T ID KEY MODE OWNER GROUP");
+ if (option & CREATOR)
+ (void)printf(" CREATOR CGROUP");
+ if (option & OUTSTANDING)
+ (void)printf(" NATTCH");
+ if (option & BIGGEST)
+ (void)printf(" SEGSZ");
+ if (option & PID)
+ (void)printf(" CPID LPID");
+ if (option & TIME)
+ (void)printf(" ATIME DTIME CTIME");
+ (void)printf("\n");
+}
+
+static void
+show_shminfo(time_t atime, time_t dtime, time_t c_time, int ipcid, u_int64_t key,
+ mode_t mode, uid_t uid, gid_t gid, uid_t cuid, gid_t cgid,
+ u_int32_t nattch, u_int64_t segsz, pid_t cpid, pid_t lpid)
+{
+ char atime_buf[100], dtime_buf[100], c_time_buf[100];
+
+ if (option & TIME) {
+ cvt_time(atime, atime_buf, sizeof(atime_buf));
+ cvt_time(dtime, dtime_buf, sizeof(dtime_buf));
+ cvt_time(c_time, c_time_buf, sizeof(c_time_buf));
+ }
+
+ (void)printf("m %9d %10lld %s %8s %8s", ipcid, (long long)key, fmt_perm(mode),
+ user_from_uid(uid, 0), group_from_gid(gid, 0));
+
+ if (option & CREATOR)
+ (void)printf(" %8s %8s", user_from_uid(cuid, 0),
+ group_from_gid(cgid, 0));
+
+ if (option & OUTSTANDING)
+ (void)printf(" %6d", nattch);
+
+ if (option & BIGGEST)
+ (void)printf(" %7llu", (long long)segsz);
+
+ if (option & PID)
+ (void)printf(" %5d %5d", cpid, lpid);
+
+ if (option & TIME)
+ (void)printf(" %s %s %s",
+ atime_buf,
+ dtime_buf,
+ c_time_buf);
+
+ (void)printf("\n");
+}
+
+static void
+show_seminfo_hdr(void)
+{
+ (void)printf("Semaphores:\n");
+ (void)printf("T ID KEY MODE OWNER GROUP");
+ if (option & CREATOR)
+ (void)printf(" CREATOR CGROUP");
+ if (option & BIGGEST)
+ (void)printf(" NSEMS");
+ if (option & TIME)
+ (void)printf(" OTIME CTIME");
+ (void)printf("\n");
+}
+
+static void
+show_seminfo(time_t otime, time_t c_time, int ipcid, u_int64_t key, mode_t mode,
+ uid_t uid, gid_t gid, uid_t cuid, gid_t cgid, int16_t nsems)
+{
+ char c_time_buf[100], otime_buf[100];
+
+ if (option & TIME) {
+ cvt_time(otime, otime_buf, sizeof(otime_buf));
+ cvt_time(c_time, c_time_buf, sizeof(c_time_buf));
+ }
+
+ (void)printf("s %9d %10lld %s %8s %8s", ipcid, (long long)key, fmt_perm(mode),
+ user_from_uid(uid, 0), group_from_gid(gid, 0));
+
+ if (option & CREATOR)
+ (void)printf(" %8s %8s", user_from_uid(cuid, 0),
+ group_from_gid(cgid, 0));
+
+ if (option & BIGGEST)
+ (void)printf(" %5d", nsems);
+
+ if (option & TIME)
+ (void)printf(" %s %s", otime_buf, c_time_buf);
+
+ (void)printf("\n");
+}
+
+static void
+msg_sysctl(void)
+{
+ struct msg_sysctl_info *msgsi;
+ void *buf;
+ int mib[4];
+ size_t len;
+ int i, valid;
+
+ mib[0] = CTL_KERN;
+ mib[1] = KERN_SYSVIPC;
+ mib[2] = KERN_SYSVIPC_MSG;
+ len = sizeof(valid);
+ if (sysctl(mib, 3, &valid, &len, NULL, 0) < 0) {
+ warn("sysctl(KERN_SYSVIPC_MSG)");
+ return;
+ }
+ if (!valid) {
+ unconfmsg();
+ return;
+ }
+
+ mib[0] = CTL_KERN;
+ mib[1] = KERN_SYSVIPC;
+ mib[2] = KERN_SYSVIPC_INFO;
+ mib[3] = KERN_SYSVIPC_MSG_INFO;
+
+ if (!(display & MSGINFO)) {
+ /* totals only */
+ len = sizeof(struct msginfo);
+ } else {
+ if (sysctl(mib, 4, NULL, &len, NULL, 0) < 0) {
+ warn("sysctl(KERN_SYSVIPC_MSG_INFO)");
+ return;
+ }
+ }
+
+ if ((buf = malloc(len)) == NULL)
+ err(1, "malloc");
+ msgsi = (struct msg_sysctl_info *)buf;
+ if (sysctl(mib, 4, msgsi, &len, NULL, 0) < 0) {
+ warn("sysctl(KERN_SYSVIPC_MSG_INFO)");
+ goto done;
+ }
+
+ if (display & MSGTOTAL)
+ show_msgtotal(&msgsi->msginfo);
+
+ if (display & MSGINFO) {
+ show_msginfo_hdr();
+ for (i = 0; i < msgsi->msginfo.msgmni; i++) {
+ struct msgid_ds_sysctl *msqptr = &msgsi->msgids[i];
+ if (msqptr->msg_qbytes != 0)
+ show_msginfo(msqptr->msg_stime,
+ msqptr->msg_rtime,
+ msqptr->msg_ctime,
+ IXSEQ_TO_IPCID(i, msqptr->msg_perm),
+ msqptr->msg_perm._key,
+ msqptr->msg_perm.mode,
+ msqptr->msg_perm.uid,
+ msqptr->msg_perm.gid,
+ msqptr->msg_perm.cuid,
+ msqptr->msg_perm.cgid,
+ msqptr->_msg_cbytes,
+ msqptr->msg_qnum,
+ msqptr->msg_qbytes,
+ msqptr->msg_lspid,
+ msqptr->msg_lrpid);
+ }
+ (void)printf("\n");
+ }
+done:
+ free(buf);
+}
+
+static void
+shm_sysctl(void)
+{
+ struct shm_sysctl_info *shmsi;
+ void *buf;
+ int mib[4];
+ size_t len;
+ uint32_t i;
+ long valid;
+
+ mib[0] = CTL_KERN;
+ mib[1] = KERN_SYSVIPC;
+ mib[2] = KERN_SYSVIPC_SHM;
+ len = sizeof(valid);
+ if (sysctl(mib, 3, &valid, &len, NULL, 0) < 0) {
+ warn("sysctl(KERN_SYSVIPC_SHM)");
+ return;
+ }
+ if (!valid) {
+ unconfshm();
+ return;
+ }
+
+ mib[0] = CTL_KERN;
+ mib[1] = KERN_SYSVIPC;
+ mib[2] = KERN_SYSVIPC_INFO;
+ mib[3] = KERN_SYSVIPC_SHM_INFO;
+
+ if (!(display & SHMINFO)) {
+ /* totals only */
+ len = sizeof(struct shminfo);
+ } else {
+ if (sysctl(mib, 4, NULL, &len, NULL, 0) < 0) {
+ warn("sysctl(KERN_SYSVIPC_SHM_INFO)");
+ return;
+ }
+ }
+
+ if ((buf = malloc(len)) == NULL)
+ err(1, "malloc");
+ shmsi = (struct shm_sysctl_info *)buf;
+ if (sysctl(mib, 4, shmsi, &len, NULL, 0) < 0) {
+ warn("sysctl(KERN_SYSVIPC_SHM_INFO)");
+ goto done;
+ }
+
+ if (display & SHMTOTAL)
+ show_shmtotal(&shmsi->shminfo);
+
+ if (display & SHMINFO) {
+ show_shminfo_hdr();
+ for (i = 0; i < shmsi->shminfo.shmmni; i++) {
+ struct shmid_ds_sysctl *shmptr = &shmsi->shmids[i];
+ if (shmptr->shm_perm.mode & 0x0800)
+ show_shminfo(shmptr->shm_atime,
+ shmptr->shm_dtime,
+ shmptr->shm_ctime,
+ IXSEQ_TO_IPCID(i, shmptr->shm_perm),
+ shmptr->shm_perm._key,
+ shmptr->shm_perm.mode,
+ shmptr->shm_perm.uid,
+ shmptr->shm_perm.gid,
+ shmptr->shm_perm.cuid,
+ shmptr->shm_perm.cgid,
+ shmptr->shm_nattch,
+ shmptr->shm_segsz,
+ shmptr->shm_cpid,
+ shmptr->shm_lpid);
+ }
+ (void)printf("\n");
+ }
+done:
+ free(buf);
+}
+
+static void
+sem_sysctl(void)
+{
+ struct sem_sysctl_info *semsi;
+ void *buf;
+ int mib[4];
+ size_t len;
+ int i, valid;
+
+ mib[0] = CTL_KERN;
+ mib[1] = KERN_SYSVIPC;
+ mib[2] = KERN_SYSVIPC_SEM;
+ len = sizeof(valid);
+ if (sysctl(mib, 3, &valid, &len, NULL, 0) < 0) {
+ warn("sysctl(KERN_SYSVIPC_SEM)");
+ return;
+ }
+ if (!valid) {
+ unconfsem();
+ return;
+ }
+
+ mib[0] = CTL_KERN;
+ mib[1] = KERN_SYSVIPC;
+ mib[2] = KERN_SYSVIPC_INFO;
+ mib[3] = KERN_SYSVIPC_SEM_INFO;
+
+ if (!(display & SEMINFO)) {
+ /* totals only */
+ len = sizeof(struct seminfo);
+ } else {
+ if (sysctl(mib, 4, NULL, &len, NULL, 0) < 0) {
+ warn("sysctl(KERN_SYSVIPC_SEM_INFO)");
+ return;
+ }
+ }
+
+ if ((buf = malloc(len)) == NULL)
+ err(1, "malloc");
+ semsi = (struct sem_sysctl_info *)buf;
+ if (sysctl(mib, 4, semsi, &len, NULL, 0) < 0) {
+ warn("sysctl(KERN_SYSVIPC_SEM_INFO)");
+ goto done;
+ }
+
+ if (display & SEMTOTAL)
+ show_semtotal(&semsi->seminfo);
+
+ if (display & SEMINFO) {
+ show_seminfo_hdr();
+ for (i = 0; i < semsi->seminfo.semmni; i++) {
+ struct semid_ds_sysctl *semaptr = &semsi->semids[i];
+ if ((semaptr->sem_perm.mode & SEM_ALLOC) != 0)
+ show_seminfo(semaptr->sem_otime,
+ semaptr->sem_ctime,
+ IXSEQ_TO_IPCID(i, semaptr->sem_perm),
+ semaptr->sem_perm._key,
+ semaptr->sem_perm.mode,
+ semaptr->sem_perm.uid,
+ semaptr->sem_perm.gid,
+ semaptr->sem_perm.cuid,
+ semaptr->sem_perm.cgid,
+ semaptr->sem_nsems);
+ }
+ (void)printf("\n");
+ }
+done:
+ free(buf);
+}
+
+static void
+usage(void)
+{
+
+ (void)fprintf(stderr,
+ "Usage: %s [-abcmopqstMQST]\n",
+ getprogname());
+ exit(1);
+}
diff --git a/usr.bin/join/join.1 b/usr.bin/join/join.1
new file mode 100644
index 0000000..d40b099
--- /dev/null
+++ b/usr.bin/join/join.1
@@ -0,0 +1,206 @@
+.\" $NetBSD: join.1,v 1.15 2017/07/04 07:01:53 wiz Exp $
+.\"
+.\" Copyright (c) 1990, 1993
+.\" The Regents of the University of California. All rights reserved.
+.\"
+.\" This code is derived from software contributed to Berkeley by
+.\" the Institute of Electrical and Electronics Engineers, Inc.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\" notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must reproduce the above copyright
+.\" notice, this list of conditions and the following disclaimer in the
+.\" documentation and/or other materials provided with the distribution.
+.\" 3. Neither the name of the University nor the names of its contributors
+.\" may be used to endorse or promote products derived from this software
+.\" without specific prior written permission.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+.\" ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+.\" SUCH DAMAGE.
+.\"
+.\" from: @(#)join.1 8.3 (Berkeley) 4/28/95
+.\"
+.Dd April 28, 1995
+.Dt JOIN 1
+.Os
+.Sh NAME
+.Nm join
+.Nd relational database operator
+.Sh SYNOPSIS
+.Nm
+.Op Fl a Ar file_number | Fl v Ar file_number
+.Op Fl e Ar string
+.Op Fl j Ar file_number field
+.Op Fl o Ar list
+.Op Fl t Ar char
+.Op Fl \&1 Ar field
+.Op Fl \&2 Ar field
+.Ar file1 file2
+.Sh DESCRIPTION
+The join utility performs an ``equality join'' on the specified files
+and writes the result to the standard output.
+The ``join field'' is the field in each file by which the files are compared.
+The first field in each line is used by default.
+There is one line in the output for each pair of lines in
+.Ar file1
+and
+.Ar file2
+which have identical join fields.
+Each output line consists of the join field, the remaining fields from
+.Ar file1
+and then the remaining fields from
+.Ar file2 .
+.Pp
+The default field separators are tab and space characters.
+In this case, multiple tabs and spaces count as a single field separator,
+and leading tabs and spaces are ignored.
+The default output field separator is a single space character.
+.Pp
+Many of the options use file and field numbers.
+Both file numbers and field numbers are 1 based, i.e. the first file on
+the command line is file number 1 and the first field is field number 1.
+The following options are available:
+.Bl -tag -width Fl
+.It Fl a Ar file_number
+In addition to the default output, produce a line for each unpairable
+line in file
+.Ar file_number .
+(The argument to
+.Fl a
+must not be preceded by a space; see the
+.Sx COMPATIBILITY
+section.)
+.It Fl e Ar string
+Replace empty output fields with
+.Ar string .
+.It Fl o Ar list
+The
+.Fl o
+option specifies the fields that will be output from each file for
+each line with matching join fields.
+Each element of
+.Ar list
+has the form
+.Ql file_number.field ,
+where
+.Ar file_number
+is a file number and
+.Ar field
+is a field number.
+The elements of list must be either comma (``,'') or whitespace separated.
+(The latter requires quoting to protect it from the shell, or, a simpler
+approach is to use multiple
+.Fl o
+options.)
+.It Fl t Ar char
+Use character
+.Ar char
+as a field delimiter for both input and output.
+Every occurrence of
+.Ar char
+in a line is significant.
+.It Fl v Ar file_number
+Do not display the default output, but display a line for each unpairable
+line in file
+.Ar file_number .
+The options
+.Fl v Ar 1
+and
+.Fl v Ar 2
+may be specified at the same time.
+.It Fl 1 Ar field
+Join on the
+.Ar field Ns 'th
+field of file 1.
+.It Fl 2 Ar field
+Join on the
+.Ar field Ns 'th
+field of file 2.
+.El
+.Pp
+When the default field delimiter characters are used, the files to be joined
+should be ordered in the collating sequence of
+.Xr sort 1 ,
+using the
+.Fl b
+option, on the fields on which they are to be joined, otherwise
+.Nm
+may not report all field matches.
+When the field delimiter characters are specified by the
+.Fl t
+option, the collating sequence should be the same as
+.Xr sort 1
+without the
+.Fl b
+option.
+.Pp
+If one of the arguments
+.Ar file1
+or
+.Ar file2
+is ``-'', the standard input is used.
+.Sh EXIT STATUS
+.Ex -std join
+.Sh COMPATIBILITY
+For compatibility with historic versions of
+.Nm ,
+the following options are available:
+.Bl -tag -width Fl
+.It Fl a
+In addition to the default output, produce a line for each unpairable line
+in both file 1 and file 2.
+(To distinguish between this and
+.Fl a Ar file_number ,
+.Nm
+currently requires that the latter not include any white space.)
+.It Fl j1 Ar field
+Join on the
+.Ar field Ns 'th
+field of file 1.
+.It Fl j2 Ar field
+Join on the
+.Ar field Ns 'th
+field of file 2.
+.It Fl j Ar field
+Join on the
+.Ar field Ns 'th
+field of both file 1 and file 2.
+.It Fl o Ar list ...
+Historical implementations of
+.Nm
+permitted multiple arguments to the
+.Fl o
+option.
+These arguments were of the form ``file_number.field_number'' as described
+for the current
+.Fl o
+option.
+This has obvious difficulties in the presence of files named ``1.2''.
+.El
+.Pp
+These options are available only so historic shell scripts don't require
+modification and should not be used.
+.Sh SEE ALSO
+.Xr awk 1 ,
+.Xr comm 1 ,
+.Xr paste 1 ,
+.Xr sort 1 ,
+.Xr uniq 1
+.Sh STANDARDS
+The
+.Nm
+command is expected to be
+.St -p1003.2
+compatible.
diff --git a/usr.bin/join/join.c b/usr.bin/join/join.c
new file mode 100644
index 0000000..b377ec2
--- /dev/null
+++ b/usr.bin/join/join.c
@@ -0,0 +1,637 @@
+/* $NetBSD: join.c,v 1.31 2011/09/04 20:27:52 joerg Exp $ */
+
+/*-
+ * Copyright (c) 1991 The Regents of the University of California.
+ * All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Steve Hayman of Indiana University, Michiro Hikida and David
+ * Goodenough.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#if HAVE_NBTOOL_CONFIG_H
+#include "nbtool_config.h"
+#endif
+
+#include <sys/cdefs.h>
+#ifndef lint
+__COPYRIGHT("@(#) Copyright (c) 1991\
+ The Regents of the University of California. All rights reserved.");
+#endif /* not lint */
+
+#ifndef lint
+#if 0
+static char sccsid[] = "from: @(#)join.c 5.1 (Berkeley) 11/18/91";
+#else
+__RCSID("$NetBSD: join.c,v 1.31 2011/09/04 20:27:52 joerg Exp $");
+#endif
+#endif /* not lint */
+
+#include <sys/types.h>
+#include <ctype.h>
+#include <err.h>
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+/*
+ * There's a structure per input file which encapsulates the state of the
+ * file. We repeatedly read lines from each file until we've read in all
+ * the consecutive lines from the file with a common join field. Then we
+ * compare the set of lines with an equivalent set from the other file.
+ */
+typedef struct {
+ char *line; /* line */
+ u_long linealloc; /* line allocated count */
+ char **fields; /* line field(s) */
+ u_long fieldcnt; /* line field(s) count */
+ u_long fieldalloc; /* line field(s) allocated count */
+} LINE;
+
+static char nolineline[1] = { '\0' };
+static LINE noline = {nolineline, 0, 0, 0, 0}; /* arg for outfield if no line to output */
+
+typedef struct {
+ FILE *fp; /* file descriptor */
+ u_long joinf; /* join field (-1, -2, -j) */
+ int unpair; /* output unpairable lines (-a) */
+ int number; /* 1 for file 1, 2 for file 2 */
+
+ LINE *set; /* set of lines with same field */
+ u_long pushback; /* line on the stack */
+ u_long setcnt; /* set count */
+ u_long setalloc; /* set allocated count */
+} INPUT;
+
+static INPUT input1 = { NULL, 0, 0, 1, NULL, -1, 0, 0, },
+ input2 = { NULL, 0, 0, 2, NULL, -1, 0, 0, };
+
+typedef struct {
+ u_long fileno; /* file number */
+ u_long fieldno; /* field number */
+} OLIST;
+
+static OLIST *olist; /* output field list */
+static u_long olistcnt; /* output field list count */
+static u_long olistalloc; /* output field allocated count */
+
+static int joinout = 1; /* show lines with matched join fields (-v) */
+static int needsep; /* need separator character */
+static int spans = 1; /* span multiple delimiters (-t) */
+static char *empty; /* empty field replacement string (-e) */
+static const char *tabchar = " \t"; /* delimiter characters (-t) */
+
+static int cmp(LINE *, u_long, LINE *, u_long);
+__dead static void enomem(void);
+static void fieldarg(char *);
+static void joinlines(INPUT *, INPUT *);
+static void obsolete(char **);
+static void outfield(LINE *, u_long);
+static void outoneline(INPUT *, LINE *);
+static void outtwoline(INPUT *, LINE *, INPUT *, LINE *);
+static void slurp(INPUT *);
+__dead static void usage(void);
+
+int
+main(int argc, char *argv[])
+{
+ INPUT *F1, *F2;
+ int aflag, ch, cval, vflag;
+ char *end;
+
+ F1 = &input1;
+ F2 = &input2;
+
+ aflag = vflag = 0;
+ obsolete(argv);
+ while ((ch = getopt(argc, argv, "\01a:e:j:1:2:o:t:v:")) != -1) {
+ switch (ch) {
+ case '\01':
+ aflag = 1;
+ F1->unpair = F2->unpair = 1;
+ break;
+ case '1':
+ if ((F1->joinf = strtol(optarg, &end, 10)) < 1) {
+ warnx("-1 option field number less than 1");
+ usage();
+ }
+ if (*end) {
+ warnx("illegal field number -- %s", optarg);
+ usage();
+ }
+ --F1->joinf;
+ break;
+ case '2':
+ if ((F2->joinf = strtol(optarg, &end, 10)) < 1) {
+ warnx("-2 option field number less than 1");
+ usage();
+ }
+ if (*end) {
+ warnx("illegal field number -- %s", optarg);
+ usage();
+ }
+ --F2->joinf;
+ break;
+ case 'a':
+ aflag = 1;
+ switch(strtol(optarg, &end, 10)) {
+ case 1:
+ F1->unpair = 1;
+ break;
+ case 2:
+ F2->unpair = 1;
+ break;
+ default:
+ warnx("-a option file number not 1 or 2");
+ usage();
+ break;
+ }
+ if (*end) {
+ warnx("illegal file number -- %s", optarg);
+ usage();
+ }
+ break;
+ case 'e':
+ empty = optarg;
+ break;
+ case 'j':
+ if ((F1->joinf = F2->joinf =
+ strtol(optarg, &end, 10)) < 1) {
+ warnx("-j option field number less than 1");
+ usage();
+ }
+ if (*end) {
+ warnx("illegal field number -- %s", optarg);
+ usage();
+ }
+ --F1->joinf;
+ --F2->joinf;
+ break;
+ case 'o':
+ fieldarg(optarg);
+ break;
+ case 't':
+ spans = 0;
+ if (strlen(tabchar = optarg) != 1) {
+ warnx("illegal tab character specification");
+ usage();
+ }
+ break;
+ case 'v':
+ vflag = 1;
+ joinout = 0;
+ switch(strtol(optarg, &end, 10)) {
+ case 1:
+ F1->unpair = 1;
+ break;
+ case 2:
+ F2->unpair = 1;
+ break;
+ default:
+ warnx("-v option file number not 1 or 2");
+ usage();
+ break;
+ }
+ if (*end) {
+ warnx("illegal file number -- %s", optarg);
+ usage();
+ }
+ break;
+ case '?':
+ default:
+ usage();
+ }
+ }
+ argc -= optind;
+ argv += optind;
+
+ if (aflag && vflag)
+ errx(1, "-a and -v options mutually exclusive");
+
+ if (argc != 2)
+ usage();
+
+ /* Open the files; "-" means stdin. */
+ if (!strcmp(*argv, "-"))
+ F1->fp = stdin;
+ else if ((F1->fp = fopen(*argv, "r")) == NULL)
+ err(1, "%s", *argv);
+ ++argv;
+ if (!strcmp(*argv, "-"))
+ F2->fp = stdin;
+ else if ((F2->fp = fopen(*argv, "r")) == NULL)
+ err(1, "%s", *argv);
+ if (F1->fp == stdin && F2->fp == stdin)
+ errx(1, "only one input file may be stdin");
+
+ slurp(F1);
+ slurp(F2);
+ while (F1->setcnt && F2->setcnt) {
+ cval = cmp(F1->set, F1->joinf, F2->set, F2->joinf);
+ if (cval == 0) {
+ /* Oh joy, oh rapture, oh beauty divine! */
+ if (joinout)
+ joinlines(F1, F2);
+ slurp(F1);
+ slurp(F2);
+ } else if (cval < 0) {
+ /* File 1 takes the lead... */
+ if (F1->unpair)
+ joinlines(F1, NULL);
+ slurp(F1);
+ } else {
+ /* File 2 takes the lead... */
+ if (F2->unpair)
+ joinlines(F2, NULL);
+ slurp(F2);
+ }
+ }
+
+ /*
+ * Now that one of the files is used up, optionally output any
+ * remaining lines from the other file.
+ */
+ if (F1->unpair)
+ while (F1->setcnt) {
+ joinlines(F1, NULL);
+ slurp(F1);
+ }
+ if (F1->fp != stdin)
+ fclose(F1->fp);
+
+ if (F2->unpair)
+ while (F2->setcnt) {
+ joinlines(F2, NULL);
+ slurp(F2);
+ }
+ if (F2->fp != stdin)
+ fclose(F2->fp);
+
+ return 0;
+}
+
+static void
+slurp(INPUT *F)
+{
+ LINE *lp;
+ LINE tmp;
+ LINE *nline;
+ size_t len;
+ u_long cnt;
+ char *bp, *fieldp;
+ u_long nsize;
+
+ /*
+ * Read all of the lines from an input file that have the same
+ * join field.
+ */
+ for (F->setcnt = 0;; ++F->setcnt) {
+ /*
+ * If we're out of space to hold line structures, allocate
+ * more. Initialize the structure so that we know that this
+ * is new space.
+ */
+ if (F->setcnt == F->setalloc) {
+ cnt = F->setalloc;
+ if (F->setalloc == 0)
+ nsize = 64;
+ else
+ nsize = F->setalloc << 1;
+ if ((nline = realloc(F->set,
+ nsize * sizeof(LINE))) == NULL)
+ enomem();
+ F->set = nline;
+ F->setalloc = nsize;
+ memset(F->set + cnt, 0,
+ (F->setalloc - cnt) * sizeof(LINE));
+ }
+
+ /*
+ * Get any pushed back line, else get the next line. Allocate
+ * space as necessary. If taking the line from the stack swap
+ * the two structures so that we don't lose the allocated space.
+ * This could be avoided by doing another level of indirection,
+ * but it's probably okay as is.
+ */
+ lp = &F->set[F->setcnt];
+ if (F->pushback != (u_long)-1) {
+ tmp = F->set[F->setcnt];
+ F->set[F->setcnt] = F->set[F->pushback];
+ F->set[F->pushback] = tmp;
+ F->pushback = (u_long)-1;
+ continue;
+ }
+ if ((bp = fgetln(F->fp, &len)) == NULL)
+ return;
+ if (lp->linealloc <= len + 1) {
+ char *n;
+
+ if (lp->linealloc == 0)
+ nsize = 128;
+ else
+ nsize = lp->linealloc;
+ while (nsize <= len + 1)
+ nsize <<= 1;
+ if ((n = realloc(lp->line,
+ nsize * sizeof(char))) == NULL)
+ enomem();
+ lp->line = n;
+ lp->linealloc = nsize;
+ }
+ memmove(lp->line, bp, len);
+
+ /* Replace trailing newline, if it exists. */
+ if (bp[len - 1] == '\n')
+ lp->line[len - 1] = '\0';
+ else
+ lp->line[len] = '\0';
+ bp = lp->line;
+
+ /* Split the line into fields, allocate space as necessary. */
+ lp->fieldcnt = 0;
+ while ((fieldp = strsep(&bp, tabchar)) != NULL) {
+ if (spans && *fieldp == '\0')
+ continue;
+ if (lp->fieldcnt == lp->fieldalloc) {
+ char **n;
+
+ if (lp->fieldalloc == 0)
+ nsize = 16;
+ else
+ nsize = lp->fieldalloc << 1;
+ if ((n = realloc(lp->fields,
+ nsize * sizeof(char *))) == NULL)
+ enomem();
+ lp->fields = n;
+ lp->fieldalloc = nsize;
+ }
+ lp->fields[lp->fieldcnt++] = fieldp;
+ }
+
+ /* See if the join field value has changed. */
+ if (F->setcnt && cmp(lp, F->joinf, lp - 1, F->joinf)) {
+ F->pushback = F->setcnt;
+ break;
+ }
+ }
+}
+
+static int
+cmp(LINE *lp1, u_long fieldno1, LINE *lp2, u_long fieldno2)
+{
+
+ if (lp1->fieldcnt <= fieldno1)
+ return (lp2->fieldcnt <= fieldno2 ? 0 : 1);
+ if (lp2->fieldcnt <= fieldno2)
+ return (-1);
+ return (strcmp(lp1->fields[fieldno1], lp2->fields[fieldno2]));
+}
+
+static void
+joinlines(INPUT *F1, INPUT *F2)
+{
+ u_long cnt1, cnt2;
+
+ /*
+ * Output the results of a join comparison. The output may be from
+ * either file 1 or file 2 (in which case the first argument is the
+ * file from which to output) or from both.
+ */
+ if (F2 == NULL) {
+ for (cnt1 = 0; cnt1 < F1->setcnt; ++cnt1)
+ outoneline(F1, &F1->set[cnt1]);
+ return;
+ }
+ for (cnt1 = 0; cnt1 < F1->setcnt; ++cnt1)
+ for (cnt2 = 0; cnt2 < F2->setcnt; ++cnt2)
+ outtwoline(F1, &F1->set[cnt1], F2, &F2->set[cnt2]);
+}
+
+static void
+outoneline(INPUT *F, LINE *lp)
+{
+ u_long cnt;
+
+ /*
+ * Output a single line from one of the files, according to the
+ * join rules. This happens when we are writing unmatched single
+ * lines. Output empty fields in the right places.
+ */
+ if (olist)
+ for (cnt = 0; cnt < olistcnt; ++cnt) {
+ if (olist[cnt].fileno == (u_long)F->number)
+ outfield(lp, olist[cnt].fieldno);
+ else
+ outfield(&noline, 1);
+ }
+ else
+ for (cnt = 0; cnt < lp->fieldcnt; ++cnt)
+ outfield(lp, cnt);
+ (void)printf("\n");
+ if (ferror(stdout))
+ err(1, "stdout");
+ needsep = 0;
+}
+
+static void
+outtwoline(INPUT *F1, LINE *lp1, INPUT *F2, LINE *lp2)
+{
+ u_long cnt;
+
+ /* Output a pair of lines according to the join list (if any). */
+ if (olist) {
+ for (cnt = 0; cnt < olistcnt; ++cnt)
+ if (olist[cnt].fileno == 1)
+ outfield(lp1, olist[cnt].fieldno);
+ else /* if (olist[cnt].fileno == 2) */
+ outfield(lp2, olist[cnt].fieldno);
+ } else {
+ /*
+ * Output the join field, then the remaining fields from F1
+ * and F2.
+ */
+ outfield(lp1, F1->joinf);
+ for (cnt = 0; cnt < lp1->fieldcnt; ++cnt)
+ if (F1->joinf != cnt)
+ outfield(lp1, cnt);
+ for (cnt = 0; cnt < lp2->fieldcnt; ++cnt)
+ if (F2->joinf != cnt)
+ outfield(lp2, cnt);
+ }
+ (void)printf("\n");
+ if (ferror(stdout))
+ err(1, "stdout");
+ needsep = 0;
+}
+
+static void
+outfield(LINE *lp, u_long fieldno)
+{
+ if (needsep++)
+ (void)printf("%c", *tabchar);
+ if (!ferror(stdout)) {
+ if (lp->fieldcnt <= fieldno) {
+ if (empty != NULL)
+ (void)printf("%s", empty);
+ } else {
+ if (*lp->fields[fieldno] == '\0')
+ return;
+ (void)printf("%s", lp->fields[fieldno]);
+ }
+ }
+ if (ferror(stdout))
+ err(1, "stdout");
+}
+
+/*
+ * Convert an output list argument "2.1, 1.3, 2.4" into an array of output
+ * fields.
+ */
+static void
+fieldarg(char *option)
+{
+ u_long fieldno;
+ char *end, *token;
+ OLIST *n;
+
+ while ((token = strsep(&option, ", \t")) != NULL) {
+ if (*token == '\0')
+ continue;
+ if ((token[0] != '1' && token[0] != '2') || token[1] != '.')
+ errx(1, "malformed -o option field");
+ fieldno = strtol(token + 2, &end, 10);
+ if (*end)
+ errx(1, "malformed -o option field");
+ if (fieldno == 0)
+ errx(1, "field numbers are 1 based");
+ if (olistcnt == olistalloc) {
+ if ((n = realloc(olist,
+ (olistalloc + 50) * sizeof(OLIST))) == NULL)
+ enomem();
+ olist = n;
+ olistalloc += 50;
+ }
+ olist[olistcnt].fileno = token[0] - '0';
+ olist[olistcnt].fieldno = fieldno - 1;
+ ++olistcnt;
+ }
+}
+
+static void
+obsolete(char **argv)
+{
+ size_t len;
+ char **p, *ap, *t;
+
+ while ((ap = *++argv) != NULL) {
+ /* Return if "--". */
+ if (ap[0] == '-' && ap[1] == '-')
+ return;
+ switch (ap[1]) {
+ case 'a':
+ /*
+ * The original join allowed "-a", which meant the
+ * same as -a1 plus -a2. POSIX 1003.2, Draft 11.2
+ * only specifies this as "-a 1" and "a -2", so we
+ * have to use another option flag, one that is
+ * unlikely to ever be used or accidentally entered
+ * on the command line. (Well, we could reallocate
+ * the argv array, but that hardly seems worthwhile.)
+ */
+ if (ap[2] == '\0')
+ ap[1] = '\01';
+ break;
+ case 'j':
+ /*
+ * The original join allowed "-j[12] arg" and "-j arg".
+ * Convert the former to "-[12] arg". Don't convert
+ * the latter since getopt(3) can handle it.
+ */
+ switch(ap[2]) {
+ case '1':
+ if (ap[3] != '\0')
+ goto jbad;
+ ap[1] = '1';
+ ap[2] = '\0';
+ break;
+ case '2':
+ if (ap[3] != '\0')
+ goto jbad;
+ ap[1] = '2';
+ ap[2] = '\0';
+ break;
+ case '\0':
+ break;
+ default:
+jbad: errx(1, "illegal option -- %s", ap);
+ usage();
+ }
+ break;
+ case 'o':
+ /*
+ * The original join allowed "-o arg arg". Convert to
+ * "-o arg -o arg".
+ */
+ if (ap[2] != '\0')
+ break;
+ for (p = argv + 2; *p; ++p) {
+ if ((p[0][0] != '1' && p[0][0] != '2') ||
+ p[0][1] != '.')
+ break;
+ len = strlen(*p);
+ if (len - 2 != strspn(*p + 2, "0123456789"))
+ break;
+ if ((t = malloc(len + 3)) == NULL)
+ enomem();
+ t[0] = '-';
+ t[1] = 'o';
+ memmove(t + 2, *p, len + 1);
+ *p = t;
+ }
+ argv = p - 1;
+ break;
+ }
+ }
+}
+
+static void
+enomem(void)
+{
+ errx(1, "no memory");
+}
+
+static void
+usage(void)
+{
+ (void)fprintf(stderr,
+ "usage: %s [-a fileno | -v fileno] [-e string] [-j fileno field]\n"
+ " [-o list] [-t char] [-1 field] [-2 field] file1 file2\n",
+ getprogname());
+ exit(1);
+}
diff --git a/usr.bin/ldd/Makefile.common b/usr.bin/ldd/Makefile.common
new file mode 100644
index 0000000..6193b71
--- /dev/null
+++ b/usr.bin/ldd/Makefile.common
@@ -0,0 +1,16 @@
+# $NetBSD: Makefile.common,v 1.3 2013/04/25 07:12:46 matt Exp $
+
+LDELFSO=${NETBSDSRCDIR}/libexec/ld.elf_so
+CPPFLAGS+= -I${LDELFSO} -DLIBDIR=\"${LIBDIR}\"
+CPPFLAGS+= -D_RTLD_SOURCE
+.if !empty(MACHINE_ARCH:Mearm*)
+CPPFLAGS+= -I${NETBSDSRCDIR}/lib/libexecinfo
+.endif
+#CPPFLAGS+= -DDEBUG
+.PATH: ${LDELFSO}
+
+.if (${MACHINE_ARCH} == "sparc") || (${MACHINE_ARCH} == "sparc64") || \
+ (${MACHINE_ARCH} == "arm") || (${MACHINE_ARCH} == "m68k") || \
+ (${MACHINE_ARCH} == "powerpc") || (${MACHINE_ARCH:Mmips*} != "")
+CPPFLAGS+= -DVARPSZ
+.endif
diff --git a/usr.bin/ldd/Makefile.elf b/usr.bin/ldd/Makefile.elf
new file mode 100644
index 0000000..3acc9a9
--- /dev/null
+++ b/usr.bin/ldd/Makefile.elf
@@ -0,0 +1,10 @@
+# $NetBSD: Makefile.elf,v 1.5 2013/05/07 13:00:35 christos Exp $
+
+# Makefile fragment to build a (32 or 64 bit) libldd_elfxx.a.
+# Expects CPPFLAGS to have ELFSIZE set, and LIB to be set.
+
+SRCS= ldd_elfxx.c
+SRCS+= xmalloc.c debug.c expand.c map_object.c load.c search.c \
+ headers.c paths.c tls.c symver.c
+
+.include "Makefile.common"
diff --git a/usr.bin/ldd/build/Makefile b/usr.bin/ldd/build/Makefile
new file mode 100644
index 0000000..312eb00
--- /dev/null
+++ b/usr.bin/ldd/build/Makefile
@@ -0,0 +1,42 @@
+# $NetBSD: Makefile,v 1.5 2019/01/27 05:17:48 kre Exp $
+
+NOSANITIZER= # defined
+
+.include <bsd.own.mk> # for MKDYNAMICROOT definition
+
+PROG= ldd
+SRCS= ldd.c
+MAN= ldd.1
+
+.PATH: ${.CURDIR}/..
+
+.if ${OBJECT_FMTS:Melf32} != ""
+LIB_ELF32DIR!= cd ${.CURDIR}/../elf32 && ${PRINTOBJDIR}
+EXTRA_LIBS+= ${LIB_ELF32DIR}/libldd_elf32.a
+.else
+CPPFLAGS.ldd.c+= -DELF64_ONLY
+.endif
+
+.if (${MACHINE_ARCH} == "mips64el") || (${MACHINE_ARCH} == "mips64eb")
+LIB_ELF32COMPATDIR!= cd ${.CURDIR}/../elf32_compat && ${PRINTOBJDIR}
+EXTRA_LIBS+= ${LIB_ELF32COMPATDIR}/libldd_elf32_compat.a
+.endif
+
+.if ${OBJECT_FMTS:Melf64} != ""
+LIB_ELF64DIR!= cd ${.CURDIR}/../elf64 && ${PRINTOBJDIR}
+EXTRA_LIBS+= ${LIB_ELF64DIR}/libldd_elf64.a
+CPPFLAGS.ldd.c+= -DELFSIZE=64
+.else
+CPPFLAGS.ldd.c+= -DELFSIZE=32
+.endif
+
+LDADD+= ${EXTRA_LIBS}
+DPADD+= ${EXTRA_LIBS}
+
+.include "Makefile.common"
+
+.if (${MKDYNAMICROOT} == "no")
+LDSTATIC?= -static
+.endif
+
+.include <bsd.prog.mk>
diff --git a/usr.bin/ldd/dummy.c b/usr.bin/ldd/dummy.c
new file mode 100644
index 0000000..51f4a6e
--- /dev/null
+++ b/usr.bin/ldd/dummy.c
@@ -0,0 +1,3 @@
+/* $NetBSD: dummy.c,v 1.1 2009/01/07 00:39:24 mrg Exp $ */
+
+/* This file left intentially blank. */
diff --git a/usr.bin/ldd/elf32/Makefile b/usr.bin/ldd/elf32/Makefile
new file mode 100644
index 0000000..71e00b2
--- /dev/null
+++ b/usr.bin/ldd/elf32/Makefile
@@ -0,0 +1,33 @@
+# $NetBSD: Makefile,v 1.12 2019/01/27 05:16:55 kre Exp $
+
+NOSANITIZER= # defined
+
+.include <bsd.own.mk>
+.include <bsd.init.mk>
+
+RTLD_FUNCS = \
+ _rtld_tls_allocate \
+ _rtld_tls_free \
+
+.for _d in ${RTLD_FUNCS}
+CPPFLAGS+= -D${_d}=_elf32_${_d}
+.endfor
+
+CPPFLAGS+= -DELFSIZE=32
+LIB= ldd_elf32
+
+# XXX Force one member
+SRCS= dummy.c
+
+LIBISPRIVATE= yes
+.PATH: ${.CURDIR}/..
+
+.ifdef MLIBDIR
+CPPFLAGS+= -DRTLD_ARCH_SUBDIR=\"${MLIBDIR}\"
+.endif
+
+.if ${OBJECT_FMTS:Melf32} != ""
+.include "../Makefile.elf"
+.endif
+
+.include <bsd.lib.mk>
diff --git a/usr.bin/ldd/elf32_compat/Makefile b/usr.bin/ldd/elf32_compat/Makefile
new file mode 100644
index 0000000..e604c20
--- /dev/null
+++ b/usr.bin/ldd/elf32_compat/Makefile
@@ -0,0 +1,26 @@
+# $NetBSD: Makefile,v 1.4 2019/01/27 05:15:42 kre Exp $
+
+NOSANITIZER= # defined
+
+.include <bsd.own.mk>
+.include <bsd.init.mk>
+
+CPPFLAGS+= -DELFSIZE=32 -DELF32_COMPAT
+LIB= ldd_elf32_compat
+
+# XXX Force one member
+SRCS= dummy.c
+
+LIBISPRIVATE= yes
+.PATH: ${.CURDIR}/..
+
+.ifdef COMPAT_MLIBDIR
+MLIBDIR= ${COMPAT_MLIBDIR}
+CPPFLAGS+= -DRTLD_ARCH_SUBDIR=\"${MLIBDIR}\"
+.endif
+
+.ifdef MLIBDIR
+.include "../Makefile.elf"
+.endif
+
+.include <bsd.lib.mk>
diff --git a/usr.bin/ldd/elf64/Makefile b/usr.bin/ldd/elf64/Makefile
new file mode 100644
index 0000000..f5cb2ab
--- /dev/null
+++ b/usr.bin/ldd/elf64/Makefile
@@ -0,0 +1,51 @@
+# $NetBSD: Makefile,v 1.11 2019/01/27 05:14:45 kre Exp $
+
+NOSANITIZER= # defined
+
+.include <bsd.own.mk>
+
+CPPFLAGS+= -DELFSIZE=64
+LIB= ldd_elf64
+
+# XXX Force one member
+SRCS= dummy.c
+
+LIBISPRIVATE= yes
+.PATH: ${.CURDIR}/..
+
+.if ${OBJECT_FMTS:Melf64} != ""
+
+# XXX we need to make sure that we don't accidentally get the elf32
+# XXX versions of these.
+
+RTLD_FUNCS = \
+ _rtld_expand_path \
+ _rtld_digest_dynamic \
+ _rtld_digest_phdr \
+ _rtld_load_needed_objects \
+ _rtld_load_object \
+ _rtld_map_object \
+ _rtld_obj_free \
+ _rtld_obj_new \
+ _rtld_object_add_name \
+ _rtld_object_match_name \
+ _rtld_add_paths \
+ _rtld_process_hints \
+ _rtld_sysctl \
+ _rtld_tls_allocate \
+ _rtld_tls_free \
+ _rtld_load_library
+
+.for _d in ${RTLD_FUNCS}
+CPPFLAGS+= -D${_d}=_elf64_${_d}
+.endfor
+
+.if (${MACHINE_ARCH} == "mips64el") || (${MACHINE_ARCH} == "mips64eb")
+CPPFLAGS+= -DRTLD_ARCH_SUBDIR=\"${MLIBDIR}\"
+.endif
+
+.include "../Makefile.elf"
+
+.endif
+
+.include <bsd.lib.mk>
diff --git a/usr.bin/ldd/ldd.1 b/usr.bin/ldd/ldd.1
new file mode 100644
index 0000000..6e49e9c
--- /dev/null
+++ b/usr.bin/ldd/ldd.1
@@ -0,0 +1,126 @@
+.\" $NetBSD: ldd.1,v 1.20 2017/12/25 05:08:49 maya Exp $
+.\"
+.\" Copyright (c) 1998 The NetBSD Foundation, Inc.
+.\" All rights reserved.
+.\"
+.\" This code is derived from software contributed to The NetBSD Foundation
+.\" by Paul Kranenburg.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\" notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must reproduce the above copyright
+.\" notice, this list of conditions and the following disclaimer in the
+.\" documentation and/or other materials provided with the distribution.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
+.\" ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+.\" TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+.\" PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
+.\" BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+.\" CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+.\" SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+.\" INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+.\" CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+.\" ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+.\" POSSIBILITY OF SUCH DAMAGE.
+.\"
+.Dd December 25, 2017
+.Dt LDD 1
+.Os
+.Sh NAME
+.Nm ldd
+.Nd list dynamic object dependencies
+.Sh SYNOPSIS
+.Nm
+.Op Fl o
+.Op Fl f Ar format
+.Ar program ...
+.Sh DESCRIPTION
+.Nm
+displays all shared objects that are needed to run the given program.
+Contrary to
+.Xr nm 1 ,
+the list includes
+.Dq indirect
+dependencies that are the result of needed shared objects which themselves
+depend on yet other shared objects.
+Zero, one or two
+.Fl f
+options may be given.
+The argument is a format string passed to
+.Xr rtld 1
+and allows customization of
+.Nm ldd Ns 's
+output.
+The first format argument is used for library objects and defaults to
+.Qq "\et-l%o.%m => %p\en" .
+The second format argument is used for non-library objects and defaults to
+.Qq "\et%o => %p\en" .
+.Pp
+These arguments are interpreted as format strings a la
+.Xr printf 3
+to customize the trace output and allow
+.Nm
+to be operated as a filter more conveniently.
+The following conversions can be used:
+.Bl -tag -width xxxx
+.It \&%a
+The main program's name
+.Po also known as
+.Dq __progname
+.Pc .
+.It \&%A
+The value of the environment variable
+.Ev LD_TRACE_LOADED_OBJECTS_PROGNAME
+in a.out and the program name from the argument vector from elf.
+.It \&%o
+The library name.
+.It \&%m
+The library's major version number.
+.It \&%n
+The library's minor version number (a.out only, ignored in elf).
+.It \&%p
+The full pathname as determined by
+.Nm rtld Ns 's
+library search rules.
+.It \&%x
+The library's load address
+.El
+.Pp
+Additionally,
+.Sy \en
+and
+.Sy \et
+are recognized and have their usual meaning.
+.Pp
+The
+.Fl o
+option is an alias for
+.Fl f
+.Ar \&%a:-l\&%o.\&%m => \&%p\en ,
+which makes
+.Nm
+behave analogously to
+.Ic nm Fl o .
+.Sh EXIT STATUS
+.Ex -std
+.Sh SEE ALSO
+.Xr ld 1 ,
+.Xr ld.elf_so 1 ,
+.Xr nm 1 ,
+.Xr rtld 1
+.Sh HISTORY
+A
+.Nm
+utility first appeared in SunOS 4.0, it appeared in its current form
+in
+.Nx 0.9a .
+.Sh BUGS
+The
+a.out
+.Nm
+actually runs the program it has been requested to analyze which in specially
+constructed environments can have security implications.
diff --git a/usr.bin/ldd/ldd.c b/usr.bin/ldd/ldd.c
new file mode 100644
index 0000000..a2794f8
--- /dev/null
+++ b/usr.bin/ldd/ldd.c
@@ -0,0 +1,237 @@
+/* $NetBSD: ldd.c,v 1.23 2017/12/25 05:08:49 maya Exp $ */
+
+/*-
+ * Copyright (c) 1998, 2000 The NetBSD Foundation, Inc.
+ * All rights reserved.
+ *
+ * This code is derived from software contributed to The NetBSD Foundation
+ * by Paul Kranenburg.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
+ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/*
+ * Copyright 1996 John D. Polstra.
+ * Copyright 1996 Matt Thomas <matt@3am-software.com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. All advertising materials mentioning features or use of this software
+ * must display the following acknowledgement:
+ * This product includes software developed by John Polstra.
+ * 4. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+#ifndef lint
+__RCSID("$NetBSD: ldd.c,v 1.23 2017/12/25 05:08:49 maya Exp $");
+#endif /* not lint */
+
+#include <sys/types.h>
+#include <sys/mman.h>
+#include <sys/wait.h>
+
+#include <dirent.h>
+#include <err.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <ctype.h>
+
+#include "debug.h"
+#include "rtld.h"
+#include "ldd.h"
+
+/*
+ * Data declarations.
+ */
+static char *error_message; /* Message for dlopen(), or NULL */
+bool _rtld_trust; /* False for setuid and setgid programs */
+/*
+ * This may be ELF64 or ELF32 but since they are used opaquely it doesn't
+ * really matter.
+ */
+Obj_Entry *_rtld_objlist; /* Head of linked list of shared objects */
+Obj_Entry **_rtld_objtail = &_rtld_objlist;
+ /* Link field of last object in list */
+u_int _rtld_objcount; /* Number of shared objects */
+u_int _rtld_objloads; /* Number of objects loaded */
+
+Obj_Entry *_rtld_objmain; /* The main program shared object */
+size_t _rtld_pagesz;
+
+Search_Path *_rtld_default_paths;
+Search_Path *_rtld_paths;
+Library_Xform *_rtld_xforms;
+
+static void usage(void) __dead;
+char *main_local;
+char *main_progname;
+
+static void
+usage(void)
+{
+ fprintf(stderr, "Usage: %s [-f <format 1>] [-f <format 2>] <filename>"
+ " ...\n", getprogname());
+ exit(1);
+}
+
+int
+main(int argc, char **argv)
+{
+ const char *fmt1 = NULL, *fmt2 = NULL;
+ int c, exit_status = EXIT_SUCCESS;
+
+#ifdef DEBUG
+ debug = 1;
+#endif
+ while ((c = getopt(argc, argv, "f:o")) != -1) {
+ switch (c) {
+ case 'f':
+ if (fmt1) {
+ if (fmt2)
+ errx(1, "Too many formats");
+ fmt2 = optarg;
+ } else
+ fmt1 = optarg;
+ break;
+ case 'o':
+ if (fmt1 || fmt2)
+ errx(1, "Cannot use -o and -f together");
+ fmt1 = "%a:-l%o.%m => %p\n";
+ break;
+ default:
+ usage();
+ /*NOTREACHED*/
+ }
+ }
+ argc -= optind;
+ argv += optind;
+
+ if (argc <= 0) {
+ usage();
+ /*NOTREACHED*/
+ }
+
+ for (; argc != 0; argc--, argv++) {
+ int fd;
+
+ fd = open(*argv, O_RDONLY);
+ if (fd == -1) {
+ exit_status = EXIT_FAILURE;
+ warn("%s", *argv);
+ continue;
+ }
+ if (elf_ldd(fd, *argv, fmt1, fmt2) == -1
+ /* Alpha never had 32 bit support. */
+#if (defined(_LP64) && !defined(ELF64_ONLY)) || defined(MIPS_N32)
+ && elf32_ldd(fd, *argv, fmt1, fmt2) == -1
+#if defined(__mips__) && 0 /* XXX this is still hosed for some reason */
+ && elf32_ldd_compat(fd, *argv, fmt1, fmt2) == -1
+#endif
+#endif
+ ) {
+ exit_status = EXIT_FAILURE;
+ warnx("%s", error_message);
+ }
+ close(fd);
+ }
+
+ return exit_status;
+}
+
+/*
+ * Error reporting function. Use it like printf. If formats the message
+ * into a buffer, and sets things up so that the next call to dlerror()
+ * will return the message.
+ */
+void
+_rtld_error(const char *fmt, ...)
+{
+ static char buf[512];
+ va_list ap;
+ va_start(ap, fmt);
+ xvsnprintf(buf, sizeof buf, fmt, ap);
+ error_message = buf;
+ va_end(ap);
+}
+
+char *
+dlerror()
+{
+ char *msg = error_message;
+ error_message = NULL;
+ return msg;
+}
+
+void
+_rtld_die(void)
+{
+ const char *msg = dlerror();
+
+ if (msg == NULL)
+ msg = "Fatal error";
+ xerrx(1, "%s", msg);
+}
+
+void
+_rtld_shared_enter(void)
+{
+}
+
+void
+_rtld_shared_exit(void)
+{
+}
+
+void
+_rtld_exclusive_enter(sigset_t *mask)
+{
+}
+
+void
+_rtld_exclusive_exit(sigset_t *mask)
+{
+}
diff --git a/usr.bin/ldd/ldd.h b/usr.bin/ldd/ldd.h
new file mode 100644
index 0000000..a3b4520
--- /dev/null
+++ b/usr.bin/ldd/ldd.h
@@ -0,0 +1,47 @@
+/* $NetBSD: ldd.h,v 1.7 2012/07/08 00:53:44 matt Exp $ */
+
+/*
+ * Copyright (c) 2008 Matthew R. Green
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+ * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+ * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+int elf32_ldd(int, char *, const char *, const char *);
+
+#ifdef _LP64
+#define LDD_ELF64
+#endif
+
+#ifdef LDD_ELF64
+int elf64_ldd(int, char *, const char *, const char *);
+#define elf_ldd elf64_ldd
+#elif defined(ELF32_COMPAT)
+#define elf_ldd elf32_compat_ldd
+#else
+#define elf_ldd elf32_ldd
+#endif
+
+extern char *main_local;
+extern char *main_progname;
diff --git a/usr.bin/ldd/ldd_elfxx.c b/usr.bin/ldd/ldd_elfxx.c
new file mode 100644
index 0000000..d5def64
--- /dev/null
+++ b/usr.bin/ldd/ldd_elfxx.c
@@ -0,0 +1,269 @@
+/* $NetBSD: ldd_elfxx.c,v 1.7 2017/01/10 21:11:25 christos Exp $ */
+
+/*-
+ * Copyright (c) 1998, 2000 The NetBSD Foundation, Inc.
+ * All rights reserved.
+ *
+ * This code is derived from software contributed to The NetBSD Foundation
+ * by Paul Kranenburg.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
+ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/*
+ * Copyright 1996 John D. Polstra.
+ * Copyright 1996 Matt Thomas <matt@3am-software.com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. All advertising materials mentioning features or use of this software
+ * must display the following acknowledgement:
+ * This product includes software developed by John Polstra.
+ * 4. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+#ifndef lint
+__RCSID("$NetBSD: ldd_elfxx.c,v 1.7 2017/01/10 21:11:25 christos Exp $");
+#endif /* not lint */
+
+#include <sys/types.h>
+#include <sys/mman.h>
+#include <sys/wait.h>
+#include <sys/stat.h>
+
+#include <dirent.h>
+#include <err.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <ctype.h>
+
+#include "debug.h"
+#include "rtld.h"
+#include "ldd.h"
+
+static void print_needed(Obj_Entry *, const char *, const char *);
+static void fmtprint(const char *, Obj_Entry *, const char *, const char *);
+
+/*
+ * elfxx_ldd() - bit-size independent ELF ldd implementation.
+ * returns 0 on success and -1 on failure.
+ */
+int
+ELFNAME(ldd)(int fd, char *path, const char *fmt1, const char *fmt2)
+{
+ struct stat st;
+
+ if (lseek(fd, 0, SEEK_SET) < 0 ||
+ fstat(fd, &st) < 0) {
+ _rtld_error("%s: %s", path, strerror(errno));
+ return -1;
+ }
+
+ _rtld_pagesz = sysconf(_SC_PAGESIZE);
+
+#ifdef RTLD_ARCH_SUBDIR
+ _rtld_add_paths(path, &_rtld_default_paths,
+ RTLD_DEFAULT_LIBRARY_PATH "/" RTLD_ARCH_SUBDIR);
+#endif
+ _rtld_add_paths(path, &_rtld_default_paths, RTLD_DEFAULT_LIBRARY_PATH);
+
+ _rtld_paths = NULL;
+ _rtld_trust = (st.st_mode & (S_ISUID | S_ISGID)) == 0;
+ if (_rtld_trust)
+ _rtld_add_paths(path, &_rtld_paths, getenv("LD_LIBRARY_PATH"));
+
+ _rtld_process_hints(path, &_rtld_paths, &_rtld_xforms, _PATH_LD_HINTS);
+ _rtld_objmain = _rtld_map_object(xstrdup(path), fd, &st);
+ if (_rtld_objmain == NULL)
+ return -1;
+
+ _rtld_objmain->path = xstrdup(path);
+ _rtld_digest_dynamic(path, _rtld_objmain);
+
+ /* Link the main program into the list of objects. */
+ *_rtld_objtail = _rtld_objmain;
+ _rtld_objtail = &_rtld_objmain->next;
+ ++_rtld_objmain->refcount;
+
+ (void) _rtld_load_needed_objects(_rtld_objmain, 0);
+
+ if (fmt1 == NULL)
+ printf("%s:\n", _rtld_objmain->path);
+ main_local = path;
+ main_progname = _rtld_objmain->path;
+ print_needed(_rtld_objmain, fmt1, fmt2);
+
+ while (_rtld_objlist != NULL) {
+ Obj_Entry *obj = _rtld_objlist;
+ _rtld_objlist = obj->next;
+ while (obj->rpaths != NULL) {
+ const Search_Path *rpath = obj->rpaths;
+ obj->rpaths = rpath->sp_next;
+ xfree(__UNCONST(rpath->sp_path));
+ xfree(__UNCONST(rpath));
+ }
+ while (obj->needed != NULL) {
+ const Needed_Entry *needed = obj->needed;
+ obj->needed = needed->next;
+ xfree(__UNCONST(needed));
+ }
+ (void) munmap(obj->mapbase, obj->mapsize);
+ xfree(obj->path);
+ xfree(obj);
+ }
+
+ _rtld_objmain = NULL;
+ _rtld_objtail = &_rtld_objlist;
+ /* Need to free _rtld_paths? */
+
+ return 0;
+}
+
+void
+fmtprint(const char *libname, Obj_Entry *obj, const char *fmt1,
+ const char *fmt2)
+{
+ const char *libpath = obj ? obj->path : "not found";
+ char libnamebuf[200];
+ char *libmajor = NULL;
+ const char *fmt;
+ char *cp;
+ int c;
+
+ if (strncmp(libname, "lib", 3) == 0 &&
+ (cp = strstr(libname, ".so")) != NULL) {
+ size_t i = cp - (libname + 3);
+
+ if (i >= sizeof(libnamebuf))
+ i = sizeof(libnamebuf) - 1;
+ (void)memcpy(libnamebuf, libname + 3, i);
+ libnamebuf[i] = '\0';
+ if (cp[3] && isdigit((unsigned char)cp[4]))
+ libmajor = &cp[4];
+ libname = libnamebuf;
+ }
+
+ if (fmt1 == NULL)
+ fmt1 = libmajor != NULL ?
+ "\t-l%o.%m => %p\n" :
+ "\t-l%o => %p\n";
+ if (fmt2 == NULL)
+ fmt2 = "\t%o => %p\n";
+
+ fmt = libname == libnamebuf ? fmt1 : fmt2;
+ while ((c = *fmt++) != '\0') {
+ switch (c) {
+ default:
+ putchar(c);
+ continue;
+ case '\\':
+ switch (c = *fmt) {
+ case '\0':
+ continue;
+ case 'n':
+ putchar('\n');
+ break;
+ case 't':
+ putchar('\t');
+ break;
+ }
+ break;
+ case '%':
+ switch (c = *fmt) {
+ case '\0':
+ continue;
+ case '%':
+ default:
+ putchar(c);
+ break;
+ case 'A':
+ printf("%s", main_local);
+ break;
+ case 'a':
+ printf("%s", main_progname);
+ break;
+ case 'o':
+ printf("%s", libname);
+ break;
+ case 'm':
+ printf("%s", libmajor);
+ break;
+ case 'n':
+ /* XXX: not supported for elf */
+ break;
+ case 'p':
+ printf("%s", libpath);
+ break;
+ case 'x':
+ printf("%p", obj ? obj->mapbase : 0);
+ break;
+ }
+ break;
+ }
+ ++fmt;
+ }
+}
+
+void
+print_needed(Obj_Entry *obj, const char *fmt1, const char *fmt2)
+{
+ const Needed_Entry *needed;
+
+ for (needed = obj->needed; needed != NULL; needed = needed->next) {
+ const char *libname = obj->strtab + needed->name;
+
+ if (needed->obj != NULL) {
+ if (!needed->obj->printed) {
+ fmtprint(libname, needed->obj, fmt1, fmt2);
+ needed->obj->printed = 1;
+ print_needed(needed->obj, fmt1, fmt2);
+ }
+ } else {
+ fmtprint(libname, needed->obj, fmt1, fmt2);
+ }
+ }
+}
diff --git a/usr.bin/locale/locale.1 b/usr.bin/locale/locale.1
new file mode 100644
index 0000000..5cd4b0e
--- /dev/null
+++ b/usr.bin/locale/locale.1
@@ -0,0 +1,113 @@
+.\" $NetBSD: locale.1,v 1.2 2003/07/04 07:28:14 wiz Exp $
+.\"
+.\" Copyright (c) 2003 Alexey Zelkin <phantom@FreeBSD.org>
+.\" All rights reserved.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\" notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must reproduce the above copyright
+.\" notice, this list of conditions and the following disclaimer in the
+.\" documentation and/or other materials provided with the distribution.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+.\" ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+.\" SUCH DAMAGE.
+.\"
+.\" FreeBSD: src/usr.bin/locale/locale.1,v 1.3 2003/06/26 11:01:03 phantom Exp
+.\"
+.Dd July 4, 2003
+.Dt LOCALE 1
+.Os
+.Sh NAME
+.Nm locale
+.Nd get locale-specific information
+.Sh SYNOPSIS
+.Nm
+.Op Fl a | Fl m
+.Nm
+.Op Fl ck
+.Op Ar keyword ...
+.Sh DESCRIPTION
+The
+.Nm
+utility is supposed to provide most locale specific information to
+the standard output.
+.Pp
+When
+.Nm
+is invoked without arguments it will print out a summary of the
+current locale environment depending on environment variable settings
+and internal status.
+.Pp
+When
+.Nm
+is invoked with arguments and no options specified it will print out
+.Em keyword Ns No 's
+value determined using current locale settings.
+.Pp
+The following options are available:
+.Bl -tag -width indent
+.It Fl a
+Write names of all available locales.
+While looking for locales
+.Nm
+will respect the
+.Ev PATH_LOCALE
+environment variable, and use it instead of the system default
+locale directory.
+.It Fl c
+Write the category name for the selected keywords.
+.It Fl k
+Write the name and value of the selected keywords.
+.It Fl m
+Write names of all available charmaps.
+.El
+.Sh IMPLEMENTATION DETAILS
+Special
+.Pf ( Fx Ns -
+/
+.Nx Ns -specific )
+keyword
+.Ar list
+can be used to retrieve a human readable list of available keywords.
+.Sh DIAGNOSTICS
+.Ex -std locale
+.Sh STANDARDS
+.Nm
+conforms to
+.St -p1003.1-2001 .
+.Sh HISTORY
+.Nm
+first appeared in
+.Nx 2.0 .
+.Sh AUTHORS
+This implementation of
+.Nm
+was originally written by
+.An Alexey Zelkin
+.Aq phantom@FreeBSD.org
+for
+.Fx .
+.Sh BUGS
+Since
+.Nx
+does not support
+.Em charmap Ns No s
+in their
+.Em POSIX
+meaning
+.Nm
+emulates the
+.Fl m
+option via CODESETs listing of all available locales.
diff --git a/usr.bin/locale/locale.c b/usr.bin/locale/locale.c
new file mode 100644
index 0000000..bd02d53
--- /dev/null
+++ b/usr.bin/locale/locale.c
@@ -0,0 +1,707 @@
+/* $NetBSD: locale.c,v 1.8 2012/01/20 16:31:30 joerg Exp $ */
+
+/*-
+ * Copyright (c) 2002, 2003 Alexey Zelkin <phantom@FreeBSD.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * FreeBSD: src/usr.bin/locale/locale.c,v 1.10 2003/06/26 11:05:56 phantom Exp
+ */
+
+#include <sys/cdefs.h>
+#if defined(LIBC_SCCS) && !defined(lint)
+__RCSID("$NetBSD: locale.c,v 1.8 2012/01/20 16:31:30 joerg Exp $");
+#endif /* LIBC_SCCS and not lint */
+
+/*
+ * XXX: implement missing era_* (LC_TIME) keywords (require libc &
+ * nl_langinfo(3) extensions)
+ *
+ * XXX: correctly handle reserved 'charmap' keyword and '-m' option (require
+ * localedef(1) implementation). Currently it's handled via
+ * nl_langinfo(CODESET).
+ */
+
+#include <sys/types.h>
+#include <assert.h>
+#include <dirent.h>
+#include <err.h>
+#include <locale.h>
+#include <langinfo.h>
+#include <limits.h>
+#include <paths.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stringlist.h>
+#include <unistd.h>
+
+#include "citrus_namespace.h"
+#include "citrus_region.h"
+#include "citrus_lookup.h"
+#include "setlocale_local.h"
+
+/* Local prototypes */
+void init_locales_list(void);
+void init_locales_list_alias(void);
+void list_charmaps(void);
+void list_locales(void);
+const char *lookup_localecat(int);
+char *kwval_lconv(int);
+int kwval_lookup(char *, char **, int *, int *);
+void showdetails(char *);
+void showkeywordslist(void);
+void showlocale(void);
+void usage(void);
+
+/* Global variables */
+static StringList *locales = NULL;
+
+int all_locales = 0;
+int all_charmaps = 0;
+int prt_categories = 0;
+int prt_keywords = 0;
+int more_params = 0;
+
+struct _lcinfo {
+ const char *name;
+ int id;
+} lcinfo [] = {
+ { "LC_CTYPE", LC_CTYPE },
+ { "LC_COLLATE", LC_COLLATE },
+ { "LC_TIME", LC_TIME },
+ { "LC_NUMERIC", LC_NUMERIC },
+ { "LC_MONETARY", LC_MONETARY },
+ { "LC_MESSAGES", LC_MESSAGES }
+};
+#define NLCINFO (sizeof(lcinfo)/sizeof(lcinfo[0]))
+
+/* ids for values not referenced by nl_langinfo() */
+#define KW_ZERO 10000
+#define KW_GROUPING (KW_ZERO+1)
+#define KW_INT_CURR_SYMBOL (KW_ZERO+2)
+#define KW_CURRENCY_SYMBOL (KW_ZERO+3)
+#define KW_MON_DECIMAL_POINT (KW_ZERO+4)
+#define KW_MON_THOUSANDS_SEP (KW_ZERO+5)
+#define KW_MON_GROUPING (KW_ZERO+6)
+#define KW_POSITIVE_SIGN (KW_ZERO+7)
+#define KW_NEGATIVE_SIGN (KW_ZERO+8)
+#define KW_INT_FRAC_DIGITS (KW_ZERO+9)
+#define KW_FRAC_DIGITS (KW_ZERO+10)
+#define KW_P_CS_PRECEDES (KW_ZERO+11)
+#define KW_P_SEP_BY_SPACE (KW_ZERO+12)
+#define KW_N_CS_PRECEDES (KW_ZERO+13)
+#define KW_N_SEP_BY_SPACE (KW_ZERO+14)
+#define KW_P_SIGN_POSN (KW_ZERO+15)
+#define KW_N_SIGN_POSN (KW_ZERO+16)
+#define KW_INT_P_CS_PRECEDES (KW_ZERO+17)
+#define KW_INT_P_SEP_BY_SPACE (KW_ZERO+18)
+#define KW_INT_N_CS_PRECEDES (KW_ZERO+19)
+#define KW_INT_N_SEP_BY_SPACE (KW_ZERO+20)
+#define KW_INT_P_SIGN_POSN (KW_ZERO+21)
+#define KW_INT_N_SIGN_POSN (KW_ZERO+22)
+
+struct _kwinfo {
+ const char *name;
+ int isstr; /* true - string, false - number */
+ int catid; /* LC_* */
+ int value_ref;
+ const char *comment;
+} kwinfo [] = {
+ { "charmap", 1, LC_CTYPE, CODESET, "" }, /* hack */
+
+ { "decimal_point", 1, LC_NUMERIC, RADIXCHAR, "" },
+ { "thousands_sep", 1, LC_NUMERIC, THOUSEP, "" },
+ { "grouping", 1, LC_NUMERIC, KW_GROUPING, "" },
+ { "radixchar", 1, LC_NUMERIC, RADIXCHAR,
+ "Same as decimal_point (BSD only)" }, /* compat */
+ { "thousep", 1, LC_NUMERIC, THOUSEP,
+ "Same as thousands_sep (BSD only)" }, /* compat */
+
+ { "int_curr_symbol", 1, LC_MONETARY, KW_INT_CURR_SYMBOL, "" },
+ { "currency_symbol", 1, LC_MONETARY, KW_CURRENCY_SYMBOL, "" },
+ { "mon_decimal_point", 1, LC_MONETARY, KW_MON_DECIMAL_POINT, "" },
+ { "mon_thousands_sep", 1, LC_MONETARY, KW_MON_THOUSANDS_SEP, "" },
+ { "mon_grouping", 1, LC_MONETARY, KW_MON_GROUPING, "" },
+ { "positive_sign", 1, LC_MONETARY, KW_POSITIVE_SIGN, "" },
+ { "negative_sign", 1, LC_MONETARY, KW_NEGATIVE_SIGN, "" },
+
+ { "int_frac_digits", 0, LC_MONETARY, KW_INT_FRAC_DIGITS, "" },
+ { "frac_digits", 0, LC_MONETARY, KW_FRAC_DIGITS, "" },
+ { "p_cs_precedes", 0, LC_MONETARY, KW_P_CS_PRECEDES, "" },
+ { "p_sep_by_space", 0, LC_MONETARY, KW_P_SEP_BY_SPACE, "" },
+ { "n_cs_precedes", 0, LC_MONETARY, KW_N_CS_PRECEDES, "" },
+ { "n_sep_by_space", 0, LC_MONETARY, KW_N_SEP_BY_SPACE, "" },
+ { "p_sign_posn", 0, LC_MONETARY, KW_P_SIGN_POSN, "" },
+ { "n_sign_posn", 0, LC_MONETARY, KW_N_SIGN_POSN, "" },
+ { "int_p_cs_precedes", 0, LC_MONETARY, KW_INT_P_CS_PRECEDES, "" },
+ { "int_p_sep_by_space", 0, LC_MONETARY, KW_INT_P_SEP_BY_SPACE, "" },
+ { "int_n_cs_precedes", 0, LC_MONETARY, KW_INT_N_CS_PRECEDES, "" },
+ { "int_n_sep_by_space", 0, LC_MONETARY, KW_INT_N_SEP_BY_SPACE, "" },
+ { "int_p_sign_posn", 0, LC_MONETARY, KW_INT_P_SIGN_POSN, "" },
+ { "int_n_sign_posn", 0, LC_MONETARY, KW_INT_N_SIGN_POSN, "" },
+
+ { "d_t_fmt", 1, LC_TIME, D_T_FMT, "" },
+ { "d_fmt", 1, LC_TIME, D_FMT, "" },
+ { "t_fmt", 1, LC_TIME, T_FMT, "" },
+ { "am_str", 1, LC_TIME, AM_STR, "" },
+ { "pm_str", 1, LC_TIME, PM_STR, "" },
+ { "t_fmt_ampm", 1, LC_TIME, T_FMT_AMPM, "" },
+ { "day_1", 1, LC_TIME, DAY_1, "" },
+ { "day_2", 1, LC_TIME, DAY_2, "" },
+ { "day_3", 1, LC_TIME, DAY_3, "" },
+ { "day_4", 1, LC_TIME, DAY_4, "" },
+ { "day_5", 1, LC_TIME, DAY_5, "" },
+ { "day_6", 1, LC_TIME, DAY_6, "" },
+ { "day_7", 1, LC_TIME, DAY_7, "" },
+ { "abday_1", 1, LC_TIME, ABDAY_1, "" },
+ { "abday_2", 1, LC_TIME, ABDAY_2, "" },
+ { "abday_3", 1, LC_TIME, ABDAY_3, "" },
+ { "abday_4", 1, LC_TIME, ABDAY_4, "" },
+ { "abday_5", 1, LC_TIME, ABDAY_5, "" },
+ { "abday_6", 1, LC_TIME, ABDAY_6, "" },
+ { "abday_7", 1, LC_TIME, ABDAY_7, "" },
+ { "mon_1", 1, LC_TIME, MON_1, "" },
+ { "mon_2", 1, LC_TIME, MON_2, "" },
+ { "mon_3", 1, LC_TIME, MON_3, "" },
+ { "mon_4", 1, LC_TIME, MON_4, "" },
+ { "mon_5", 1, LC_TIME, MON_5, "" },
+ { "mon_6", 1, LC_TIME, MON_6, "" },
+ { "mon_7", 1, LC_TIME, MON_7, "" },
+ { "mon_8", 1, LC_TIME, MON_8, "" },
+ { "mon_9", 1, LC_TIME, MON_9, "" },
+ { "mon_10", 1, LC_TIME, MON_10, "" },
+ { "mon_11", 1, LC_TIME, MON_11, "" },
+ { "mon_12", 1, LC_TIME, MON_12, "" },
+ { "abmon_1", 1, LC_TIME, ABMON_1, "" },
+ { "abmon_2", 1, LC_TIME, ABMON_2, "" },
+ { "abmon_3", 1, LC_TIME, ABMON_3, "" },
+ { "abmon_4", 1, LC_TIME, ABMON_4, "" },
+ { "abmon_5", 1, LC_TIME, ABMON_5, "" },
+ { "abmon_6", 1, LC_TIME, ABMON_6, "" },
+ { "abmon_7", 1, LC_TIME, ABMON_7, "" },
+ { "abmon_8", 1, LC_TIME, ABMON_8, "" },
+ { "abmon_9", 1, LC_TIME, ABMON_9, "" },
+ { "abmon_10", 1, LC_TIME, ABMON_10, "" },
+ { "abmon_11", 1, LC_TIME, ABMON_11, "" },
+ { "abmon_12", 1, LC_TIME, ABMON_12, "" },
+ { "era", 1, LC_TIME, ERA, "(unavailable)" },
+ { "era_d_fmt", 1, LC_TIME, ERA_D_FMT, "(unavailable)" },
+ { "era_d_t_fmt", 1, LC_TIME, ERA_D_T_FMT, "(unavailable)" },
+ { "era_t_fmt", 1, LC_TIME, ERA_T_FMT, "(unavailable)" },
+ { "alt_digits", 1, LC_TIME, ALT_DIGITS, "" },
+
+ { "yesexpr", 1, LC_MESSAGES, YESEXPR, "" },
+ { "noexpr", 1, LC_MESSAGES, NOEXPR, "" },
+ { "yesstr", 1, LC_MESSAGES, YESSTR,
+ "(POSIX legacy)" }, /* compat */
+ { "nostr", 1, LC_MESSAGES, NOSTR,
+ "(POSIX legacy)" } /* compat */
+
+};
+#define NKWINFO (sizeof(kwinfo)/sizeof(kwinfo[0]))
+
+int
+main(int argc, char *argv[])
+{
+ int ch;
+ int tmp;
+
+ while ((ch = getopt(argc, argv, "ackm")) != -1) {
+ switch (ch) {
+ case 'a':
+ all_locales = 1;
+ break;
+ case 'c':
+ prt_categories = 1;
+ break;
+ case 'k':
+ prt_keywords = 1;
+ break;
+ case 'm':
+ all_charmaps = 1;
+ break;
+ default:
+ usage();
+ }
+ }
+ argc -= optind;
+ argv += optind;
+
+ /* validate arguments */
+ if (all_locales && all_charmaps)
+ usage();
+ if ((all_locales || all_charmaps) && argc > 0)
+ usage();
+ if ((all_locales || all_charmaps) && (prt_categories || prt_keywords))
+ usage();
+ if ((prt_categories || prt_keywords) && argc <= 0)
+ usage();
+
+ /* process '-a' */
+ if (all_locales) {
+ list_locales();
+ exit(0);
+ }
+
+ /* process '-m' */
+ if (all_charmaps) {
+ list_charmaps();
+ exit(0);
+ }
+
+ /* check for special case '-k list' */
+ tmp = 0;
+ if (prt_keywords && argc > 0)
+ while (tmp < argc)
+ if (strcasecmp(argv[tmp++], "list") == 0) {
+ showkeywordslist();
+ exit(0);
+ }
+
+ /* process '-c' and/or '-k' */
+ if (prt_categories || prt_keywords || argc > 0) {
+ setlocale(LC_ALL, "");
+ while (argc > 0) {
+ showdetails(*argv);
+ argv++;
+ argc--;
+ }
+ exit(0);
+ }
+
+ /* no arguments, show current locale state */
+ showlocale();
+
+ return (0);
+}
+
+void
+usage(void)
+{
+ printf("usage: locale [ -a | -m ]\n"
+ " locale [ -ck ] name ...\n");
+ exit(1);
+}
+
+/*
+ * Output information about all available locales
+ *
+ * XXX actually output of this function does not guarantee that locale
+ * is really available to application, since it can be broken or
+ * inconsistent thus setlocale() will fail. Maybe add '-V' function to
+ * also validate these locales?
+ */
+void
+list_locales(void)
+{
+ size_t i;
+
+ init_locales_list();
+ for (i = 0; i < locales->sl_cur; i++) {
+ printf("%s\n", locales->sl_str[i]);
+ }
+}
+
+/*
+ * qsort() helper function
+ */
+static int
+scmp(const void *s1, const void *s2)
+{
+ return strcmp(*(const char **)s1, *(const char **)s2);
+}
+
+/*
+ * Output information about all available charmaps
+ *
+ * XXX this function is doing a task in hackish way, i.e. by scaning
+ * list of locales, spliting their codeset part and building list of
+ * them.
+ */
+void
+list_charmaps(void)
+{
+ size_t i;
+ char *s, *cs;
+ StringList *charmaps;
+
+ /* initialize StringList */
+ charmaps = sl_init();
+ if (charmaps == NULL)
+ err(1, "could not allocate memory");
+
+ /* fetch locales list */
+ init_locales_list();
+
+ /* split codesets and build their list */
+ for (i = 0; i < locales->sl_cur; i++) {
+ s = locales->sl_str[i];
+ if ((cs = strchr(s, '.')) != NULL) {
+ cs++;
+ if (sl_find(charmaps, cs) == NULL)
+ sl_add(charmaps, cs);
+ }
+ }
+
+ /* add US-ASCII, if not yet added */
+ if (sl_find(charmaps, "US-ASCII") == NULL)
+ sl_add(charmaps, "US-ASCII");
+
+ /* sort the list */
+ qsort(charmaps->sl_str, charmaps->sl_cur, sizeof(char *), scmp);
+
+ /* print results */
+ for (i = 0; i < charmaps->sl_cur; i++) {
+ printf("%s\n", charmaps->sl_str[i]);
+ }
+}
+
+/*
+ * Retrieve sorted list of system locales (or user locales, if PATH_LOCALE
+ * environment variable is set)
+ */
+void
+init_locales_list(void)
+{
+ DIR *dirp;
+ struct dirent *dp;
+ char *s;
+
+ /* why call this function twice ? */
+ if (locales != NULL)
+ return;
+
+ /* initialize StringList */
+ locales = sl_init();
+ if (locales == NULL)
+ err(1, "could not allocate memory");
+
+ /* get actual locales directory name */
+ setlocale(LC_CTYPE, "C");
+ if (_PathLocale == NULL)
+ errx(1, "unable to find locales storage");
+
+ /* open locales directory */
+ dirp = opendir(_PathLocale);
+ if (dirp == NULL)
+ err(1, "could not open directory '%s'", _PathLocale);
+
+ /* scan directory and store its contents except "." and ".." */
+ while ((dp = readdir(dirp)) != NULL) {
+ /* exclude "." and "..", _LOCALE_ALIAS_NAME */
+ if ((dp->d_name[0] != '.' || (dp->d_name[1] != '\0' &&
+ (dp->d_name[1] != '.' || dp->d_name[2] != '\0'))) &&
+ strcmp(_LOCALE_ALIAS_NAME, dp->d_name) != 0) {
+ s = strdup(dp->d_name);
+ if (s == NULL)
+ err(1, "could not allocate memory");
+ sl_add(locales, s);
+ }
+ }
+ closedir(dirp);
+
+ /* make sure that 'POSIX' and 'C' locales are present in the list.
+ * POSIX 1003.1-2001 requires presence of 'POSIX' name only here, but
+ * we also list 'C' for constistency
+ */
+ if (sl_find(locales, "POSIX") == NULL)
+ sl_add(locales, "POSIX");
+
+ if (sl_find(locales, "C") == NULL)
+ sl_add(locales, "C");
+
+ init_locales_list_alias();
+
+ /* make output nicer, sort the list */
+ qsort(locales->sl_str, locales->sl_cur, sizeof(char *), scmp);
+}
+
+void
+init_locales_list_alias(void)
+{
+ char aliaspath[PATH_MAX];
+ struct _lookup *hlookup;
+ struct _region key, dat;
+ size_t n;
+ char *s, *t;
+
+ _DIAGASSERT(locales != NULL);
+ _DIAGASSERT(_PathLocale != NULL);
+
+ (void)snprintf(aliaspath, sizeof(aliaspath),
+ "%s/" _LOCALE_ALIAS_NAME, _PathLocale);
+
+ if (_lookup_seq_open(&hlookup, aliaspath,
+ _LOOKUP_CASE_SENSITIVE) == 0) {
+ while (_lookup_seq_next(hlookup, &key, &dat) == 0) {
+ n = _region_size((const struct _region *)&key);
+ s = _region_head((const struct _region *)&key);
+ for (t = s; n > 0 && *s!= '/'; --n, ++s);
+ n = (size_t)(s - t);
+ s = malloc(n + 1);
+ if (s == NULL)
+ err(1, "could not allocate memory");
+ memcpy(s, t, n);
+ s[n] = '\0';
+ if (sl_find(locales, s) == NULL)
+ sl_add(locales, s);
+ else
+ free(s);
+ }
+ _lookup_seq_close(hlookup);
+ }
+}
+
+/*
+ * Show current locale status, depending on environment variables
+ */
+void
+showlocale(void)
+{
+ size_t i;
+ const char *lang, *vval, *eval;
+
+ setlocale(LC_ALL, "");
+
+ lang = getenv("LANG");
+ if (lang == NULL) {
+ lang = "";
+ }
+ printf("LANG=\"%s\"\n", lang);
+ /* XXX: if LANG is null, then set it to "C" to get implied values? */
+
+ for (i = 0; i < NLCINFO; i++) {
+ vval = setlocale(lcinfo[i].id, NULL);
+ eval = getenv(lcinfo[i].name);
+ if (eval != NULL && !strcmp(eval, vval)
+ && strcmp(lang, vval)) {
+ /*
+ * Appropriate environment variable set, its value
+ * is valid and not overriden by LC_ALL
+ *
+ * XXX: possible side effect: if both LANG and
+ * overriden environment variable are set into same
+ * value, then it'll be assumed as 'implied'
+ */
+ printf("%s=\"%s\"\n", lcinfo[i].name, vval);
+ } else {
+ printf("%s=\"%s\"\n", lcinfo[i].name, vval);
+ }
+ }
+
+ vval = getenv("LC_ALL");
+ if (vval == NULL) {
+ vval = "";
+ }
+ printf("LC_ALL=\"%s\"\n", vval);
+}
+
+/*
+ * keyword value lookup helper (via localeconv())
+ */
+char *
+kwval_lconv(int id)
+{
+ struct lconv *lc;
+ char *rval;
+
+ rval = NULL;
+ lc = localeconv();
+ switch (id) {
+ case KW_GROUPING:
+ rval = lc->grouping;
+ break;
+ case KW_INT_CURR_SYMBOL:
+ rval = lc->int_curr_symbol;
+ break;
+ case KW_CURRENCY_SYMBOL:
+ rval = lc->currency_symbol;
+ break;
+ case KW_MON_DECIMAL_POINT:
+ rval = lc->mon_decimal_point;
+ break;
+ case KW_MON_THOUSANDS_SEP:
+ rval = lc->mon_thousands_sep;
+ break;
+ case KW_MON_GROUPING:
+ rval = lc->mon_grouping;
+ break;
+ case KW_POSITIVE_SIGN:
+ rval = lc->positive_sign;
+ break;
+ case KW_NEGATIVE_SIGN:
+ rval = lc->negative_sign;
+ break;
+ case KW_INT_FRAC_DIGITS:
+ rval = &(lc->int_frac_digits);
+ break;
+ case KW_FRAC_DIGITS:
+ rval = &(lc->frac_digits);
+ break;
+ case KW_P_CS_PRECEDES:
+ rval = &(lc->p_cs_precedes);
+ break;
+ case KW_P_SEP_BY_SPACE:
+ rval = &(lc->p_sep_by_space);
+ break;
+ case KW_N_CS_PRECEDES:
+ rval = &(lc->n_cs_precedes);
+ break;
+ case KW_N_SEP_BY_SPACE:
+ rval = &(lc->n_sep_by_space);
+ break;
+ case KW_P_SIGN_POSN:
+ rval = &(lc->p_sign_posn);
+ break;
+ case KW_N_SIGN_POSN:
+ rval = &(lc->n_sign_posn);
+ break;
+ case KW_INT_P_CS_PRECEDES:
+ rval = &(lc->int_p_cs_precedes);
+ break;
+ case KW_INT_P_SEP_BY_SPACE:
+ rval = &(lc->int_p_sep_by_space);
+ break;
+ case KW_INT_N_CS_PRECEDES:
+ rval = &(lc->int_n_cs_precedes);
+ break;
+ case KW_INT_N_SEP_BY_SPACE:
+ rval = &(lc->int_n_sep_by_space);
+ break;
+ case KW_INT_P_SIGN_POSN:
+ rval = &(lc->int_p_sign_posn);
+ break;
+ case KW_INT_N_SIGN_POSN:
+ rval = &(lc->int_n_sign_posn);
+ break;
+ default:
+ break;
+ }
+ return (rval);
+}
+
+/*
+ * keyword value and properties lookup
+ */
+int
+kwval_lookup(char *kwname, char **kwval, int *cat, int *isstr)
+{
+ int rval;
+ size_t i;
+
+ rval = 0;
+ for (i = 0; i < NKWINFO; i++) {
+ if (strcasecmp(kwname, kwinfo[i].name) == 0) {
+ rval = 1;
+ *cat = kwinfo[i].catid;
+ *isstr = kwinfo[i].isstr;
+ if (kwinfo[i].value_ref < KW_ZERO) {
+ *kwval = nl_langinfo(kwinfo[i].value_ref);
+ } else {
+ *kwval = kwval_lconv(kwinfo[i].value_ref);
+ }
+ break;
+ }
+ }
+
+ return (rval);
+}
+
+/*
+ * Show details about requested keyword according to '-k' and/or '-c'
+ * command line options specified.
+ */
+void
+showdetails(char *kw)
+{
+ int isstr, cat, tmpval;
+ char *kwval;
+
+ if (kwval_lookup(kw, &kwval, &cat, &isstr) == 0) {
+ /*
+ * invalid keyword specified.
+ * XXX: any actions?
+ */
+ return;
+ }
+
+ if (prt_categories) {
+ printf("%s\n", lookup_localecat(cat));
+ }
+
+ if (prt_keywords) {
+ if (isstr) {
+ printf("%s=\"%s\"\n", kw, kwval);
+ } else {
+ tmpval = (char) *kwval;
+ printf("%s=%d\n", kw, tmpval);
+ }
+ }
+
+ if (!prt_categories && !prt_keywords) {
+ if (isstr) {
+ printf("%s\n", kwval);
+ } else {
+ tmpval = (char) *kwval;
+ printf("%d\n", tmpval);
+ }
+ }
+}
+
+/*
+ * Convert locale category id into string
+ */
+const char *
+lookup_localecat(int cat)
+{
+ size_t i;
+
+ for (i = 0; i < NLCINFO; i++)
+ if (lcinfo[i].id == cat) {
+ return (lcinfo[i].name);
+ }
+ return ("UNKNOWN");
+}
+
+/*
+ * Show list of keywords
+ */
+void
+showkeywordslist(void)
+{
+ size_t i;
+
+#define FMT "%-20s %-12s %-7s %-20s\n"
+
+ printf("List of available keywords\n\n");
+ printf(FMT, "Keyword", "Category", "Type", "Comment");
+ printf("-------------------- ------------ ------- --------------------\n");
+ for (i = 0; i < NKWINFO; i++) {
+ printf(FMT,
+ kwinfo[i].name,
+ lookup_localecat(kwinfo[i].catid),
+ (kwinfo[i].isstr == 0) ? "number" : "string",
+ kwinfo[i].comment);
+ }
+}
diff --git a/usr.bin/logger/logger.1 b/usr.bin/logger/logger.1
new file mode 100644
index 0000000..da47b68
--- /dev/null
+++ b/usr.bin/logger/logger.1
@@ -0,0 +1,117 @@
+.\" $NetBSD: logger.1,v 1.15 2012/05/13 17:08:31 njoly Exp $
+.\"
+.\" Copyright (c) 1983, 1990, 1993
+.\" The Regents of the University of California. All rights reserved.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\" notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must reproduce the above copyright
+.\" notice, this list of conditions and the following disclaimer in the
+.\" documentation and/or other materials provided with the distribution.
+.\" 3. Neither the name of the University nor the names of its contributors
+.\" may be used to endorse or promote products derived from this software
+.\" without specific prior written permission.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+.\" ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+.\" SUCH DAMAGE.
+.\"
+.\" @(#)logger.1 8.1 (Berkeley) 6/6/93
+.\"
+.Dd April 26, 2012
+.Dt LOGGER 1
+.Os
+.Sh NAME
+.Nm logger
+.Nd make entries in the system log
+.Sh SYNOPSIS
+.Nm
+.Op Fl cins
+.Op Fl d Ar SD
+.Op Fl f Ar file
+.Op Fl m Ar msgid
+.Op Fl p Ar pri
+.Op Fl t Ar tag
+.Op Ar message ...
+.Sh DESCRIPTION
+.Nm
+provides a shell command interface to the
+.Xr syslog 3
+system log module.
+.Pp
+Options:
+.Pp
+.Bl -tag -width "messageXX"
+.It Fl c
+Log to console
+.Dv ( LOG_CONS ) .
+.It Fl d Ar sd
+Log this in the structured data (SD) field.
+.Po
+.Ar sd
+has to be passed as one argument and will require careful quoting when used from
+the shell.
+.Pc
+.It Fl f Ar file
+Log the specified file.
+.It Fl i
+Log the process id of the logger process
+with each line
+.Dv ( LOG_PID ) .
+.It Fl m Ar msgid
+The MSGID used for the message.
+.It Fl n
+Open log file immediately
+.Dv ( LOG_NDELAY ) .
+.It Fl p Ar pri
+Enter the message with the specified priority.
+The priority may be specified numerically or as a
+.Dq facility.level
+pair.
+For example,
+.Dq \-p local3.info
+logs the message(s) as
+.Ar info Ns rmational
+level in the
+.Ar local3
+facility.
+The default is
+.Dq user.notice .
+.It Fl s
+Log the message to standard error, as well as the system log
+.Dv ( LOG_PERROR ) .
+.It Fl t Ar tag
+Mark every line in the log with the specified
+.Ar tag .
+.It Ar message
+Write the message to log; if not specified, and the
+.Fl f
+flag is not provided, standard input is logged.
+.El
+.Sh EXIT STATUS
+.Ex -std logger
+.Sh EXAMPLES
+.Bd -literal -offset indent -compact
+logger System rebooted
+
+logger \-p local0.notice \-t HOSTIDM \-f /dev/idmc
+.Ed
+.Sh SEE ALSO
+.Xr syslog 3 ,
+.Xr syslogd 8
+.Sh STANDARDS
+The
+.Nm
+utility conforms to
+.St -p1003.2-92 .
diff --git a/usr.bin/logger/logger.c b/usr.bin/logger/logger.c
new file mode 100644
index 0000000..b87aa5d
--- /dev/null
+++ b/usr.bin/logger/logger.c
@@ -0,0 +1,203 @@
+/* $NetBSD: logger.c,v 1.17 2012/04/27 06:30:48 wiz Exp $ */
+
+/*
+ * Copyright (c) 1983, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+#ifndef lint
+__COPYRIGHT("@(#) Copyright (c) 1983, 1993\
+ The Regents of the University of California. All rights reserved.");
+#endif /* not lint */
+
+#ifndef lint
+#if 0
+static char sccsid[] = "@(#)logger.c 8.1 (Berkeley) 6/6/93";
+#endif
+__RCSID("$NetBSD: logger.c,v 1.17 2012/04/27 06:30:48 wiz Exp $");
+#endif /* not lint */
+
+#include <errno.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <ctype.h>
+#include <string.h>
+#include <err.h>
+
+#define SYSLOG_NAMES
+#include <syslog.h>
+
+static int decode(const char *, const CODE *);
+static int pencode(char *);
+__dead static void usage(void);
+
+/*
+ * logger -- read and log utility
+ *
+ * Reads from an input and arranges to write the result on the system
+ * log.
+ */
+int
+main(int argc, char *argv[])
+{
+ int ch, logflags, pri;
+ const char *tag;
+ const char *sd = "-";
+ const char *msgid = "-";
+ char buf[1024];
+
+ tag = NULL;
+ pri = LOG_NOTICE;
+ logflags = 0;
+ while ((ch = getopt(argc, argv, "cd:f:im:np:st:")) != -1)
+ switch((char)ch) {
+ case 'c': /* log to console */
+ logflags |= LOG_CONS;
+ break;
+ case 'd': /* structured data field */
+ sd = optarg;
+ break;
+ case 'f': /* file to log */
+ if (freopen(optarg, "r", stdin) == NULL)
+ err(EXIT_FAILURE, "%s", optarg);
+ break;
+ case 'i': /* log process id also */
+ logflags |= LOG_PID;
+ break;
+ case 'm': /* msgid field */
+ msgid = optarg;
+ break;
+ case 'n': /* open log file immediately */
+ logflags |= LOG_NDELAY;
+ break;
+ case 'p': /* priority */
+ pri = pencode(optarg);
+ break;
+ case 's': /* log to standard error */
+ logflags |= LOG_PERROR;
+ break;
+ case 't': /* tag */
+ tag = optarg;
+ break;
+ case '?':
+ default:
+ usage();
+ }
+ argc -= optind;
+ argv += optind;
+
+ /* setup for logging */
+ openlog(tag != NULL ? tag : getlogin(), logflags, 0);
+ (void)fclose(stdout);
+
+ /* log input line if appropriate */
+ if (argc > 0) {
+ char *p, *endp;
+ size_t len;
+
+ for (p = buf, endp = buf + sizeof(buf) - 2; *argv != NULL;) {
+ len = strlen(*argv);
+ if (p + len > endp && p > buf) {
+ syslogp(pri, msgid, sd, "%s", buf);
+ p = buf;
+ }
+ if (len > sizeof(buf) - 1)
+ syslogp(pri, msgid, sd, "%s", *argv++);
+ else {
+ if (p != buf)
+ *p++ = ' ';
+ memmove(p, *argv++, len);
+ *(p += len) = '\0';
+ }
+ }
+ if (p != buf)
+ syslogp(pri, msgid, sd, "%s", buf);
+ } else /* TODO: allow syslog-protocol messages from file/stdin
+ * but that will require parsing the line to split
+ * it into three fields.
+ */
+ while (fgets(buf, sizeof(buf), stdin) != NULL)
+ syslogp(pri, msgid, sd, "%s", buf);
+
+ exit(EXIT_SUCCESS);
+ /* NOTREACHED */
+}
+
+/*
+ * Decode a symbolic name to a numeric value
+ */
+static int
+pencode(char *s)
+{
+ char *save;
+ int fac, lev;
+
+ for (save = s; *s != '\0' && *s != '.'; ++s)
+ ;
+ if (*s != '\0') {
+ *s = '\0';
+ fac = decode(save, facilitynames);
+ if (fac < 0)
+ errx(EXIT_FAILURE, "unknown facility name: %s", save);
+ *s++ = '.';
+ } else {
+ fac = 0;
+ s = save;
+ }
+ lev = decode(s, prioritynames);
+ if (lev < 0)
+ errx(EXIT_FAILURE, "unknown priority name: %s", s);
+ return ((lev & LOG_PRIMASK) | (fac & LOG_FACMASK));
+}
+
+static int
+decode(const char *name, const CODE *codetab)
+{
+ const CODE *c;
+
+ if (isdigit((unsigned char)*name))
+ return (atoi(name));
+
+ for (c = codetab; c->c_name != NULL; c++)
+ if (strcasecmp(name, c->c_name) == 0)
+ return (c->c_val);
+
+ return (-1);
+}
+
+static void
+usage(void)
+{
+
+ (void)fprintf(stderr,
+ "Usage: %s [-cins] [-d SD] [-f file] [-m msgid] "
+ "[-p pri] [-t tag] [message ...]\n",
+ getprogname());
+ exit(EXIT_FAILURE);
+}
diff --git a/usr.bin/logname/logname.1 b/usr.bin/logname/logname.1
new file mode 100644
index 0000000..242ac00
--- /dev/null
+++ b/usr.bin/logname/logname.1
@@ -0,0 +1,72 @@
+.\" $NetBSD: logname.1,v 1.14 2017/07/04 07:02:44 wiz Exp $
+.\"
+.\" Copyright (c) 1991, 1993
+.\" The Regents of the University of California. All rights reserved.
+.\"
+.\" This code is derived from software contributed to Berkeley by
+.\" the Institute of Electrical and Electronics Engineers, Inc.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\" notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must reproduce the above copyright
+.\" notice, this list of conditions and the following disclaimer in the
+.\" documentation and/or other materials provided with the distribution.
+.\" 3. Neither the name of the University nor the names of its contributors
+.\" may be used to endorse or promote products derived from this software
+.\" without specific prior written permission.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+.\" ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+.\" SUCH DAMAGE.
+.\"
+.\" @(#)logname.1 8.1 (Berkeley) 6/9/93
+.\"
+.Dd June 9, 1993
+.Dt LOGNAME 1
+.Os
+.Sh NAME
+.Nm logname
+.Nd display user's login name
+.Sh SYNOPSIS
+.Nm
+.Sh DESCRIPTION
+The
+.Nm
+utility writes the user's login name to standard output followed by
+a newline.
+.Pp
+The
+.Nm
+utility explicitly ignores the
+.Ev LOGNAME
+and
+.Ev USER
+environment variables
+because the environment cannot be trusted.
+.Sh EXIT STATUS
+.Ex -std logname
+.Sh SEE ALSO
+.Xr who 1 ,
+.Xr whoami 1 ,
+.Xr getlogin 2
+.Sh STANDARDS
+The
+.Nm
+utility conforms to
+.St -p1003.2-92 .
+.Sh HISTORY
+The
+.Nm
+command appeared in
+.Bx 4.4 .
diff --git a/usr.bin/logname/logname.c b/usr.bin/logname/logname.c
new file mode 100644
index 0000000..77f1009
--- /dev/null
+++ b/usr.bin/logname/logname.c
@@ -0,0 +1,85 @@
+/* $NetBSD: logname.c,v 1.10 2011/09/04 20:29:12 joerg Exp $ */
+
+/*-
+ * Copyright (c) 1991, 1993, 1994
+ * The Regents of the University of California. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+#ifndef lint
+__COPYRIGHT("@(#) Copyright (c) 1991, 1993, 1994\
+ The Regents of the University of California. All rights reserved.");
+#endif /* not lint */
+
+#ifndef lint
+#if 0
+static char sccsid[] = "@(#)logname.c 8.2 (Berkeley) 4/3/94";
+#endif
+__RCSID("$NetBSD: logname.c,v 1.10 2011/09/04 20:29:12 joerg Exp $");
+#endif /* not lint */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <locale.h>
+#include <unistd.h>
+#include <err.h>
+
+__dead static void usage(void);
+
+int
+main(int argc, char *argv[])
+{
+ int ch;
+ char *p;
+
+ setlocale(LC_ALL, "");
+
+ while ((ch = getopt(argc, argv, "")) != -1)
+ switch (ch) {
+ case '?':
+ default:
+ usage();
+ /* NOTREACHED */
+ }
+
+ if (argc != optind) {
+ usage();
+ /* NOTREACHED */
+ }
+
+ if ((p = getlogin()) == NULL)
+ err(1, "getlogin");
+ (void)printf("%s\n", p);
+ exit(0);
+}
+
+static void
+usage(void)
+{
+ (void)fprintf(stderr, "usage: logname\n");
+ exit(1);
+}
diff --git a/usr.bin/m4/NOTES b/usr.bin/m4/NOTES
new file mode 100644
index 0000000..d60f80e
--- /dev/null
+++ b/usr.bin/m4/NOTES
@@ -0,0 +1,64 @@
+m4 - macro processor
+
+PD m4 is based on the macro tool distributed with the software
+tools (VOS) package, and described in the "SOFTWARE TOOLS" and
+"SOFTWARE TOOLS IN PASCAL" books. It has been expanded to include
+most of the command set of SysV m4, the standard UN*X macro processor.
+
+Since both PD m4 and UN*X m4 are based on SOFTWARE TOOLS macro,
+there may be certain implementation similarities between
+the two. The PD m4 was produced without ANY references to m4
+sources.
+
+written by: Ozan S. Yigit
+
+References:
+
+ Software Tools distribution: macro
+
+ Kernighan, Brian W. and P. J. Plauger, SOFTWARE
+ TOOLS IN PASCAL, Addison-Wesley, Mass. 1981
+
+ Kernighan, Brian W. and P. J. Plauger, SOFTWARE
+ TOOLS, Addison-Wesley, Mass. 1976
+
+ Kernighan, Brian W. and Dennis M. Ritchie,
+ THE M4 MACRO PROCESSOR, Unix Programmer's Manual,
+ Seventh Edition, Vol. 2, Bell Telephone Labs, 1979
+
+ System V man page for M4
+
+
+Implementation Notes:
+
+[1] PD m4 uses a different (and simpler) stack mechanism than the one
+ described in Software Tools and Software Tools in Pascal books.
+ The triple stack thing is replaced with a single stack containing
+ the call frames and the arguments. Each frame is back-linked to a
+ previous stack frame, which enables us to rewind the stack after
+ each nested call is completed. Each argument is a character pointer
+ to the beginning of the argument string within the string space.
+ The only exceptions to this are (*) arg 0 and arg 1, which are
+ the macro definition and macro name strings, stored dynamically
+ for the hash table.
+
+ . .
+ | . | <-- sp | . |
+ +-------+ +-----+
+ | arg 3 ------------------------------->| str |
+ +-------+ | . |
+ | arg 2 --------------+ .
+ +-------+ |
+ * | | |
+ +-------+ | +-----+
+ | plev | <-- fp +---------------->| str |
+ +-------+ | . |
+ | type | .
+ +-------+
+ | prcf -----------+ plev: paren level
+ +-------+ | type: call type
+ | . | | prcf: prev. call frame
+ . |
+ +-------+ |
+ | <----------+
+ +-------+
diff --git a/usr.bin/m4/PSD.doc/Makefile b/usr.bin/m4/PSD.doc/Makefile
new file mode 100644
index 0000000..9de1f83
--- /dev/null
+++ b/usr.bin/m4/PSD.doc/Makefile
@@ -0,0 +1,10 @@
+# $NetBSD: Makefile,v 1.4 2014/07/05 19:22:04 dholland Exp $
+#
+# @(#)Makefile 8.1 (Berkeley) 6/8/93
+
+SECTION=psd
+ARTICLE=m4
+SRCS= m4.ms
+MACROS= -msU
+
+.include <bsd.doc.mk>
diff --git a/usr.bin/m4/TEST/ack.m4 b/usr.bin/m4/TEST/ack.m4
new file mode 100644
index 0000000..2784c45
--- /dev/null
+++ b/usr.bin/m4/TEST/ack.m4
@@ -0,0 +1,41 @@
+# $NetBSD: ack.m4,v 1.4 1995/09/28 05:37:54 tls Exp $
+#
+# Copyright (c) 1989, 1993
+# The Regents of the University of California. All rights reserved.
+#
+# This code is derived from software contributed to Berkeley by
+# Ozan Yigit.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1. Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+# 3. All advertising materials mentioning features or use of this software
+# must display the following acknowledgement:
+# This product includes software developed by the University of
+# California, Berkeley and its contributors.
+# 4. Neither the name of the University nor the names of its contributors
+# may be used to endorse or promote products derived from this software
+# without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+# ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+# SUCH DAMAGE.
+#
+# @(#)ack.m4 8.1 (Berkeley) 6/6/93
+#
+
+define(ack, `ifelse($1,0,incr($2),$2,0,`ack(DECR($1),1)',
+`ack(DECR($1), ack($1,DECR($2)))')')
diff --git a/usr.bin/m4/TEST/hanoi.m4 b/usr.bin/m4/TEST/hanoi.m4
new file mode 100644
index 0000000..6f73431
--- /dev/null
+++ b/usr.bin/m4/TEST/hanoi.m4
@@ -0,0 +1,46 @@
+# $NetBSD: hanoi.m4,v 1.4 1995/09/28 05:37:56 tls Exp $
+#
+# Copyright (c) 1989, 1993
+# The Regents of the University of California. All rights reserved.
+#
+# This code is derived from software contributed to Berkeley by
+# Ozan Yigit.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1. Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+# 3. All advertising materials mentioning features or use of this software
+# must display the following acknowledgement:
+# This product includes software developed by the University of
+# California, Berkeley and its contributors.
+# 4. Neither the name of the University nor the names of its contributors
+# may be used to endorse or promote products derived from this software
+# without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+# ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+# SUCH DAMAGE.
+#
+# @(#)hanoi.m4 8.1 (Berkeley) 6/6/93
+#
+
+define(hanoi, `trans(A, B, C, $1)')
+
+define(moved,`move disk from $1 to $2
+')
+
+define(trans, `ifelse($4,1,`moved($1,$2)',
+ `trans($1,$3,$2,DECR($4))moved($1,$2)trans($3,$2,$1,DECR($4))')')
diff --git a/usr.bin/m4/TEST/hash.m4 b/usr.bin/m4/TEST/hash.m4
new file mode 100644
index 0000000..5de2f79
--- /dev/null
+++ b/usr.bin/m4/TEST/hash.m4
@@ -0,0 +1,56 @@
+# $NetBSD: hash.m4,v 1.4 1995/09/28 05:37:58 tls Exp $
+#
+# Copyright (c) 1989, 1993
+# The Regents of the University of California. All rights reserved.
+#
+# This code is derived from software contributed to Berkeley by
+# Ozan Yigit.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1. Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+# 3. All advertising materials mentioning features or use of this software
+# must display the following acknowledgement:
+# This product includes software developed by the University of
+# California, Berkeley and its contributors.
+# 4. Neither the name of the University nor the names of its contributors
+# may be used to endorse or promote products derived from this software
+# without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+# ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+# SUCH DAMAGE.
+#
+# @(#)hash.m4 8.1 (Berkeley) 6/6/93
+#
+
+dnl This probably will not run on any m4 that cannot
+dnl handle char constants in eval.
+dnl
+changequote(<,>) define(HASHVAL,99) dnl
+define(hash,<eval(str(substr($1,1),0)%HASHVAL)>) dnl
+define(str,
+ <ifelse($1,",$2,
+ <str(substr(<$1>,1),<eval($2+'substr($1,0,1)')>)>)
+ >) dnl
+define(KEYWORD,<$1,hash($1),>) dnl
+define(TSTART,
+<struct prehash {
+ char *keyword;
+ int hashval;
+} keytab[] = {>) dnl
+define(TEND,< "",0
+};>) dnl
diff --git a/usr.bin/m4/TEST/math.m4 b/usr.bin/m4/TEST/math.m4
new file mode 100644
index 0000000..dff3937
--- /dev/null
+++ b/usr.bin/m4/TEST/math.m4
@@ -0,0 +1,182 @@
+dnl $NetBSD: math.m4,v 1.2 2005/10/06 17:38:09 drochner Exp $
+dnl FreeBSD: /repoman/r/ncvs/src/usr.bin/m4/TEST/math.m4,v 1.1 2004/05/01 03:27:05 smkelly Exp
+dnl A regression test for m4 C operators (ksb,petef)
+dnl If you think you have a short-circuiting m4, run us m4 -DSHORCIRCUIT=yes
+dnl
+dnl first level of precedence
+ifelse(eval(-7),-7,,`failed -
+')dnl
+ifelse(eval(- -2),2,,`failed -
+')dnl
+ifelse(eval(!0),1,,`failed !
+')dnl
+ifelse(eval(!7),0,,`failed !
+')dnl
+ifelse(eval(~-1),0,,`failed ~
+')dnl
+dnl next level of precedence
+ifelse(eval(3*5),15,,`failed *
+')dnl
+ifelse(eval(3*0),0,,`failed *
+')dnl
+ifelse(eval(11/2),5,,`failed /
+')dnl
+ifelse(eval(1/700),0,,`failed /
+')dnl
+ifelse(eval(10%5),0,,`failed %
+')dnl
+ifelse(eval(2%5),2,,`failed %
+')dnl
+ifelse(eval(2%-1),0,,`failed %
+')dnl
+dnl next level of precedence
+ifelse(eval(2+2),4,,`failed +
+')dnl
+ifelse(eval(2+-2),0,,`failed +
+')dnl
+ifelse(eval(2- -2),4,,`failed -
+')dnl
+ifelse(eval(2-2),0,,`failed -
+')dnl
+dnl next level of precedence
+ifelse(eval(1<<4),16,,`failed <<
+')dnl
+ifelse(eval(16>>4),1,,`failed >>
+')dnl
+dnl next level of precedence
+ifelse(eval(4<4),0,,`failed <
+')dnl
+ifelse(eval(4<5),1,,`failed <
+')dnl
+ifelse(eval(4<3),0,,`failed <
+')dnl
+ifelse(eval(4>4),0,,`failed >
+')dnl
+ifelse(eval(4>5),0,,`failed >
+')dnl
+ifelse(eval(4>3),1,,`failed >
+')dnl
+ifelse(eval(4<=4),1,,`failed <=
+')dnl
+ifelse(eval(4<=5),1,,`failed <=
+')dnl
+ifelse(eval(4<=3),0,,`failed <=
+')dnl
+ifelse(eval(4>=4),1,,`failed >=
+')dnl
+ifelse(eval(4>=5),0,,`failed >=
+')dnl
+ifelse(eval(4>=3),1,,`failed >=
+')dnl
+dnl next level of precedence
+ifelse(eval(1==1),1,,`failed ==
+')dnl
+ifelse(eval(1==-1),0,,`failed ==
+')dnl
+ifelse(eval(1!=1),0,,`failed !=
+')dnl
+ifelse(eval(1!=2),1,,`failed !=
+')dnl
+dnl next level of precedence
+ifelse(eval(3&5),1,,`failed &
+')dnl
+ifelse(eval(8&7),0,,`failed &
+')dnl
+dnl next level of precedence
+ifelse(eval(1^1),0,,`failed ^
+')dnl
+ifelse(eval(21^5),16,,`failed ^
+')dnl
+dnl next level of precedence
+ifelse(eval(1|1),1,,`failed |
+')dnl
+ifelse(eval(21|5),21,,`failed |
+')dnl
+ifelse(eval(100|1),101,,`failed |
+')dnl
+dnl next level of precedence
+ifelse(eval(1&&1),1,,`failed &&
+')dnl
+ifelse(eval(0&&1),0,,`failed &&
+')dnl
+ifelse(eval(1&&0),0,,`failed &&
+')dnl
+ifelse(SHORTCIRCUIT,`yes',`ifelse(eval(0&&10/0),0,,`failed && shortcircuit
+')')dnl
+dnl next level of precedence
+ifelse(eval(1||1),1,,`failed ||
+')dnl
+ifelse(eval(1||0),1,,`failed ||
+')dnl
+ifelse(eval(0||0),0,,`failed ||
+')dnl
+ifelse(SHORTCIRCUIT,`yes',`ifelse(eval(1||10/0),1,,`failed || shortcircuit
+')')dnl
+dnl next level of precedence
+ifelse(eval(0 ? 2 : 5),5,,`failed ?:
+')dnl
+ifelse(eval(1 ? 2 : 5),2,,`failed ?:
+')dnl
+ifelse(SHORTCIRCUIT,`yes',`ifelse(eval(0 ? 10/0 : 7),7,,`failed ?: shortcircuit
+')')dnl
+ifelse(SHORTCIRCUIT,`yes',`ifelse(eval(1 ? 7 : 10/0),7,,`failed ?: shortcircuit
+')')dnl
+dnl operator precedence
+ifelse(eval(!0*-2),-2,,`precedence wrong, ! *
+')dnl
+ifelse(eval(~8/~2),3,,`precedence wrong ~ /
+')dnl
+ifelse(eval(~-20%7),5,,`precedence wrong ~ %
+')dnl
+ifelse(eval(3*2+100),106,,`precedence wrong * +
+')dnl
+ifelse(eval(3+2*100),203,,`precedence wrong + *
+')dnl
+ifelse(eval(2%5-6/3),0,,`precedence wrong % -
+')dnl
+ifelse(eval(2/5-5%3),-2,,`precedence wrong / -
+')dnl
+ifelse(eval(2+5%5+1),3,,`precedence wrong % +
+')dnl
+ifelse(eval(7+9<<1),32,,`precedence wrong + <<
+')dnl
+ifelse(eval(35-3>>2),8,,`precedence wrong - >>
+')dnl
+ifelse(eval(9<10<<5),1,,`precedence wrong << <
+')dnl
+ifelse(eval(9>10<<5),0,,`precedence wrong << >
+')dnl
+ifelse(eval(32>>2<32),1,,`precedence wrong >> <
+')dnl
+ifelse(eval(9<=10<<5),1,,`precedence wrong << <
+')dnl
+ifelse(eval(5<<1<=20>>1),1,,`precedence wrong << <=
+')dnl
+ifelse(eval(5<<1>=20>>1),1,,`precedence wrong << >=
+')dnl
+ifelse(eval(0<7==5>=5),1,,`precedence wrong < ==
+')dnl
+ifelse(eval(0<7!=5>=5),0,,`precedence wrong < !=
+')dnl
+ifelse(eval(0>7==5>=5),0,,`precedence wrong > ==
+')dnl
+ifelse(eval(0>7!=5>=5),1,,`precedence wrong > !=
+')dnl
+ifelse(eval(1&7==7),1,,`precedence wrong & ==
+')dnl
+ifelse(eval(0&7!=6),0,,`precedence wrong & !=
+')dnl
+ifelse(eval(9&1|5),5,,`precedence wrong & |
+')dnl
+ifelse(eval(9&1^5),4,,`precedence wrong & ^
+')dnl
+ifelse(eval(9^1|5),13,,`precedence wrong ^ |
+')dnl
+ifelse(eval(5|0&&1),1,,`precedence wrong | &&
+')dnl
+ifelse(eval(5&&0||0&&5||5),1,,`precedence wrong && ||
+')dnl
+ifelse(eval(0 || 1 ? 0 : 1),0,,`precedence wrong || ?:
+')dnl
+ifelse(eval(5&&(0||0)&&(5||5)),0,,`precedence wrong || parens
+')dnl
diff --git a/usr.bin/m4/TEST/sqroot.m4 b/usr.bin/m4/TEST/sqroot.m4
new file mode 100644
index 0000000..6fc8d2c
--- /dev/null
+++ b/usr.bin/m4/TEST/sqroot.m4
@@ -0,0 +1,46 @@
+# $NetBSD: sqroot.m4,v 1.4 1995/09/28 05:38:01 tls Exp $
+#
+# Copyright (c) 1989, 1993
+# The Regents of the University of California. All rights reserved.
+#
+# This code is derived from software contributed to Berkeley by
+# Ozan Yigit.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1. Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+# 3. All advertising materials mentioning features or use of this software
+# must display the following acknowledgement:
+# This product includes software developed by the University of
+# California, Berkeley and its contributors.
+# 4. Neither the name of the University nor the names of its contributors
+# may be used to endorse or promote products derived from this software
+# without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+# ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+# SUCH DAMAGE.
+#
+# @(#)sqroot.m4 8.1 (Berkeley) 6/6/93
+#
+
+define(square_root,
+ `ifelse(eval($1<0),1,negative-square-root,
+ `square_root_aux($1, 1, eval(($1+1)/2))')')
+define(square_root_aux,
+ `ifelse($3, $2, $3,
+ $3, eval($1/$2), $3,
+ `square_root_aux($1, $3, eval(($3+($1/$3))/2))')')
diff --git a/usr.bin/m4/TEST/string.m4 b/usr.bin/m4/TEST/string.m4
new file mode 100644
index 0000000..a14f14a
--- /dev/null
+++ b/usr.bin/m4/TEST/string.m4
@@ -0,0 +1,46 @@
+# $NetBSD: string.m4,v 1.4 1995/09/28 05:38:03 tls Exp $
+#
+# Copyright (c) 1989, 1993
+# The Regents of the University of California. All rights reserved.
+#
+# This code is derived from software contributed to Berkeley by
+# Ozan Yigit.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1. Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+# 3. All advertising materials mentioning features or use of this software
+# must display the following acknowledgement:
+# This product includes software developed by the University of
+# California, Berkeley and its contributors.
+# 4. Neither the name of the University nor the names of its contributors
+# may be used to endorse or promote products derived from this software
+# without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+# ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+# SUCH DAMAGE.
+#
+# @(#)string.m4 8.1 (Berkeley) 6/6/93
+#
+
+define(string,`integer $1(len(substr($2,1)))
+str($1,substr($2,1),0)
+data $1(len(substr($2,1)))/EOS/
+')
+
+define(str,`ifelse($2,",,data $1(incr($3))/`LET'substr($2,0,1)/
+`str($1,substr($2,1),incr($3))')')
diff --git a/usr.bin/m4/TEST/test.m4 b/usr.bin/m4/TEST/test.m4
new file mode 100644
index 0000000..5b19807
--- /dev/null
+++ b/usr.bin/m4/TEST/test.m4
@@ -0,0 +1,244 @@
+# $NetBSD: test.m4,v 1.4 1995/09/28 05:38:05 tls Exp $
+#
+# Copyright (c) 1989, 1993
+# The Regents of the University of California. All rights reserved.
+#
+# This code is derived from software contributed to Berkeley by
+# Ozan Yigit.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1. Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+# 3. All advertising materials mentioning features or use of this software
+# must display the following acknowledgement:
+# This product includes software developed by the University of
+# California, Berkeley and its contributors.
+# 4. Neither the name of the University nor the names of its contributors
+# may be used to endorse or promote products derived from this software
+# without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+# ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+# SUCH DAMAGE.
+#
+# @(#)test.m4 8.1 (Berkeley) 6/6/93
+#
+
+# test file for mp (not comprehensive)
+#
+# v7 m4 does not have `decr'.
+#
+define(DECR,`eval($1-1)')
+#
+# include string macros
+#
+include(string.m4)
+#
+# create some fortrash strings for an even uglier language
+#
+string(TEXT, "text")
+string(DATA, "data")
+string(BEGIN, "begin")
+string(END, "end")
+string(IF, "if")
+string(THEN, "then")
+string(ELSE, "else")
+string(CASE, "case")
+string(REPEAT, "repeat")
+string(WHILE, "while")
+string(DEFAULT, "default")
+string(UNTIL, "until")
+string(FUNCTION, "function")
+string(PROCEDURE, "procedure")
+string(EXTERNAL, "external")
+string(FORWARD, "forward")
+string(TYPE, "type")
+string(VAR, "var")
+string(CONST, "const")
+string(PROGRAM, "program")
+string(INPUT, "input")
+string(OUTPUT, "output")
+#
+divert(2)
+diversion #1
+divert(3)
+diversion #2
+divert(4)
+diversion #3
+divert(5)
+diversion #4
+divert(0)
+define(abc,xxx)
+ifdef(`abc',defined,undefined)
+#
+# v7 m4 does this wrong. The right output is
+# this is A vEry lon sEntEnCE
+# see m4 documentation for translit.
+#
+translit(`this is a very long sentence', abcdefg, ABCDEF)
+#
+# include towers-of-hanoi
+#
+include(hanoi.m4)
+#
+# some reasonable set of disks
+#
+hanoi(6)
+#
+# include ackermann's function
+#
+include(ack.m4)
+#
+# something like (3,3) will blow away un*x m4.
+#
+ack(2,3)
+#
+# include a square_root function for fixed nums
+#
+include(sqroot.m4)
+#
+# some square roots.
+#
+square_root(15)
+square_root(100)
+square_root(-4)
+square_root(21372)
+#
+# some textual material for enjoyment.
+#
+[taken from the 'Clemson University Computer Newsletter',
+ September 1981, pp. 6-7]
+
+I am a wizard in the magical Kingdom of Transformation and I
+slay dragons for a living. Actually, I am a systems programmer.
+One of the problems with systems programming is explaining to
+non-computer enthusiasts what that is. All of the terms I use to
+describe my job are totally meaningless to them. Usually my response
+to questions about my work is to say as little as possible. For
+instance, if someone asks what happened at work this week, I say
+"Nothing much" and then I change the subject.
+
+With the assistance of my brother, a mechanical engineer, I have devised
+an analogy that everyone can understand. The analogy describes the
+"Kingdom of Transformation" where travelers wander and are magically
+transformed. This kingdom is the computer and the travelers are information.
+The purpose of the computer is to change information to a more meaningful
+forma. The law of conservation applies here: The computer never creates
+and never intentionally destroys data. With no further ado, let us travel
+to the Kingdom of Transformation:
+
+In a land far, far away, there is a magical kingdom called the Kingdom of
+Transformation. A king rules over this land and employs a Council of
+Wizardry. The main purpose of this kingdom is to provide a way for
+neighboring kingdoms to transform citizens into more useful citizens. This
+is done by allowing the citizens to enter the kingdom at one of its ports
+and to travel any of the many routes in the kingdom. They are magically
+transformed along the way. The income of the Kingdom of Transformation
+comes from the many toll roads within its boundaries.
+
+The Kingdom of Transformation was created when several kingdoms got
+together and discovered a mutual need for new talents and abilities for
+citizens. They employed CTK, Inc. (Creators of Transformation, Inc.) to
+create this kingdom. CTK designed the country, its transportation routes,
+and its laws of transformation, and created the major highway system.
+
+Hazards
+=======
+
+Because magic is not truly controllable, CTK invariably, but unknowingly,
+creates dragons. Dragons are huge fire-breathing beasts which sometimes
+injure or kill travelers. Fortunately, they do not travel, but always
+remain near their den.
+
+Other hazards also exist which are potentially harmful. As the roads
+become older and more weatherbeaten, pot-holes will develop, trees will
+fall on travelers, etc. CTK maintenance men are called to fix these
+problems.
+
+Wizards
+=======
+
+The wizards play a major role in creating and maintaining the kingdom but
+get little credit for their work because it is performed secretly. The
+wizards do not wan the workers or travelers to learn their incantations
+because many laws would be broken and chaos would result.
+
+CTK's grand design is always general enough to be applicable in many
+different situations. As a result, it is often difficult to use. The
+first duty of the wizards is to tailor the transformation laws so as to be
+more beneficial and easier to use in their particular environment.
+
+After creation of the kingdom, a major duty of the wizards is to search for
+and kill dragons. If travelers do not return on time or if they return
+injured, the ruler of the country contacts the wizards. If the wizards
+determine that the injury or death occurred due to the traveler's
+negligence, they provide the traveler's country with additional warnings.
+If not, they must determine if the cause was a road hazard or a dragon. If
+the suspect a road hazard, they call in a CTK maintenance man to locate the
+hazard and to eliminate it, as in repairing the pothole in the road. If
+they think that cause was a dragon, then they must find and slay it.
+
+The most difficult part of eliminating a dragon is finding it. Sometimes
+the wizard magically knows where the dragon's lair it, but often the wizard
+must send another traveler along the same route and watch to see where he
+disappears. This sounds like a failsafe method for finding dragons (and a
+suicide mission for thr traveler) but the second traveler does not always
+disappear. Some dragons eat any traveler who comes too close; others are
+very picky.
+
+The wizards may call in CTK who designed the highway system and
+transformation laws to help devise a way to locate the dragon. CTK also
+helps provide the right spell or incantation to slay the dragon. (There is
+no general spell to slay dragons; each dragon must be eliminated with a
+different spell.)
+
+Because neither CTK nor wizards are perfect, spells to not always work
+correctly. At best, nothing happens when the wrong spell is uttered. At
+worst, the dragon becomes a much larger dragon or multiplies into several
+smaller ones. In either case, new spells must be found.
+
+If all existing dragons are quiet (i.e. have eaten sufficiently), wizards
+have time to do other things. They hide in castles and practice spells and
+incatations. They also devise shortcuts for travelers and new laws of
+transformation.
+
+Changes in the Kingdom
+======================
+
+As new transformation kingdoms are created and old ones are maintained,
+CTK, Inc. is constantly learning new things. It learns ways to avoid
+creating some of the dragons that they have previously created. It also
+discovers new and better laws of transformation. As a result, CTK will
+periodically create a new grand design which is far better than the old.
+The wizards determine when is a good time to implement this new design.
+This is when the tourist season is slow or when no important travelers
+(VIPs) are to arrive. The kingdom must be closed for the actual
+implementation and is leter reopened as a new and better place to go.
+
+A final question you might ask is what happens when the number of tourists
+becomes too great for the kingdom to handle in a reasonable period of time
+(i.e., the tourist lines at the ports are too long). The Kingdom of
+Transformation has three options: (1) shorten the paths that a tourist must
+travel, or (2) convince CTK to develop a faster breed of horses so that the
+travelers can finish sooner, or (3) annex more territories so that the
+kingdom can handle more travelers.
+
+Thus ends the story of the Kingdom of Transformation. I hope this has
+explained my job to you: I slay dragons for a living.
+
+#
+#should do an automatic undivert..
+#
diff --git a/usr.bin/m4/eval.c b/usr.bin/m4/eval.c
new file mode 100644
index 0000000..94150b1
--- /dev/null
+++ b/usr.bin/m4/eval.c
@@ -0,0 +1,1053 @@
+/* $OpenBSD: eval.c,v 1.66 2008/08/21 21:01:47 espie Exp $ */
+/* $NetBSD: eval.c,v 1.27 2018/07/30 22:58:09 kre Exp $ */
+
+/*
+ * Copyright (c) 1989, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Ozan Yigit at York University.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+/*
+ * eval.c
+ * Facility: m4 macro processor
+ * by: oz
+ */
+#if HAVE_NBTOOL_CONFIG_H
+#include "nbtool_config.h"
+#endif
+#include <sys/cdefs.h>
+__RCSID("$NetBSD: eval.c,v 1.27 2018/07/30 22:58:09 kre Exp $");
+
+#include <sys/types.h>
+#include <ctype.h>
+#include <err.h>
+#include <errno.h>
+#include <limits.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <stddef.h>
+#include <stdint.h>
+#include <string.h>
+#include <inttypes.h>
+#include <fcntl.h>
+#include "mdef.h"
+#include "stdd.h"
+#include "extern.h"
+#include "pathnames.h"
+
+static void dodefn(const char *);
+static void dopushdef(const char *, const char *);
+static void dodump(const char *[], int);
+static void dotrace(const char *[], int, int);
+static void doifelse(const char *[], int);
+static int doincl(const char *);
+static int dopaste(const char *);
+static void dochq(const char *[], int);
+static void dochc(const char *[], int);
+static void dom4wrap(const char *);
+static void dodiv(int);
+static void doundiv(const char *[], int);
+static void dosub(const char *[], int);
+static void map(char *, const char *, const char *, const char *);
+static const char *handledash(char *, char *, const char *);
+static void expand_builtin(const char *[], int, int);
+static void expand_macro(const char *[], int);
+static void dump_one_def(const char *, struct macro_definition *);
+
+unsigned long expansion_id;
+
+/*
+ * eval - eval all macros and builtins calls
+ * argc - number of elements in argv.
+ * argv - element vector :
+ * argv[0] = definition of a user
+ * macro or NULL if built-in.
+ * argv[1] = name of the macro or
+ * built-in.
+ * argv[2] = parameters to user-defined
+ * . macro or built-in.
+ * .
+ *
+ * A call in the form of macro-or-builtin() will result in:
+ * argv[0] = nullstr
+ * argv[1] = macro-or-builtin
+ * argv[2] = nullstr
+ *
+ * argc is 3 for macro-or-builtin() and 2 for macro-or-builtin
+ */
+void
+eval(const char *argv[], int argc, int td, int is_traced)
+{
+ size_t mark = SIZE_MAX;
+
+ expansion_id++;
+ if (td & RECDEF)
+ m4errx(1, "expanding recursive definition for %s.", argv[1]);
+ if (is_traced)
+ mark = trace(argv, argc, infile+ilevel);
+ if (td == MACRTYPE)
+ expand_macro(argv, argc);
+ else
+ expand_builtin(argv, argc, td);
+ if (mark != SIZE_MAX)
+ finish_trace(mark);
+}
+
+/*
+ * expand_builtin - evaluate built-in macros.
+ */
+void
+expand_builtin(const char *argv[], int argc, int td)
+{
+ int c, n;
+ int ac;
+ static int sysval = 0;
+
+#ifdef DEBUG
+ printf("argc = %d\n", argc);
+ for (n = 0; n < argc; n++)
+ printf("argv[%d] = %s\n", n, argv[n]);
+ fflush(stdout);
+#endif
+
+ /*
+ * if argc == 3 and argv[2] is null, then we
+ * have macro-or-builtin() type call. We adjust
+ * argc to avoid further checking..
+ */
+ /* we keep the initial value for those built-ins that differentiate
+ * between builtin() and builtin.
+ */
+ ac = argc;
+
+ if (argc == 3 && !*(argv[2]) && !mimic_gnu)
+ argc--;
+
+ switch (td & TYPEMASK) {
+
+ case DEFITYPE:
+ if (argc > 2)
+ dodefine(argv[2], (argc > 3) ? argv[3] : null);
+ break;
+
+ case PUSDTYPE:
+ if (argc > 2)
+ dopushdef(argv[2], (argc > 3) ? argv[3] : null);
+ break;
+
+ case DUMPTYPE:
+ dodump(argv, argc);
+ break;
+
+ case TRACEONTYPE:
+ dotrace(argv, argc, 1);
+ break;
+
+ case TRACEOFFTYPE:
+ dotrace(argv, argc, 0);
+ break;
+
+ case EXPRTYPE:
+ /*
+ * doexpr - evaluate arithmetic
+ * expression
+ */
+ {
+ int base = 10;
+ int maxdigits = 0;
+ int e;
+
+ if (argc > 3) {
+ base = strtoi(argv[3], NULL, 0, 2, 36, &e);
+ if (e) {
+ m4errx(1, "expr: base %s invalid.", argv[3]);
+ }
+ }
+ if (argc > 4) {
+ maxdigits = strtoi(argv[4], NULL, 0, 0, INT_MAX, &e);
+ if (e) {
+ m4errx(1, "expr: maxdigits %s invalid.", argv[4]);
+ }
+ }
+ if (argc > 2)
+ pbnumbase(expr(argv[2]), base, maxdigits);
+ break;
+ }
+
+ case IFELTYPE:
+ if (argc > 4)
+ doifelse(argv, argc);
+ break;
+
+ case IFDFTYPE:
+ /*
+ * doifdef - select one of two
+ * alternatives based on the existence of
+ * another definition
+ */
+ if (argc > 3) {
+ if (lookup_macro_definition(argv[2]) != NULL)
+ pbstr(argv[3]);
+ else if (argc > 4)
+ pbstr(argv[4]);
+ }
+ break;
+
+ case LENGTYPE:
+ /*
+ * dolen - find the length of the
+ * argument
+ */
+ pbnum((argc > 2) ? strlen(argv[2]) : 0);
+ break;
+
+ case INCRTYPE:
+ /*
+ * doincr - increment the value of the
+ * argument
+ */
+ if (argc > 2)
+ pbnum(atoi(argv[2]) + 1);
+ break;
+
+ case DECRTYPE:
+ /*
+ * dodecr - decrement the value of the
+ * argument
+ */
+ if (argc > 2)
+ pbnum(atoi(argv[2]) - 1);
+ break;
+
+ case SYSCTYPE:
+ /*
+ * dosys - execute system command
+ */
+ if (argc > 2) {
+ fflush(stdout);
+ sysval = system(argv[2]);
+ }
+ break;
+
+ case SYSVTYPE:
+ /*
+ * dosysval - return value of the last
+ * system call.
+ *
+ */
+ pbnum(sysval);
+ break;
+
+ case ESYSCMDTYPE:
+ if (argc > 2)
+ doesyscmd(argv[2]);
+ break;
+ case INCLTYPE:
+ if (argc > 2)
+ if (!doincl(argv[2]))
+ err(1, "%s at line %lu: include(%s)",
+ CURRENT_NAME, CURRENT_LINE, argv[2]);
+ break;
+
+ case SINCTYPE:
+ if (argc > 2)
+ (void) doincl(argv[2]);
+ break;
+#ifdef EXTENDED
+ case PASTTYPE:
+ if (argc > 2)
+ if (!dopaste(argv[2]))
+ err(1, "%s at line %lu: paste(%s)",
+ CURRENT_NAME, CURRENT_LINE, argv[2]);
+ break;
+
+ case SPASTYPE:
+ if (argc > 2)
+ (void) dopaste(argv[2]);
+ break;
+ case FORMATTYPE:
+ doformat(argv, argc);
+ break;
+#endif
+ case CHNQTYPE:
+ dochq(argv, ac);
+ break;
+
+ case CHNCTYPE:
+ dochc(argv, argc);
+ break;
+
+ case SUBSTYPE:
+ /*
+ * dosub - select substring
+ *
+ */
+ if (argc > 3)
+ dosub(argv, argc);
+ break;
+
+ case SHIFTYPE:
+ /*
+ * doshift - push back all arguments
+ * except the first one (i.e. skip
+ * argv[2])
+ */
+ if (argc > 3) {
+ for (n = argc - 1; n > 3; n--) {
+ pbstr(rquote);
+ pbstr(argv[n]);
+ pbstr(lquote);
+ pushback(COMMA);
+ }
+ pbstr(rquote);
+ pbstr(argv[3]);
+ pbstr(lquote);
+ }
+ break;
+
+ case DIVRTYPE:
+ if (argc > 2 && (n = atoi(argv[2])) != 0)
+ dodiv(n);
+ else {
+ active = stdout;
+ oindex = 0;
+ }
+ break;
+
+ case UNDVTYPE:
+ doundiv(argv, argc);
+ break;
+
+ case DIVNTYPE:
+ /*
+ * dodivnum - return the number of
+ * current output diversion
+ */
+ pbnum(oindex);
+ break;
+
+ case UNDFTYPE:
+ /*
+ * doundefine - undefine a previously
+ * defined macro(s) or m4 keyword(s).
+ */
+ if (argc > 2)
+ for (n = 2; n < argc; n++)
+ macro_undefine(argv[n]);
+ break;
+
+ case POPDTYPE:
+ /*
+ * dopopdef - remove the topmost
+ * definitions of macro(s) or m4
+ * keyword(s).
+ */
+ if (argc > 2)
+ for (n = 2; n < argc; n++)
+ macro_popdef(argv[n]);
+ break;
+
+ case MKTMTYPE:
+ /*
+ * dotemp - create a temporary file
+ */
+ if (argc > 2) {
+ int fd;
+ char *temp;
+
+ temp = xstrdup(argv[2]);
+
+ fd = mkstemp(temp);
+ if (fd == -1)
+ err(1,
+ "%s at line %lu: couldn't make temp file %s",
+ CURRENT_NAME, CURRENT_LINE, argv[2]);
+ close(fd);
+ pbstr(temp);
+ free(temp);
+ }
+ break;
+
+ case TRNLTYPE:
+ /*
+ * dotranslit - replace all characters in
+ * the source string that appears in the
+ * "from" string with the corresponding
+ * characters in the "to" string.
+ */
+ if (argc > 3) {
+ char *temp;
+
+ temp = xalloc(strlen(argv[2])+1, NULL);
+ if (argc > 4)
+ map(temp, argv[2], argv[3], argv[4]);
+ else
+ map(temp, argv[2], argv[3], null);
+ pbstr(temp);
+ free(temp);
+ } else if (argc > 2)
+ pbstr(argv[2]);
+ break;
+
+ case INDXTYPE:
+ /*
+ * doindex - find the index of the second
+ * argument string in the first argument
+ * string. -1 if not present.
+ */
+ pbnum((argc > 3) ? indx(argv[2], argv[3]) : -1);
+ break;
+
+ case ERRPTYPE:
+ /*
+ * doerrp - print the arguments to stderr
+ * file
+ */
+ if (argc > 2) {
+ for (n = 2; n < argc; n++)
+ fprintf(stderr, "%s%s",
+ mimic_gnu && n == 2 ? "" : " ",
+ argv[n]);
+ if (!mimic_gnu)
+ fprintf(stderr, "\n");
+ }
+ break;
+
+ case DNLNTYPE:
+ /*
+ * dodnl - eat-up-to and including
+ * newline
+ */
+ while ((c = gpbc()) != '\n' && c != EOF)
+ ;
+ break;
+
+ case M4WRTYPE:
+ /*
+ * dom4wrap - set up for
+ * wrap-up/wind-down activity
+ */
+ if (argc > 2)
+ dom4wrap(argv[2]);
+ break;
+
+ case EXITTYPE:
+ /*
+ * doexit - immediate exit from m4.
+ */
+ killdiv();
+ exit((argc > 2) ? atoi(argv[2]) : 0);
+ break;
+
+ case DEFNTYPE:
+ if (argc > 2)
+ for (n = 2; n < argc; n++)
+ dodefn(argv[n]);
+ break;
+
+ case INDIRTYPE: /* Indirect call */
+ if (argc > 2)
+ doindir(argv, argc);
+ break;
+
+ case BUILTINTYPE: /* Builtins only */
+ if (argc > 2)
+ dobuiltin(argv, argc);
+ break;
+
+ case PATSTYPE:
+ if (argc > 2)
+ dopatsubst(argv, argc);
+ break;
+ case REGEXPTYPE:
+ if (argc > 2)
+ doregexp(argv, argc);
+ break;
+ case LINETYPE:
+ doprintlineno(infile+ilevel);
+ break;
+ case FILENAMETYPE:
+ doprintfilename(infile+ilevel);
+ break;
+ case SELFTYPE:
+ pbstr(rquote);
+ pbstr(argv[1]);
+ pbstr(lquote);
+ break;
+ default:
+ m4errx(1, "eval: major botch.");
+ break;
+ }
+}
+
+/*
+ * expand_macro - user-defined macro expansion
+ */
+void
+expand_macro(const char *argv[], int argc)
+{
+ const char *t;
+ const char *p;
+ int n;
+ int argno;
+
+ t = argv[0]; /* defn string as a whole */
+ p = t;
+ while (*p)
+ p++;
+ p--; /* last character of defn */
+ while (p > t) {
+ if (*(p - 1) != ARGFLAG)
+ PUSHBACK(*p);
+ else {
+ switch (*p) {
+
+ case '#':
+ pbnum(argc - 2);
+ break;
+ case '0':
+ case '1':
+ case '2':
+ case '3':
+ case '4':
+ case '5':
+ case '6':
+ case '7':
+ case '8':
+ case '9':
+ argno = *p - '0';
+ if (mimic_gnu) {
+ const unsigned char *q =
+ (const unsigned char *)p;
+ while (isdigit(*++q)) {
+ bp--;
+ argno = argno * 10 + *q - '0';
+ }
+ }
+ if (argno < argc - 1)
+ pbstr(argv[argno + 1]);
+ break;
+ case '*':
+ if (argc > 2) {
+ for (n = argc - 1; n > 2; n--) {
+ pbstr(argv[n]);
+ pushback(COMMA);
+ }
+ pbstr(argv[2]);
+ }
+ break;
+ case '@':
+ if (argc > 2) {
+ for (n = argc - 1; n > 2; n--) {
+ pbstr(rquote);
+ pbstr(argv[n]);
+ pbstr(lquote);
+ pushback(COMMA);
+ }
+ pbstr(rquote);
+ pbstr(argv[2]);
+ pbstr(lquote);
+ }
+ break;
+ default:
+ PUSHBACK(*p);
+ PUSHBACK('$');
+ break;
+ }
+ p--;
+ }
+ p--;
+ }
+ if (p == t) /* do last character */
+ PUSHBACK(*p);
+}
+
+
+/*
+ * dodefine - install definition in the table
+ */
+void
+dodefine(const char *name, const char *defn)
+{
+ if (!*name && !mimic_gnu)
+ m4errx(1, "null definition.");
+ else
+ macro_define(name, defn);
+}
+
+/*
+ * dodefn - push back a quoted definition of
+ * the given name.
+ */
+static void
+dodefn(const char *name)
+{
+ struct macro_definition *p;
+
+ if ((p = lookup_macro_definition(name)) != NULL) {
+ if ((p->type & TYPEMASK) == MACRTYPE) {
+ pbstr(rquote);
+ pbstr(p->defn);
+ pbstr(lquote);
+ } else {
+ pbstr(p->defn);
+ pbstr(BUILTIN_MARKER);
+ }
+ }
+}
+
+/*
+ * dopushdef - install a definition in the hash table
+ * without removing a previous definition. Since
+ * each new entry is entered in *front* of the
+ * hash bucket, it hides a previous definition from
+ * lookup.
+ */
+static void
+dopushdef(const char *name, const char *defn)
+{
+ if (!*name && !mimic_gnu)
+ m4errx(1, "null definition.");
+ else
+ macro_pushdef(name, defn);
+}
+
+/*
+ * dump_one_def - dump the specified definition.
+ */
+static void
+dump_one_def(const char *name, struct macro_definition *p)
+{
+ if (!traceout)
+ traceout = stderr;
+ if (mimic_gnu) {
+ if ((p->type & TYPEMASK) == MACRTYPE)
+ fprintf(traceout, "%s:\t%s\n", name, p->defn);
+ else {
+ fprintf(traceout, "%s:\t<%s>\n", name, p->defn);
+ }
+ } else
+ fprintf(traceout, "`%s'\t`%s'\n", name, p->defn);
+}
+
+/*
+ * dodumpdef - dump the specified definitions in the hash
+ * table to stderr. If nothing is specified, the entire
+ * hash table is dumped.
+ */
+static void
+dodump(const char *argv[], int argc)
+{
+ int n;
+ struct macro_definition *p;
+
+ if (argc > 2) {
+ for (n = 2; n < argc; n++)
+ if ((p = lookup_macro_definition(argv[n])) != NULL)
+ dump_one_def(argv[n], p);
+ } else
+ macro_for_all(dump_one_def);
+}
+
+/*
+ * dotrace - mark some macros as traced/untraced depending upon on.
+ */
+static void
+dotrace(const char *argv[], int argc, int on)
+{
+ int n;
+
+ if (argc > 2) {
+ for (n = 2; n < argc; n++)
+ mark_traced(argv[n], on);
+ } else
+ mark_traced(NULL, on);
+}
+
+/*
+ * doifelse - select one of two alternatives - loop.
+ */
+static void
+doifelse(const char *argv[], int argc)
+{
+ cycle {
+ if (argc < 5)
+ m4errx(1, "wrong number of args for ifelse");
+ if (STREQ(argv[2], argv[3]))
+ pbstr(argv[4]);
+ else if (argc == 6)
+ pbstr(argv[5]);
+ else if (argc > 6) {
+ argv += 3;
+ argc -= 3;
+ continue;
+ }
+ break;
+ }
+}
+
+/*
+ * doinclude - include a given file.
+ */
+static int
+doincl(const char *ifile)
+{
+#ifndef REAL_FREEZE
+ if (thawing)
+ return 1;
+#endif
+ if (ilevel + 1 == MAXINP)
+ m4errx(1, "too many include files.");
+ if (fopen_trypath(infile+ilevel+1, ifile) != NULL) {
+ ilevel++;
+ bbase[ilevel] = bufbase = bp;
+ return (1);
+ } else
+ return (0);
+}
+
+#ifdef EXTENDED
+/*
+ * dopaste - include a given file without any
+ * macro processing.
+ */
+static int
+dopaste(const char *pfile)
+{
+ FILE *pf;
+ int c;
+
+ if ((pf = fopen(pfile, "r")) != NULL) {
+ if (synch_lines)
+ fprintf(active, "#line 1 \"%s\"\n", pfile);
+ while ((c = getc(pf)) != EOF)
+ putc(c, active);
+ (void) fclose(pf);
+ emit_synchline();
+ return (1);
+ } else
+ return (0);
+}
+#endif
+
+/*
+ * dochq - change quote characters
+ */
+static void
+dochq(const char *argv[], int ac)
+{
+ if (ac == 2) {
+ lquote[0] = LQUOTE; lquote[1] = EOS;
+ rquote[0] = RQUOTE; rquote[1] = EOS;
+ } else {
+ strlcpy(lquote, argv[2], sizeof(lquote));
+ if (ac > 3) {
+ strlcpy(rquote, argv[3], sizeof(rquote));
+ } else {
+ rquote[0] = ECOMMT; rquote[1] = EOS;
+ }
+ }
+}
+
+/*
+ * dochc - change comment characters
+ */
+static void
+dochc(const char *argv[], int argc)
+{
+/* XXX Note that there is no difference between no argument and a single
+ * empty argument.
+ */
+ if (argc == 2) {
+ scommt[0] = EOS;
+ ecommt[0] = EOS;
+ } else {
+ strlcpy(scommt, argv[2], sizeof(scommt));
+ if (argc == 3) {
+ ecommt[0] = ECOMMT; ecommt[1] = EOS;
+ } else {
+ strlcpy(ecommt, argv[3], sizeof(ecommt));
+ }
+ }
+}
+
+/*
+ * dom4wrap - expand text at EOF
+ */
+static void
+dom4wrap(const char *text)
+{
+ if (wrapindex >= maxwraps) {
+ if (maxwraps == 0)
+ maxwraps = 16;
+ else
+ maxwraps *= 2;
+ m4wraps = xrealloc(m4wraps, maxwraps * sizeof(*m4wraps),
+ "too many m4wraps");
+ }
+ m4wraps[wrapindex++] = xstrdup(text);
+}
+
+/*
+ * dodivert - divert the output to a temporary file
+ */
+static void
+dodiv(int n)
+{
+ int fd;
+
+ oindex = n;
+ if (n >= maxout) {
+ if (mimic_gnu)
+ resizedivs(n + 10);
+ else
+ n = 0; /* bitbucket */
+ }
+
+ if (n < 0)
+ n = 0; /* bitbucket */
+ if (outfile[n] == NULL) {
+ char fname[] = _PATH_DIVNAME;
+
+ if ((fd = mkstemp(fname)) < 0 ||
+ (outfile[n] = fdopen(fd, "w+")) == NULL)
+ err(1, "%s: cannot divert", fname);
+ if (unlink(fname) == -1)
+ err(1, "%s: cannot unlink", fname);
+ }
+ active = outfile[n];
+}
+
+/*
+ * doundivert - undivert a specified output, or all
+ * other outputs, in numerical order.
+ */
+static void
+doundiv(const char *argv[], int argc)
+{
+ int ind;
+ int n;
+
+ if (argc > 2) {
+ for (ind = 2; ind < argc; ind++) {
+ int e;
+ n = strtoi(argv[ind], NULL, 0, 1, INT_MAX, &e);
+ if (e) {
+ if (errno == EINVAL && mimic_gnu)
+ getdivfile(argv[ind]);
+ } else {
+ if (n < maxout && outfile[n] != NULL)
+ getdiv(n);
+ }
+ }
+ }
+ else
+ for (n = 1; n < maxout; n++)
+ if (outfile[n] != NULL)
+ getdiv(n);
+}
+
+/*
+ * dosub - select substring
+ */
+static void
+dosub(const char *argv[], int argc)
+{
+ const char *ap, *fc, *k;
+ int nc;
+
+ ap = argv[2]; /* target string */
+#ifdef EXPR
+ fc = ap + expr(argv[3]); /* first char */
+#else
+ fc = ap + atoi(argv[3]); /* first char */
+#endif
+ nc = strlen(fc);
+ if (argc >= 5)
+#ifdef EXPR
+ nc = min(nc, expr(argv[4]));
+#else
+ nc = min(nc, atoi(argv[4]));
+#endif
+ if (fc >= ap && fc < ap + strlen(ap))
+ for (k = fc + nc - 1; k >= fc; k--)
+ pushback(*k);
+}
+
+/*
+ * map:
+ * map every character of s1 that is specified in from
+ * into s3 and replace in s. (source s1 remains untouched)
+ *
+ * This is a standard implementation of map(s,from,to) function of ICON
+ * language. Within mapvec, we replace every character of "from" with
+ * the corresponding character in "to". If "to" is shorter than "from",
+ * than the corresponding entries are null, which means that those
+ * characters dissapear altogether. Furthermore, imagine
+ * map(dest, "sourcestring", "srtin", "rn..*") type call. In this case,
+ * `s' maps to `r', `r' maps to `n' and `n' maps to `*'. Thus, `s'
+ * ultimately maps to `*'. In order to achieve this effect in an efficient
+ * manner (i.e. without multiple passes over the destination string), we
+ * loop over mapvec, starting with the initial source character. if the
+ * character value (dch) in this location is different than the source
+ * character (sch), sch becomes dch, once again to index into mapvec, until
+ * the character value stabilizes (i.e. sch = dch, in other words
+ * mapvec[n] == n). Even if the entry in the mapvec is null for an ordinary
+ * character, it will stabilize, since mapvec[0] == 0 at all times. At the
+ * end, we restore mapvec* back to normal where mapvec[n] == n for
+ * 0 <= n <= 127. This strategy, along with the restoration of mapvec, is
+ * about 5 times faster than any algorithm that makes multiple passes over
+ * destination string.
+ */
+static void
+map(char *dest, const char *src, const char *from, const char *to)
+{
+ const char *tmp;
+ unsigned char sch, dch;
+ unsigned char found[256];
+ static char frombis[257];
+ static char tobis[257];
+ static unsigned char mapvec[256] = {
+ 0, 1, 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, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52,
+ 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69,
+ 70, 71, 72, 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, 98, 99, 100, 101, 102,
+ 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115,
+ 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127, 128,
+ 129, 130, 131, 132, 133, 134, 135, 136, 137, 138, 139, 140, 141,
+ 142, 143, 144, 145, 146, 147, 148, 149, 150, 151, 152, 153, 154,
+ 155, 156, 157, 158, 159, 160, 161, 162, 163, 164, 165, 166, 167,
+ 168, 169, 170, 171, 172, 173, 174, 175, 176, 177, 178, 179, 180,
+ 181, 182, 183, 184, 185, 186, 187, 188, 189, 190, 191, 192, 193,
+ 194, 195, 196, 197, 198, 199, 200, 201, 202, 203, 204, 205, 206,
+ 207, 208, 209, 210, 211, 212, 213, 214, 215, 216, 217, 218, 219,
+ 220, 221, 222, 223, 224, 225, 226, 227, 228, 229, 230, 231, 232,
+ 233, 234, 235, 236, 237, 238, 239, 240, 241, 242, 243, 244, 245,
+ 246, 247, 248, 249, 250, 251, 252, 253, 254, 255
+ };
+
+ if (*src) {
+ if (mimic_gnu) {
+ /*
+ * expand character ranges on the fly
+ */
+ from = handledash(frombis, frombis + 256, from);
+ to = handledash(tobis, tobis + 256, to);
+ }
+ tmp = from;
+ /*
+ * create a mapping between "from" and
+ * "to"
+ */
+ memset(found, 0, sizeof(found));
+ for (; (sch = (unsigned char)*from) != '\0'; from++) {
+ if (!mimic_gnu || !found[sch]) {
+ found[sch] = 1;
+ mapvec[sch] = *to;
+ }
+ if (*to)
+ to++;
+ }
+
+ if (mimic_gnu) {
+ for (; (sch = (unsigned char)*src) != '\0'; src++) {
+ if (!found[sch])
+ *dest++ = sch;
+ else if ((dch = mapvec[sch]) != '\0')
+ *dest++ = dch;
+ }
+ } else {
+ while (*src) {
+ sch = (unsigned char)(*src++);
+ dch = mapvec[sch];
+ while (dch != sch) {
+ sch = dch;
+ dch = mapvec[sch];
+ }
+ if ((*dest = (char)dch))
+ dest++;
+ }
+ }
+ /*
+ * restore all the changed characters
+ */
+ while (*tmp) {
+ mapvec[(unsigned char)(*tmp)] = (unsigned char)(*tmp);
+ tmp++;
+ }
+ }
+ *dest = '\0';
+}
+
+
+/*
+ * handledash:
+ * use buffer to copy the src string, expanding character ranges
+ * on the way.
+ */
+static const char *
+handledash(char *buffer, char *end, const char *src)
+{
+ char *p;
+
+ p = buffer;
+ while(*src) {
+ if (src[1] == '-' && src[2]) {
+ unsigned char i;
+ if ((unsigned char)src[0] <= (unsigned char)src[2]) {
+ for (i = (unsigned char)src[0];
+ i <= (unsigned char)src[2]; i++) {
+ *p++ = i;
+ if (p == end) {
+ *p = '\0';
+ return buffer;
+ }
+ }
+ } else {
+ for (i = (unsigned char)src[0];
+ i >= (unsigned char)src[2]; i--) {
+ *p++ = i;
+ if (p == end) {
+ *p = '\0';
+ return buffer;
+ }
+ }
+ }
+ src += 3;
+ } else
+ *p++ = *src++;
+ if (p == end)
+ break;
+ }
+ *p = '\0';
+ return buffer;
+}
diff --git a/usr.bin/m4/expr.c b/usr.bin/m4/expr.c
new file mode 100644
index 0000000..a6c901f
--- /dev/null
+++ b/usr.bin/m4/expr.c
@@ -0,0 +1,50 @@
+/* $NetBSD: expr.c,v 1.19 2009/10/26 21:11:28 christos Exp $ */
+/* $OpenBSD: expr.c,v 1.17 2006/01/20 23:10:19 espie Exp $ */
+/*
+ * Copyright (c) 2004 Marc Espie <espie@cvs.openbsd.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+#if HAVE_NBTOOL_CONFIG_H
+#include "nbtool_config.h"
+#endif
+#include <sys/cdefs.h>
+__RCSID("$NetBSD: expr.c,v 1.19 2009/10/26 21:11:28 christos Exp $");
+#include <stdint.h>
+#include <stdio.h>
+#include <stddef.h>
+#include "mdef.h"
+#include "extern.h"
+
+int32_t end_result;
+const char *copy_toeval;
+
+extern void yy_scan_string(const char *);
+extern int yyparse(void);
+extern int yyerror(const char *);
+
+int
+yyerror(const char *msg)
+{
+ fprintf(stderr, "m4: %s in expr %s\n", msg, copy_toeval);
+ return(0);
+}
+
+int
+expr(const char *toeval)
+{
+ copy_toeval = toeval;
+ yy_scan_string(toeval);
+ yyparse();
+ return end_result;
+}
diff --git a/usr.bin/m4/extern.h b/usr.bin/m4/extern.h
new file mode 100644
index 0000000..1cb9de1
--- /dev/null
+++ b/usr.bin/m4/extern.h
@@ -0,0 +1,190 @@
+/* $OpenBSD: extern.h,v 1.49 2009/10/14 17:19:47 sthen Exp $ */
+/* $NetBSD: extern.h,v 1.19 2016/01/16 18:30:57 christos Exp $ */
+
+/*-
+ * Copyright (c) 1991, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Ozan Yigit at York University.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * @(#)extern.h 8.1 (Berkeley) 6/6/93
+ */
+
+/* eval.c */
+extern void eval(const char *[], int, int, int);
+extern void dodefine(const char *, const char *);
+extern unsigned long expansion_id;
+
+/* expr.c */
+extern int expr(const char *);
+
+/* gnum4.c */
+extern void addtoincludepath(const char *);
+extern struct input_file *fopen_trypath(struct input_file *, const char *);
+extern void doindir(const char *[], int);
+extern void dobuiltin(const char *[], int);
+extern void dopatsubst(const char *[], int);
+extern void doregexp(const char *[], int);
+
+extern void doprintlineno(struct input_file *);
+extern void doprintfilename(struct input_file *);
+
+extern void doesyscmd(const char *);
+extern void getdivfile(const char *);
+extern void doformat(const char *[], int);
+#ifdef REAL_FREEZE
+extern void freeze_state(const char *);
+extern void thaw_state(const char *);
+#endif
+
+
+/* look.c */
+
+#define FLAG_UNTRACED 0
+#define FLAG_TRACED 1
+#define FLAG_NO_TRACE 2
+
+extern void init_macros(void);
+extern ndptr lookup(const char *);
+extern void mark_traced(const char *, int);
+extern struct ohash macros;
+
+extern struct macro_definition *lookup_macro_definition(const char *);
+extern void macro_define(const char *, const char *);
+extern void macro_pushdef(const char *, const char *);
+extern void macro_popdef(const char *);
+extern void macro_undefine(const char *);
+extern void setup_builtin(const char *, unsigned int);
+extern void macro_for_all(void (*)(const char *, struct macro_definition *));
+#define macro_getdef(p) ((p)->d)
+#define macro_name(p) ((p)->name)
+#define macro_builtin_type(p) ((p)->builtin_type)
+#define is_traced(p) ((p)->trace_flags == FLAG_NO_TRACE ? (trace_flags & TRACE_ALL) : (p)->trace_flags)
+
+extern ndptr macro_getbuiltin(const char *);
+#ifdef REAL_FREEZE
+extern void dump_state(FILE *);
+extern void restore_state(FILE *);
+#endif
+
+/* main.c */
+extern void outputstr(const char *);
+extern void do_emit_synchline(void);
+#define emit_synchline() do { if (synch_lines) do_emit_synchline(); } while(0)
+
+/* misc.c */
+extern void chrsave(int);
+extern char *compute_prevep(void);
+extern void getdiv(int);
+extern ptrdiff_t indx(const char *, const char *);
+extern void initspaces(void);
+extern void killdiv(void);
+extern void pbnum(int);
+extern void pbnumbase(int, int, int);
+extern void pbunsigned(unsigned long);
+extern void pbstr(const char *);
+extern void pushback(int);
+extern void *xalloc(size_t, const char *fmt, ...) __printflike(2, 3);
+extern void *xrealloc(void *, size_t, const char *fmt, ...)
+ __printflike(3, 4);
+extern char *xstrdup(const char *);
+extern void resizedivs(int);
+extern size_t buffer_mark(void);
+extern void dump_buffer(FILE *, size_t);
+extern void __dead m4errx(int, const char *, ...) __printflike(2, 3);
+
+extern int obtain_char(struct input_file *);
+extern void set_input(struct input_file *, FILE *, const char *);
+extern void release_input(struct input_file *);
+
+/* speeded-up versions of chrsave/pushback */
+#define PUSHBACK(c) \
+ do { \
+ if (bp >= endpbb) \
+ enlarge_bufspace(); \
+ *bp++ = (c); \
+ } while(0)
+
+#define CHRSAVE(c) \
+ do { \
+ if (ep >= endest) \
+ enlarge_strspace(); \
+ *ep++ = (c); \
+ } while(0)
+
+/* and corresponding exposure for local symbols */
+extern void enlarge_bufspace(void);
+extern void enlarge_strspace(void);
+extern unsigned char *endpbb;
+extern char *endest;
+
+/* trace.c */
+extern unsigned int trace_flags;
+#define TRACE_ALL 512
+extern void trace_file(const char *);
+extern size_t trace(const char **, int, struct input_file *);
+extern void finish_trace(size_t);
+extern void set_trace_flags(const char *);
+extern FILE *traceout;
+
+extern ndptr hashtab[]; /* hash table for macros etc. */
+extern stae *mstack; /* stack of m4 machine */
+extern char *sstack; /* shadow stack, for string space extension */
+extern FILE *active; /* active output file pointer */
+extern struct input_file infile[];/* input file stack (0=stdin) */
+extern FILE **outfile; /* diversion array(0=bitbucket) */
+extern int maxout; /* maximum number of diversions */
+extern int fp; /* m4 call frame pointer */
+extern int ilevel; /* input file stack pointer */
+extern int oindex; /* diversion index. */
+extern int sp; /* current m4 stack pointer */
+extern unsigned char *bp; /* first available character */
+extern unsigned char *buf; /* push-back buffer */
+extern unsigned char *bufbase; /* buffer base for this ilevel */
+extern unsigned char *bbase[]; /* buffer base per ilevel */
+extern char ecommt[MAXCCHARS+1];/* end character for comment */
+extern char *ep; /* first free char in strspace */
+extern char lquote[MAXCCHARS+1];/* left quote character (`) */
+extern char **m4wraps; /* m4wrap string default. */
+extern int maxwraps; /* size of m4wraps array */
+extern int wrapindex; /* current index in m4wraps */
+extern int fatal_warnings; /* exit on warning */
+extern int quiet; /* no warnings */
+extern int nesting_limit; /* macro expansion nesting limit */
+#ifndef REAL_FREEZE
+extern FILE *freezef; /* copy of input */
+extern int thawing; /* don't process includes during thaw */
+#endif
+
+extern const char *null; /* as it says.. just a null. */
+extern char rquote[MAXCCHARS+1];/* right quote character (') */
+extern char scommt[MAXCCHARS+1];/* start character for comment */
+extern int synch_lines; /* line synchronisation directives */
+
+extern int mimic_gnu; /* behaves like gnu-m4 */
+extern int prefix_builtins; /* prefix builtin macros with m4_ */
diff --git a/usr.bin/m4/gnum4.c b/usr.bin/m4/gnum4.c
new file mode 100644
index 0000000..a450a99
--- /dev/null
+++ b/usr.bin/m4/gnum4.c
@@ -0,0 +1,817 @@
+/* $NetBSD: gnum4.c,v 1.10 2016/01/16 16:59:18 christos Exp $ */
+/* $OpenBSD: gnum4.c,v 1.39 2008/08/21 21:01:04 espie Exp $ */
+
+/*
+ * Copyright (c) 1999 Marc Espie
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+/*
+ * functions needed to support gnu-m4 extensions, including a fake freezing
+ */
+#if HAVE_NBTOOL_CONFIG_H
+#include "nbtool_config.h"
+#endif
+#include <sys/cdefs.h>
+__RCSID("$NetBSD: gnum4.c,v 1.10 2016/01/16 16:59:18 christos Exp $");
+
+#include <sys/param.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <ctype.h>
+#include <err.h>
+#include <paths.h>
+#include <regex.h>
+#include <stddef.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <errno.h>
+#include <unistd.h>
+#include "mdef.h"
+#include "stdd.h"
+#include "extern.h"
+
+
+int mimic_gnu = 0;
+#ifndef SIZE_T_MAX
+#define SIZE_T_MAX (size_t)~0ull
+#endif
+
+/*
+ * Support for include path search
+ * First search in the current directory.
+ * If not found, and the path is not absolute, include path kicks in.
+ * First, -I options, in the order found on the command line.
+ * Then M4PATH env variable
+ */
+
+struct path_entry {
+ char *name;
+ struct path_entry *next;
+} *first, *last;
+
+static struct path_entry *new_path_entry(const char *);
+static void ensure_m4path(void);
+static struct input_file *dopath(struct input_file *, const char *);
+
+static struct path_entry *
+new_path_entry(const char *dirname)
+{
+ struct path_entry *n;
+
+ n = malloc(sizeof(struct path_entry));
+ if (!n)
+ errx(1, "out of memory");
+ n->name = strdup(dirname);
+ if (!n->name)
+ errx(1, "out of memory");
+ n->next = 0;
+ return n;
+}
+
+void
+addtoincludepath(const char *dirname)
+{
+ struct path_entry *n;
+
+ n = new_path_entry(dirname);
+
+ if (last) {
+ last->next = n;
+ last = n;
+ }
+ else
+ last = first = n;
+}
+
+static void
+ensure_m4path(void)
+{
+ static int envpathdone = 0;
+ char *envpath;
+ char *sweep;
+ char *path;
+
+ if (envpathdone)
+ return;
+ envpathdone = TRUE;
+ envpath = getenv("M4PATH");
+ if (!envpath)
+ return;
+ /* for portability: getenv result is read-only */
+ envpath = strdup(envpath);
+ if (!envpath)
+ errx(1, "out of memory");
+ for (sweep = envpath;
+ (path = strsep(&sweep, ":")) != NULL;)
+ addtoincludepath(path);
+ free(envpath);
+}
+
+static
+struct input_file *
+dopath(struct input_file *i, const char *filename)
+{
+ char path[MAXPATHLEN];
+ struct path_entry *pe;
+ FILE *f;
+
+ for (pe = first; pe; pe = pe->next) {
+ snprintf(path, sizeof(path), "%s/%s", pe->name, filename);
+ if ((f = fopen(path, "r")) != 0) {
+ set_input(i, f, path);
+ return i;
+ }
+ }
+ return NULL;
+}
+
+struct input_file *
+fopen_trypath(struct input_file *i, const char *filename)
+{
+ FILE *f;
+
+ f = fopen(filename, "r");
+ if (f != NULL) {
+ set_input(i, f, filename);
+ return i;
+ }
+ if (filename[0] == '/')
+ return NULL;
+
+ ensure_m4path();
+
+ return dopath(i, filename);
+}
+
+void
+doindir(const char *argv[], int argc)
+{
+ ndptr n;
+ struct macro_definition *p;
+
+ n = lookup(argv[2]);
+ if (n == NULL || (p = macro_getdef(n)) == NULL)
+ m4errx(1, "indir: undefined macro %s.", argv[2]);
+ argv[1] = p->defn;
+
+ eval(argv+1, argc-1, p->type, is_traced(n));
+}
+
+void
+dobuiltin(const char *argv[], int argc)
+{
+ ndptr p;
+
+ argv[1] = NULL;
+ p = macro_getbuiltin(argv[2]);
+ if (p != NULL)
+ eval(argv+1, argc-1, macro_builtin_type(p), is_traced(p));
+ else
+ m4errx(1, "unknown builtin %s.", argv[2]);
+}
+
+
+/* We need some temporary buffer space, as pb pushes BACK and substitution
+ * proceeds forward... */
+static char *buffer;
+static size_t bufsize = 0;
+static size_t current = 0;
+
+static void addchars(const char *, size_t);
+static void addchar(int);
+static char *twiddle(const char *);
+static char *getstring(void);
+static void exit_regerror(int, const char *, regex_t *) __dead;
+static void do_subst(const char *, const char *, regex_t *, const char *,
+ regmatch_t *);
+static void do_regexpindex(const char *, const char *, regex_t *, regmatch_t *);
+static void do_regexp(const char *, const char *, regex_t *, const char *, regmatch_t *);
+static void add_sub(size_t, const char *, regex_t *, regmatch_t *);
+static void add_replace(const char *, regex_t *, const char *, regmatch_t *);
+#define addconstantstring(s) addchars((s), sizeof(s)-1)
+
+static void
+addchars(const char *c, size_t n)
+{
+ if (n == 0)
+ return;
+ while (current + n > bufsize) {
+ if (bufsize == 0)
+ bufsize = 1024;
+ else
+ bufsize *= 2;
+ buffer = xrealloc(buffer, bufsize, NULL);
+ }
+ memcpy(buffer+current, c, n);
+ current += n;
+}
+
+static void
+addchar(int c)
+{
+ if (current +1 > bufsize) {
+ if (bufsize == 0)
+ bufsize = 1024;
+ else
+ bufsize *= 2;
+ buffer = xrealloc(buffer, bufsize, NULL);
+ }
+ buffer[current++] = c;
+}
+
+static char *
+getstring(void)
+{
+ addchar('\0');
+ current = 0;
+ return buffer;
+}
+
+
+static void
+exit_regerror(int er, const char *pat, regex_t *re)
+{
+ size_t errlen;
+ char *errbuf;
+
+ errlen = regerror(er, re, NULL, 0);
+ errbuf = xalloc(errlen,
+ "malloc in regerror: %lu", (unsigned long)errlen);
+ regerror(er, re, errbuf, errlen);
+ m4errx(1, "regular expression error: %s for: `%s'", errbuf, pat);
+}
+
+static void
+add_sub(size_t n, const char *string, regex_t *re, regmatch_t *pm)
+{
+ if (n > re->re_nsub) {
+ if (!quiet)
+ warnx("No subexpression %zu", n);
+ if (fatal_warnings)
+ exit(EXIT_FAILURE);
+ }
+ /* Subexpressions that did not match are
+ * not an error. */
+ else if (pm[n].rm_so != -1 &&
+ pm[n].rm_eo != -1) {
+ addchars(string + pm[n].rm_so,
+ pm[n].rm_eo - pm[n].rm_so);
+ }
+}
+
+/* Add replacement string to the output buffer, recognizing special
+ * constructs and replacing them with substrings of the original string.
+ */
+static void
+add_replace(const char *string, regex_t *re, const char *replace, regmatch_t *pm)
+{
+ const char *p;
+
+ for (p = replace; *p != '\0'; p++) {
+ if (*p == '&' && !mimic_gnu) {
+ add_sub(0, string, re, pm);
+ continue;
+ }
+ if (*p == '\\') {
+ if (p[1] == '\\') {
+ addchar(p[1]);
+ p++;
+ continue;
+ }
+ if (p[1] == '&') {
+ if (mimic_gnu)
+ add_sub(0, string, re, pm);
+ else
+ addchar(p[1]);
+ p++;
+ continue;
+ }
+ if (isdigit((unsigned char)p[1])) {
+ add_sub(*(++p) - '0', string, re, pm);
+ continue;
+ }
+ }
+ addchar(*p);
+ }
+}
+
+static void
+do_subst(const char *pat, const char *string, regex_t *re, const char *replace,
+ regmatch_t *pm)
+{
+ int error;
+ int flags = 0;
+ const char *last_match = NULL;
+
+ while ((error = regexec(re, string, re->re_nsub+1, pm, flags)) == 0) {
+ if (pm[0].rm_eo != 0) {
+ if (string[pm[0].rm_eo-1] == '\n')
+ flags = 0;
+ else
+ flags = REG_NOTBOL;
+ }
+
+ /* NULL length matches are special... We use the `vi-mode'
+ * rule: don't allow a NULL-match at the last match
+ * position.
+ */
+ if (pm[0].rm_so == pm[0].rm_eo &&
+ string + pm[0].rm_so == last_match) {
+ if (*string == '\0')
+ return;
+ addchar(*string);
+ if (*string++ == '\n')
+ flags = 0;
+ else
+ flags = REG_NOTBOL;
+ continue;
+ }
+ last_match = string + pm[0].rm_so;
+ addchars(string, pm[0].rm_so);
+ add_replace(string, re, replace, pm);
+ string += pm[0].rm_eo;
+ buffer[current] = '\0';
+ }
+ while (*string)
+ addchar(*string++);
+ if (error != REG_NOMATCH)
+ exit_regerror(error, pat, re);
+ pbstr(string);
+}
+
+static void
+do_regexp(const char *pat, const char *string, regex_t *re, const char *replace,
+ regmatch_t *pm)
+{
+ int error;
+
+ switch(error = regexec(re, string, re->re_nsub+1, pm, 0)) {
+ case 0:
+ add_replace(string, re, replace, pm);
+ pbstr(getstring());
+ break;
+ case REG_NOMATCH:
+ break;
+ default:
+ exit_regerror(error, pat, re);
+ }
+}
+
+static void
+do_regexpindex(const char *pat, const char *string, regex_t *re, regmatch_t *pm)
+{
+ int error;
+
+ switch(error = regexec(re, string, re->re_nsub+1, pm, 0)) {
+ case 0:
+ pbunsigned(pm[0].rm_so);
+ break;
+ case REG_NOMATCH:
+ pbnum(-1);
+ break;
+ default:
+ exit_regerror(error, pat, re);
+ }
+}
+
+/* In Gnu m4 mode, parentheses for backmatch don't work like POSIX 1003.2
+ * says. So we twiddle with the regexp before passing it to regcomp.
+ */
+static char *
+twiddle(const char *p)
+{
+ /* + at start of regexp is a normal character for Gnu m4 */
+ if (*p == '^') {
+ addchar(*p);
+ p++;
+ }
+ if (*p == '+') {
+ addchar('\\');
+ }
+ /* This could use strcspn for speed... */
+ while (*p != '\0') {
+ if (*p == '\\') {
+ switch(p[1]) {
+ case '(':
+ case ')':
+ case '|':
+ addchar(p[1]);
+ break;
+ case 'w':
+ addconstantstring("[_a-zA-Z0-9]");
+ break;
+ case 'W':
+ addconstantstring("[^_a-zA-Z0-9]");
+ break;
+ case '<':
+ addconstantstring("[[:<:]]");
+ break;
+ case '>':
+ addconstantstring("[[:>:]]");
+ break;
+ default:
+ addchars(p, 2);
+ break;
+ }
+ p+=2;
+ continue;
+ }
+ if (*p == '(' || *p == ')' || *p == '|')
+ addchar('\\');
+
+ addchar(*p);
+ p++;
+ }
+ return getstring();
+}
+
+static int
+checkempty(const char *argv[], int argc)
+{
+ const char *s;
+ size_t len;
+
+ if (argc != 3 && argv[3][0] != '\0')
+ return 0;
+
+ if (argc == 3) {
+ if (!quiet)
+ warnx("Too few arguments to patsubst");
+ if (fatal_warnings)
+ exit(EXIT_FAILURE);
+ }
+
+ if (argv[4] && argc > 4)
+ len = strlen(argv[4]);
+ else
+ len = 0;
+ for (s = argv[2]; *s != '\0'; s++) {
+ addchars(argv[4], len);
+ addchar(*s);
+ }
+ return 1;
+}
+
+/* patsubst(string, regexp, opt replacement) */
+/* argv[2]: string
+ * argv[3]: regexp
+ * argv[4]: opt rep
+ */
+void
+dopatsubst(const char *argv[], int argc)
+{
+ if (argc < 3) {
+ if (!quiet)
+ warnx("Too few arguments to patsubst");
+ if (fatal_warnings)
+ exit(EXIT_FAILURE);
+ return;
+ }
+ /* special case: empty regexp */
+ if (!checkempty(argv, argc)) {
+
+ const char *pat;
+ int error;
+ regex_t re;
+ regmatch_t *pmatch;
+ int mode = REG_EXTENDED;
+ size_t l = strlen(argv[3]);
+
+ if (!mimic_gnu ||
+ (argv[3][0] == '^') ||
+ (l > 0 && argv[3][l-1] == '$'))
+ mode |= REG_NEWLINE;
+
+ pat = mimic_gnu ? twiddle(argv[3]) : argv[3];
+ error = regcomp(&re, pat, mode);
+ if (error != 0)
+ exit_regerror(error, pat, &re);
+
+ pmatch = xalloc(sizeof(regmatch_t) * (re.re_nsub+1), NULL);
+ do_subst(pat, argv[2], &re,
+ argc > 4 && argv[4] != NULL ? argv[4] : "", pmatch);
+ free(pmatch);
+ regfree(&re);
+ }
+ pbstr(getstring());
+}
+
+void
+doregexp(const char *argv[], int argc)
+{
+ int error;
+ regex_t re;
+ regmatch_t *pmatch;
+ const char *pat;
+
+ if (argc < 3) {
+ if (!quiet)
+ warnx("Too few arguments to regexp");
+ if (fatal_warnings)
+ exit(EXIT_FAILURE);
+ return;
+ }
+ if (checkempty(argv, argc)) {
+ return;
+ }
+
+ pat = mimic_gnu ? twiddle(argv[3]) : argv[3];
+ error = regcomp(&re, pat, REG_EXTENDED);
+ if (error != 0)
+ exit_regerror(error, pat, &re);
+
+ pmatch = xalloc(sizeof(regmatch_t) * (re.re_nsub+1), NULL);
+ if (argv[4] == NULL || argc == 4)
+ do_regexpindex(pat, argv[2], &re, pmatch);
+ else
+ do_regexp(pat, argv[2], &re, argv[4], pmatch);
+ free(pmatch);
+ regfree(&re);
+}
+
+void
+doformat(const char *argv[], int argc)
+{
+ const char *format = argv[2];
+ int pos = 3;
+ int left_padded;
+ long width;
+ size_t l;
+ const char *thisarg;
+ char temp[2];
+ size_t extra;
+
+ while (*format != 0) {
+ if (*format != '%') {
+ addchar(*format++);
+ continue;
+ }
+
+ format++;
+ if (*format == '%') {
+ addchar(*format++);
+ continue;
+ }
+ if (*format == 0) {
+ addchar('%');
+ break;
+ }
+
+ if (*format == '*') {
+ format++;
+ if (pos >= argc)
+ m4errx(1,
+ "Format with too many format specifiers.");
+ width = strtol(argv[pos++], NULL, 10);
+ } else {
+ char *eformat;
+ width = strtol(format, &eformat, 10);
+ format = eformat;
+ }
+ if (width < 0) {
+ left_padded = 1;
+ width = -width;
+ } else {
+ left_padded = 0;
+ }
+ if (*format == '.') {
+ format++;
+ if (*format == '*') {
+ format++;
+ if (pos >= argc)
+ m4errx(1,
+ "Format with too many format specifiers.");
+ extra = strtol(argv[pos++], NULL, 10);
+ } else {
+ char *eformat;
+ extra = strtol(format, &eformat, 10);
+ format = eformat;
+ }
+ } else {
+ extra = SIZE_T_MAX;
+ }
+ if (pos >= argc)
+ m4errx(1, "Format with too many format specifiers.");
+ switch(*format) {
+ case 's':
+ thisarg = argv[pos++];
+ break;
+ case 'c':
+ temp[0] = strtoul(argv[pos++], NULL, 10);
+ temp[1] = 0;
+ thisarg = temp;
+ break;
+ default:
+ m4errx(1, "Unsupported format specification: %s.",
+ argv[2]);
+ }
+ format++;
+ l = strlen(thisarg);
+ if (l > extra)
+ l = extra;
+ if (!left_padded) {
+ while (l < (size_t)width--)
+ addchar(' ');
+ }
+ addchars(thisarg, l);
+ if (left_padded) {
+ while (l < (size_t)width--)
+ addchar(' ');
+ }
+ }
+ pbstr(getstring());
+}
+
+void
+doesyscmd(const char *cmd)
+{
+ int p[2];
+ pid_t pid, cpid;
+ const char *argv[4];
+ int cc;
+ int status;
+
+ /* Follow gnu m4 documentation: first flush buffers. */
+ fflush(NULL);
+
+ argv[0] = "sh";
+ argv[1] = "-c";
+ argv[2] = cmd;
+ argv[3] = NULL;
+
+ /* Just set up standard output, share stderr and stdin with m4 */
+ if (pipe(p) == -1)
+ err(1, "bad pipe");
+ switch(cpid = fork()) {
+ case -1:
+ err(1, "bad fork");
+ /* NOTREACHED */
+ case 0:
+ (void) close(p[0]);
+ (void) dup2(p[1], 1);
+ (void) close(p[1]);
+ execv(_PATH_BSHELL, __UNCONST(argv));
+ exit(1);
+ default:
+ /* Read result in two stages, since m4's buffer is
+ * pushback-only. */
+ (void) close(p[1]);
+ do {
+ char result[BUFSIZE];
+ cc = read(p[0], result, sizeof result);
+ if (cc > 0)
+ addchars(result, cc);
+ } while (cc > 0 || (cc == -1 && errno == EINTR));
+
+ (void) close(p[0]);
+ while ((pid = wait(&status)) != cpid && pid >= 0)
+ continue;
+ pbstr(getstring());
+ }
+}
+
+void
+getdivfile(const char *name)
+{
+ FILE *f;
+ int c;
+
+ f = fopen(name, "r");
+ if (!f)
+ return;
+
+ while ((c = getc(f))!= EOF)
+ putc(c, active);
+ (void) fclose(f);
+}
+
+#ifdef REAL_FREEZE
+void
+freeze_state(const char *fname)
+{
+ FILE *f;
+
+ if ((f = fopen(fname, "wb")) == NULL)
+ m4errx(EXIT_FAILURE, "Can't open output freeze file `%s' (%s)",
+ fname, strerror(errno));
+ fprintf(f, "# This is a frozen state file generated by %s\nV1\n",
+ getprogname());
+ fprintf(f, "Q%zu,%zu\n%s%s\n", strlen(lquote), strlen(rquote),
+ lquote, rquote);
+ fprintf(f, "C%zu,%zu\n%s%s\n", strlen(scommt), strlen(ecommt),
+ scommt, ecommt);
+ dump_state(f);
+ /* XXX: diversions? */
+ fprintf(f, "D-1,0\n");
+ fprintf(f, "# End of frozen state file\n");
+ fclose(f);
+}
+
+void
+thaw_state(const char *fname)
+{
+ char *name = NULL;
+ size_t nl, namelen = 0;
+ char *defn = NULL;
+ size_t dl, defnlen = 0;
+ size_t lineno = 0;
+ char line[1024], *ptr, type;
+ FILE *f;
+
+ if ((f = fopen(fname, "rb")) == NULL)
+ m4errx(EXIT_FAILURE, "Can't open frozen file `%s' (%s)",
+ fname, strerror(errno));
+
+#define GET() if (fgets(line, (int)sizeof(line), f) == NULL) goto out
+#define GETSTR(s, l) if (fread(s, 1, l, f) != l) goto out; else s[l] = '\0'
+
+ GET(); /* comment */
+ GET(); /* version */
+ if ((ptr = strrchr(line, '\n')) != NULL)
+ *ptr = '\0';
+ if (strcmp(line, "V1") != 0)
+ m4errx(EXIT_FAILURE, "Bad frozen version `%s'", line);
+
+ for (;;) {
+ GET();
+ lineno++;
+ switch (*line) {
+ case '\n':
+ continue;
+ case '#':
+ free(name);
+ free(defn);
+ fclose(f);
+ return;
+ default:
+ if (sscanf(line, "%c%zu,%zu\n", &type, &nl, &dl) != 3)
+ m4errx(EXIT_FAILURE, "%s, %zu: Bad line `%s'",
+ fname, lineno, line);
+ break;
+ }
+
+ switch (type) {
+ case 'Q':
+ if (nl >= sizeof(lquote) || dl >= sizeof(rquote))
+ m4errx(EXIT_FAILURE, "%s, %zu: Quote too long",
+ fname, lineno);
+ GETSTR(lquote, nl);
+ GETSTR(rquote, dl);
+ break;
+
+ case 'C':
+ if (nl >= sizeof(scommt) || dl >= sizeof(ecommt))
+ m4errx(EXIT_FAILURE, "%s, %zu: Comment too long",
+ fname, lineno);
+ GETSTR(scommt, nl);
+ GETSTR(ecommt, dl);
+ break;
+
+ case 'T':
+ case 'F':
+ if (nl >= namelen)
+ name = xrealloc(name, namelen = nl + 1,
+ "name grow");
+ if (dl >= defnlen)
+ defn = xrealloc(defn, defnlen = dl + 1,
+ "defn grow");
+ GETSTR(name, nl);
+ GETSTR(defn, dl);
+ macro_pushdef(name, defn);
+ break;
+
+ case 'D':
+ /* XXX: Not implemented */
+ break;
+
+ default:
+ m4errx(EXIT_FAILURE, "%s, %zu: Unknown type %c",
+ fname, lineno,type);
+ }
+ }
+out:
+ m4errx(EXIT_FAILURE, "Unexprected end of file in `%s'", fname);
+}
+#endif
diff --git a/usr.bin/m4/lib/ohash.h b/usr.bin/m4/lib/ohash.h
new file mode 100644
index 0000000..8ccdaf9
--- /dev/null
+++ b/usr.bin/m4/lib/ohash.h
@@ -0,0 +1,73 @@
+#ifndef OHASH_H
+#define OHASH_H
+/* $OpenBSD: ohash.h,v 1.8 2005/12/29 18:54:47 jaredy Exp $ */
+/* ex:ts=8 sw=4:
+ */
+
+/* Copyright (c) 1999, 2004 Marc Espie <espie@openbsd.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+/* Open hashing support.
+ * Open hashing was chosen because it is much lighter than other hash
+ * techniques, and more efficient in most cases.
+ */
+
+struct ohash_info {
+ ptrdiff_t key_offset;
+ void *data; /* user data */
+ void *(*halloc)(size_t, void *);
+ void (*hfree)(void *, size_t, void *);
+ void *(*alloc)(size_t, void *);
+};
+
+struct _ohash_record;
+
+struct ohash {
+ struct _ohash_record *t;
+ struct ohash_info info;
+ unsigned int size;
+ unsigned int total;
+ unsigned int deleted;
+};
+
+/* For this to be tweakable, we use small primitives, and leave part of the
+ * logic to the client application. e.g., hashing is left to the client
+ * application. We also provide a simple table entry lookup that yields
+ * a hashing table index (opaque) to be used in find/insert/remove.
+ * The keys are stored at a known position in the client data.
+ */
+__BEGIN_DECLS
+void ohash_init(struct ohash *, unsigned, struct ohash_info *);
+void ohash_delete(struct ohash *);
+
+unsigned int ohash_lookup_interval(struct ohash *, const char *,
+ const char *, u_int32_t);
+unsigned int ohash_lookup_memory(struct ohash *, const char *,
+ size_t, u_int32_t);
+void *ohash_find(struct ohash *, unsigned int);
+void *ohash_remove(struct ohash *, unsigned int);
+void *ohash_insert(struct ohash *, unsigned int, void *);
+void *ohash_first(struct ohash *, unsigned int *);
+void *ohash_next(struct ohash *, unsigned int *);
+unsigned int ohash_entries(struct ohash *);
+
+void *ohash_create_entry(struct ohash_info *, const char *, const char **);
+u_int32_t ohash_interval(const char *, const char **);
+
+unsigned int ohash_qlookupi(struct ohash *, const char *, const char **);
+unsigned int ohash_qlookup(struct ohash *, const char *);
+__END_DECLS
+#endif
+
diff --git a/usr.bin/m4/lib/ohash_create_entry.c b/usr.bin/m4/lib/ohash_create_entry.c
new file mode 100644
index 0000000..3bdc9ab
--- /dev/null
+++ b/usr.bin/m4/lib/ohash_create_entry.c
@@ -0,0 +1,38 @@
+/* $OpenBSD: ohash_create_entry.c,v 1.2 2004/06/22 20:00:16 espie Exp $ */
+/* ex:ts=8 sw=4:
+ */
+
+/* Copyright (c) 1999, 2004 Marc Espie <espie@openbsd.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include "ohash_int.h"
+
+/* This handles the common case of variable length keys, where the
+ * key is stored at the end of the record.
+ */
+void *
+ohash_create_entry(struct ohash_info *i, const char *start, const char **end)
+{
+ char *p;
+
+ if (!*end)
+ *end = start + strlen(start);
+ p = (i->alloc)(i->key_offset + (*end - start) + 1, i->data);
+ if (p) {
+ memcpy(p+i->key_offset, start, *end-start);
+ p[i->key_offset + (*end - start)] = '\0';
+ }
+ return (void *)p;
+}
diff --git a/usr.bin/m4/lib/ohash_delete.c b/usr.bin/m4/lib/ohash_delete.c
new file mode 100644
index 0000000..12a9c0c
--- /dev/null
+++ b/usr.bin/m4/lib/ohash_delete.c
@@ -0,0 +1,31 @@
+/* $OpenBSD: ohash_delete.c,v 1.2 2004/06/22 20:00:16 espie Exp $ */
+/* ex:ts=8 sw=4:
+ */
+
+/* Copyright (c) 1999, 2004 Marc Espie <espie@openbsd.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include "ohash_int.h"
+/* hash_delete only frees the hash structure. Use hash_first/hash_next
+ * to free entries as well. */
+void
+ohash_delete(struct ohash *h)
+{
+ (h->info.hfree)(h->t, sizeof(struct _ohash_record) * h->size,
+ h->info.data);
+#ifndef NDEBUG
+ h->t = NULL;
+#endif
+}
diff --git a/usr.bin/m4/lib/ohash_do.c b/usr.bin/m4/lib/ohash_do.c
new file mode 100644
index 0000000..2b8467d
--- /dev/null
+++ b/usr.bin/m4/lib/ohash_do.c
@@ -0,0 +1,111 @@
+/* $OpenBSD: ohash_do.c,v 1.4 2004/06/22 20:00:16 espie Exp $ */
+/* ex:ts=8 sw=4:
+ */
+
+/* Copyright (c) 1999, 2004 Marc Espie <espie@openbsd.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include "ohash_int.h"
+
+static void ohash_resize(struct ohash *);
+
+static void
+ohash_resize(struct ohash *h)
+{
+ struct _ohash_record *n;
+ unsigned int ns, j;
+ unsigned int i, incr;
+
+ if (4 * h->deleted < h->total)
+ ns = h->size << 1;
+ else if (3 * h->deleted > 2 * h->total)
+ ns = h->size >> 1;
+ else
+ ns = h->size;
+ if (ns < MINSIZE)
+ ns = MINSIZE;
+#ifdef STATS_HASH
+ STAT_HASH_EXPAND++;
+ STAT_HASH_SIZE += ns - h->size;
+#endif
+ n = (h->info.halloc)(sizeof(struct _ohash_record) * ns, h->info.data);
+ if (!n)
+ return;
+
+ for (j = 0; j < h->size; j++) {
+ if (h->t[j].p != NULL && h->t[j].p != DELETED) {
+ i = h->t[j].hv % ns;
+ incr = ((h->t[j].hv % (ns - 2)) & ~1) + 1;
+ while (n[i].p != NULL) {
+ i += incr;
+ if (i >= ns)
+ i -= ns;
+ }
+ n[i].hv = h->t[j].hv;
+ n[i].p = h->t[j].p;
+ }
+ }
+ (h->info.hfree)(h->t, sizeof(struct _ohash_record) * h->size,
+ h->info.data);
+ h->t = n;
+ h->size = ns;
+ h->total -= h->deleted;
+ h->deleted = 0;
+}
+
+void *
+ohash_remove(struct ohash *h, unsigned int i)
+{
+ void *result = __UNCONST(h->t[i].p);
+
+ if (result == NULL || result == DELETED)
+ return NULL;
+
+#ifdef STATS_HASH
+ STAT_HASH_ENTRIES--;
+#endif
+ h->t[i].p = DELETED;
+ h->deleted++;
+ if (h->deleted >= MINDELETED && 4 * h->deleted > h->total)
+ ohash_resize(h);
+ return result;
+}
+
+void *
+ohash_find(struct ohash *h, unsigned int i)
+{
+ if (h->t[i].p == DELETED)
+ return NULL;
+ else
+ return __UNCONST(h->t[i].p);
+}
+
+void *
+ohash_insert(struct ohash *h, unsigned int i, void *p)
+{
+#ifdef STATS_HASH
+ STAT_HASH_ENTRIES++;
+#endif
+ if (h->t[i].p == DELETED) {
+ h->deleted--;
+ h->t[i].p = p;
+ } else {
+ h->t[i].p = p;
+ /* Arbitrary resize boundary. Tweak if not efficient enough. */
+ if (++h->total * 4 > h->size * 3)
+ ohash_resize(h);
+ }
+ return p;
+}
diff --git a/usr.bin/m4/lib/ohash_entries.c b/usr.bin/m4/lib/ohash_entries.c
new file mode 100644
index 0000000..683e204
--- /dev/null
+++ b/usr.bin/m4/lib/ohash_entries.c
@@ -0,0 +1,26 @@
+/* $OpenBSD: ohash_entries.c,v 1.2 2004/06/22 20:00:16 espie Exp $ */
+/* ex:ts=8 sw=4:
+ */
+
+/* Copyright (c) 1999, 2004 Marc Espie <espie@openbsd.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include "ohash_int.h"
+
+unsigned int
+ohash_entries(struct ohash *h)
+{
+ return h->total - h->deleted;
+}
diff --git a/usr.bin/m4/lib/ohash_enum.c b/usr.bin/m4/lib/ohash_enum.c
new file mode 100644
index 0000000..966daa1
--- /dev/null
+++ b/usr.bin/m4/lib/ohash_enum.c
@@ -0,0 +1,36 @@
+/* $OpenBSD: ohash_enum.c,v 1.3 2004/06/22 20:00:16 espie Exp $ */
+/* ex:ts=8 sw=4:
+ */
+
+/* Copyright (c) 1999, 2004 Marc Espie <espie@openbsd.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include "ohash_int.h"
+
+void *
+ohash_first(struct ohash *h, unsigned int *pos)
+{
+ *pos = 0;
+ return ohash_next(h, pos);
+}
+
+void *
+ohash_next(struct ohash *h, unsigned int *pos)
+{
+ for (; *pos < h->size; (*pos)++)
+ if (h->t[*pos].p != DELETED && h->t[*pos].p != NULL)
+ return __UNCONST(h->t[(*pos)++].p);
+ return NULL;
+}
diff --git a/usr.bin/m4/lib/ohash_init.3 b/usr.bin/m4/lib/ohash_init.3
new file mode 100644
index 0000000..a0939a8
--- /dev/null
+++ b/usr.bin/m4/lib/ohash_init.3
@@ -0,0 +1,229 @@
+.\" $OpenBSD: ohash_init.3,v 1.14 2007/05/31 19:19:30 jmc Exp $
+.\" Copyright (c) 1999 Marc Espie <espie@openbsd.org>
+.\"
+.\" Permission to use, copy, modify, and distribute this software for any
+.\" purpose with or without fee is hereby granted, provided that the above
+.\" copyright notice and this permission notice appear in all copies.
+.\"
+.\" THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+.\" WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+.\" MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+.\" ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+.\" WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+.\" ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+.\" OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+.\"
+.Dd $Mdocdate: May 31 2007 $
+.Dt OPEN_HASH 3
+.Os
+.Sh NAME
+.Nm ohash_init ,
+.Nm ohash_delete ,
+.Nm ohash_lookup_interval ,
+.Nm ohash_lookup_memory ,
+.Nm ohash_find ,
+.Nm ohash_remove ,
+.Nm ohash_insert ,
+.Nm ohash_first ,
+.Nm ohash_next ,
+.Nm ohash_entries
+.Nd light-weight open hashing
+.Sh SYNOPSIS
+.Fd #include <stdint.h>
+.Fd #include <stddef.h>
+.Fd #include <ohash.h>
+.Ft void
+.Fn ohash_init "struct ohash *h" "unsigned int size" "struct ohash_info *info"
+.Ft void
+.Fn ohash_delete "struct ohash *h"
+.Ft "unsigned int"
+.Fn ohash_lookup_interval "struct ohash *h" "const char *start" "const char *end" "uint32_t hv"
+.Ft "unsigned int"
+.Fn ohash_lookup_memory "struct ohash *h" "const char *k" "size_t s" "uint32_t hv"
+.Ft void *
+.Fn ohash_find "struct ohash *h" "unsigned int i"
+.Ft void *
+.Fn ohash_remove "struct ohash *h" "unsigned int i"
+.Ft void *
+.Fn ohash_insert "struct ohash *h" "unsigned int i" "void *p"
+.Ft void *
+.Fn ohash_first "struct ohash *h" "unsigned int *i"
+.Ft void *
+.Fn ohash_next "struct ohash *h" "unsigned int *i"
+.Ft "unsigned int"
+.Fn ohash_entries "struct ohash *h"
+.Sh DESCRIPTION
+These functions have been designed as a fast, extensible alternative to
+the usual hash table functions.
+They provide storage and retrieval of records indexed by keys,
+where a key is a contiguous sequence of bytes at a fixed position in
+each record.
+Keys can either be NUL-terminated strings or fixed-size memory areas.
+All functions take a pointer to an ohash structure as the
+.Fa h
+function argument.
+Storage for this structure should be provided by user code.
+.Pp
+.Fn ohash_init
+initializes the table to store roughly 2 to the power
+.Fa size
+elements.
+.Fa info
+holds the position of the key in each record, and two pointers to
+.Xr calloc 3
+and
+.Xr free 3 Ns -like
+functions, to use for managing the table internal storage.
+.Pp
+.Fn ohash_delete
+frees storage internal to
+.Fa h .
+Elements themselves should be freed by the user first, using for instance
+.Fn ohash_first
+and
+.Fn ohash_next .
+.Pp
+.Fn ohash_lookup_interval
+and
+.Fn ohash_lookup_memory
+are the basic look-up element functions.
+The hashing function result is provided by the user as
+.Fa hv .
+These return a
+.Qq slot
+in the ohash table
+.Fa h ,
+to be used with
+.Fn ohash_find ,
+.Fn ohash_insert ,
+or
+.Fn ohash_remove .
+This slot is only valid up to the next call to
+.Fn ohash_insert
+or
+.Fn ohash_remove .
+.Pp
+.Fn ohash_lookup_interval
+handles string-like keys.
+.Fn ohash_lookup_interval
+assumes the key is the interval between
+.Fa start
+and
+.Fa end ,
+exclusive,
+though the actual elements stored in the table should only contain
+NUL-terminated keys.
+.Pp
+.Fn ohash_lookup_memory
+assumes the key is the memory area starting at
+.Fa k
+of size
+.Fa s .
+All bytes are significant in key comparison.
+.Pp
+.Fn ohash_find
+retrieves an element from a slot
+.Fa i
+returned by the
+.Fn ohash_lookup*
+functions.
+It returns
+.Dv NULL
+if the slot is empty.
+.Pp
+.Fn ohash_insert
+inserts a new element
+.Fa p
+at slot
+.Fa i .
+Slot
+.Fa i
+must be empty and element
+.Fa p
+must have a key corresponding to the
+.Fn ohash_lookup*
+call.
+.Pp
+.Fn ohash_remove
+removes the element at slot
+.Fa i .
+It returns the removed element, for user code to dispose of, or
+.Dv NULL
+if the slot was empty.
+.Pp
+.Fn ohash_first
+and
+.Fn ohash_next
+can be used to access all elements in an ohash table, like this:
+.Bd -literal -offset indent
+for (n = ohash_first(h, &i); n != NULL; n = ohash_next(h, &i))
+ do_something_with(n);
+.Ed
+.Pp
+.Fa i
+points to an auxiliary unsigned integer used to record the current position
+in the ohash table.
+Those functions are safe to use even while entries are added to/removed
+from the table, but in such a case they don't guarantee that new entries
+will be returned.
+As a special case, they can safely be used to free elements in the table.
+.Pp
+.Fn ohash_entries
+returns the number of elements in the hash table.
+.Sh STORAGE HANDLING
+Only
+.Fn ohash_init ,
+.Fn ohash_insert ,
+.Fn ohash_remove
+and
+.Fn ohash_delete
+may call the user-supplied memory functions.
+It is the responsibility of the user memory allocation code to verify
+that those calls did not fail.
+.Pp
+If memory allocation fails,
+.Fn ohash_init
+returns a useless hash table.
+.Fn ohash_insert
+and
+.Fn ohash_remove
+still perform the requested operation, but the returned table should be
+considered read-only.
+It can still be accessed by
+.Fn ohash_lookup* ,
+.Fn ohash_find ,
+.Fn ohash_first
+and
+.Fn ohash_next
+to dump relevant information to disk before aborting.
+.Sh THREAD SAFETY
+The open hashing functions are not thread-safe by design.
+In particular, in a threaded environment, there is no guarantee that a
+.Qq slot
+will not move between a
+.Fn ohash_lookup*
+and a
+.Fn ohash_find ,
+.Fn ohash_insert
+or
+.Fn ohash_remove
+call.
+.Pp
+Multi-threaded applications should explicitly protect ohash table access.
+.Sh SEE ALSO
+.Xr ohash_interval 3
+.Rs
+.%A Donald E. Knuth
+.%B The Art of Computer Programming
+.%V Vol. 3
+.%P pp 506-550
+.%D 1973
+.Re
+.Sh STANDARDS
+Those functions are completely non-standard and should be avoided in
+portable programs.
+.Sh HISTORY
+Those functions were designed and written for
+.Ox
+.Xr make 1
+by Marc Espie in 1999.
diff --git a/usr.bin/m4/lib/ohash_init.c b/usr.bin/m4/lib/ohash_init.c
new file mode 100644
index 0000000..4d24fa4
--- /dev/null
+++ b/usr.bin/m4/lib/ohash_init.c
@@ -0,0 +1,41 @@
+/* $OpenBSD: ohash_init.c,v 1.2 2004/06/22 20:00:16 espie Exp $ */
+/* ex:ts=8 sw=4:
+ */
+
+/* Copyright (c) 1999, 2004 Marc Espie <espie@openbsd.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include "ohash_int.h"
+
+void
+ohash_init(struct ohash *h, unsigned int size, struct ohash_info *info)
+{
+ h->size = 1UL << size;
+ if (h->size < MINSIZE)
+ h->size = MINSIZE;
+#ifdef STATS_HASH
+ STAT_HASH_CREATION++;
+ STAT_HASH_SIZE += h->size;
+#endif
+ /* Copy info so that caller may free it. */
+ h->info.key_offset = info->key_offset;
+ h->info.halloc = info->halloc;
+ h->info.hfree = info->hfree;
+ h->info.alloc = info->alloc;
+ h->info.data = info->data;
+ h->t = (h->info.halloc)(sizeof(struct _ohash_record) * h->size,
+ h->info.data);
+ h->total = h->deleted = 0;
+}
diff --git a/usr.bin/m4/lib/ohash_int.h b/usr.bin/m4/lib/ohash_int.h
new file mode 100644
index 0000000..88c818b
--- /dev/null
+++ b/usr.bin/m4/lib/ohash_int.h
@@ -0,0 +1,23 @@
+/* $OpenBSD: ohash_int.h,v 1.3 2006/01/16 15:52:25 espie Exp $ */
+
+#if HAVE_NBTOOL_CONFIG_H
+#include "nbtool_config.h"
+#endif
+
+#include <stddef.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+#include "ohash.h"
+
+struct _ohash_record {
+ u_int32_t hv;
+ const char *p;
+};
+
+#define DELETED ((const char *)h)
+#define NONE (h->size)
+
+/* Don't bother changing the hash table if the change is small enough. */
+#define MINSIZE (1UL << 4)
+#define MINDELETED 4
diff --git a/usr.bin/m4/lib/ohash_interval.3 b/usr.bin/m4/lib/ohash_interval.3
new file mode 100644
index 0000000..2e56ca5
--- /dev/null
+++ b/usr.bin/m4/lib/ohash_interval.3
@@ -0,0 +1,90 @@
+.\" $OpenBSD: ohash_interval.3,v 1.11 2007/05/31 19:19:30 jmc Exp $
+.\" Copyright (c) 2001 Marc Espie <espie@openbsd.org>
+.\"
+.\" Permission to use, copy, modify, and distribute this software for any
+.\" purpose with or without fee is hereby granted, provided that the above
+.\" copyright notice and this permission notice appear in all copies.
+.\"
+.\" THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+.\" WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+.\" MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+.\" ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+.\" WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+.\" ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+.\" OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+.\"
+.Dd $Mdocdate: May 31 2007 $
+.Dt OPEN_HASH_HELPER 3
+.Os
+.Sh NAME
+.Nm ohash_interval ,
+.Nm ohash_create_entry ,
+.Nm ohash_qlookup ,
+.Nm ohash_qlookupi
+.Nd helper functions for open hashing
+.Sh SYNOPSIS
+.Fd #include <stdint.h>
+.Fd #include <stddef.h>
+.Fd #include <ohash.h>
+.Ft u_int32_t
+.Fn ohash_interval "const char *start" "const char **pend"
+.Ft "void *"
+.Fn ohash_create_entry "struct ohash_info *info" "const char *start" "const char **pend"
+.Ft "unsigned int"
+.Fn ohash_qlookupi "struct ohash *h" "const char *start" "const char **pend"
+.Ft "unsigned int"
+.Fn ohash_qlookup "struct ohash *h" "const char *start"
+.Sh DESCRIPTION
+These functions are commonly used to simplify open hashing usage, and use
+similar conventions.
+They operate indifferently on NUL-terminated strings
+.Po
+by setting
+.Fa *pend
+=
+.Dv NULL
+.Pc
+or memory ranges
+.Po
+delimited by
+.Fa start
+and
+.Fa *pend
+.Pc .
+For NUL-terminated strings, as a side effect, those functions
+set
+.Fa *pend
+to the terminating NUL byte.
+.Pp
+.Fn ohash_interval
+is a simple hashing function that yields good results on common data sets.
+.Pp
+.Fn ohash_create_entry
+can be used to create a new record with a given key.
+In that case,
+the alloc field of
+.Fa info
+should point to a
+.Xr malloc 3 Ns -like
+function to allocate the storage.
+.Pp
+.Fn ohash_qlookupi
+is a wrapper function that simply calls
+.Fn ohash_interval
+and
+.Fn ohash_lookup_interval .
+.Pp
+.Fn ohash_qlookup
+is a variation on
+.Fn ohash_qlookupi
+designed for NUL-terminated strings.
+.Sh SEE ALSO
+.Xr ohash_init 3
+.Sh STANDARDS
+Those functions are completely non-standard and should be avoided in
+portable programs.
+.Sh HISTORY
+Those functions were designed and written for
+.Ox
+.Xr make 1
+by Marc Espie in 1999.
diff --git a/usr.bin/m4/lib/ohash_interval.c b/usr.bin/m4/lib/ohash_interval.c
new file mode 100644
index 0000000..2434d3b
--- /dev/null
+++ b/usr.bin/m4/lib/ohash_interval.c
@@ -0,0 +1,36 @@
+/* $OpenBSD: ohash_interval.c,v 1.3 2006/01/16 15:52:25 espie Exp $ */
+/* ex:ts=8 sw=4:
+ */
+
+/* Copyright (c) 1999, 2004 Marc Espie <espie@openbsd.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include "ohash_int.h"
+
+uint32_t
+ohash_interval(const char *s, const char **e)
+{
+ uint32_t k;
+
+ if (!*e)
+ *e = s + strlen(s);
+ if (s == *e)
+ k = 0;
+ else
+ k = *s++;
+ while (s != *e)
+ k = ((k << 2) | (k >> 30)) ^ *s++;
+ return k;
+}
diff --git a/usr.bin/m4/lib/ohash_lookup_interval.c b/usr.bin/m4/lib/ohash_lookup_interval.c
new file mode 100644
index 0000000..e497801
--- /dev/null
+++ b/usr.bin/m4/lib/ohash_lookup_interval.c
@@ -0,0 +1,68 @@
+/* $OpenBSD: ohash_lookup_interval.c,v 1.3 2006/01/16 15:52:25 espie Exp $ */
+/* ex:ts=8 sw=4:
+ */
+
+/* Copyright (c) 1999, 2004 Marc Espie <espie@openbsd.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include "ohash_int.h"
+
+unsigned int
+ohash_lookup_interval(struct ohash *h, const char *start, const char *end,
+ uint32_t hv)
+{
+ unsigned int i, incr;
+ unsigned int empty;
+
+#ifdef STATS_HASH
+ STAT_HASH_LOOKUP++;
+#endif
+ empty = NONE;
+ i = hv % h->size;
+ incr = ((hv % (h->size-2)) & ~1) + 1;
+ while (h->t[i].p != NULL) {
+#ifdef STATS_HASH
+ STAT_HASH_LENGTH++;
+#endif
+ if (h->t[i].p == DELETED) {
+ if (empty == NONE)
+ empty = i;
+ } else if (h->t[i].hv == hv &&
+ strncmp(h->t[i].p+h->info.key_offset, start,
+ end - start) == 0 &&
+ (h->t[i].p+h->info.key_offset)[end-start] == '\0') {
+ if (empty != NONE) {
+ h->t[empty].hv = hv;
+ h->t[empty].p = h->t[i].p;
+ h->t[i].p = DELETED;
+ return empty;
+ } else {
+#ifdef STATS_HASH
+ STAT_HASH_POSITIVE++;
+#endif
+ return i;
+ }
+ }
+ i += incr;
+ if (i >= h->size)
+ i -= h->size;
+ }
+
+ /* Found an empty position. */
+ if (empty != NONE)
+ i = empty;
+ h->t[i].hv = hv;
+ return i;
+}
diff --git a/usr.bin/m4/lib/ohash_lookup_memory.c b/usr.bin/m4/lib/ohash_lookup_memory.c
new file mode 100644
index 0000000..93ded5a
--- /dev/null
+++ b/usr.bin/m4/lib/ohash_lookup_memory.c
@@ -0,0 +1,64 @@
+/* $OpenBSD: ohash_lookup_memory.c,v 1.3 2006/01/16 15:52:25 espie Exp $ */
+/* ex:ts=8 sw=4:
+ */
+
+/* Copyright (c) 1999, 2004 Marc Espie <espie@openbsd.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include "ohash_int.h"
+
+unsigned int
+ohash_lookup_memory(struct ohash *h, const char *k, size_t size, uint32_t hv)
+{
+ unsigned int i, incr;
+ unsigned int empty;
+
+#ifdef STATS_HASH
+ STAT_HASH_LOOKUP++;
+#endif
+ empty = NONE;
+ i = hv % h->size;
+ incr = ((hv % (h->size-2)) & ~1) + 1;
+ while (h->t[i].p != NULL) {
+#ifdef STATS_HASH
+ STAT_HASH_LENGTH++;
+#endif
+ if (h->t[i].p == DELETED) {
+ if (empty == NONE)
+ empty = i;
+ } else if (h->t[i].hv == hv &&
+ memcmp(h->t[i].p+h->info.key_offset, k, size) == 0) {
+ if (empty != NONE) {
+ h->t[empty].hv = hv;
+ h->t[empty].p = h->t[i].p;
+ h->t[i].p = DELETED;
+ return empty;
+ } else {
+#ifdef STATS_HASH
+ STAT_HASH_POSITIVE++;
+#endif
+ } return i;
+ }
+ i += incr;
+ if (i >= h->size)
+ i -= h->size;
+ }
+
+ /* Found an empty position. */
+ if (empty != NONE)
+ i = empty;
+ h->t[i].hv = hv;
+ return i;
+}
diff --git a/usr.bin/m4/lib/ohash_qlookup.c b/usr.bin/m4/lib/ohash_qlookup.c
new file mode 100644
index 0000000..58b3454
--- /dev/null
+++ b/usr.bin/m4/lib/ohash_qlookup.c
@@ -0,0 +1,27 @@
+/* $OpenBSD: ohash_qlookup.c,v 1.2 2004/06/22 20:00:17 espie Exp $ */
+/* ex:ts=8 sw=4:
+ */
+
+/* Copyright (c) 1999, 2004 Marc Espie <espie@openbsd.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include "ohash_int.h"
+
+unsigned int
+ohash_qlookup(struct ohash *h, const char *s)
+{
+ const char *e = NULL;
+ return ohash_qlookupi(h, s, &e);
+}
diff --git a/usr.bin/m4/lib/ohash_qlookupi.c b/usr.bin/m4/lib/ohash_qlookupi.c
new file mode 100644
index 0000000..2acec4e
--- /dev/null
+++ b/usr.bin/m4/lib/ohash_qlookupi.c
@@ -0,0 +1,29 @@
+/* $OpenBSD: ohash_qlookupi.c,v 1.2 2004/06/22 20:00:17 espie Exp $ */
+/* ex:ts=8 sw=4:
+ */
+
+/* Copyright (c) 1999, 2004 Marc Espie <espie@openbsd.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include "ohash_int.h"
+
+unsigned int
+ohash_qlookupi(struct ohash *h, const char *s, const char **e)
+{
+ u_int32_t hv;
+
+ hv = ohash_interval(s, e);
+ return ohash_lookup_interval(h, s, *e, hv);
+}
diff --git a/usr.bin/m4/lib/strtonum.c b/usr.bin/m4/lib/strtonum.c
new file mode 100644
index 0000000..8d11712
--- /dev/null
+++ b/usr.bin/m4/lib/strtonum.c
@@ -0,0 +1,73 @@
+/* $OpenBSD: strtonum.c,v 1.6 2004/08/03 19:38:01 millert Exp $ */
+
+/*
+ * Copyright (c) 2004 Ted Unangst and Todd Miller
+ * All rights reserved.
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+#if HAVE_NBTOOL_CONFIG_H
+#include "nbtool_config.h"
+#endif
+#include <sys/cdefs.h>
+__RCSID("$NetBSD: strtonum.c,v 1.2 2009/10/26 21:14:18 christos Exp $");
+#include <errno.h>
+#include <limits.h>
+#include <stdlib.h>
+
+#define INVALID 1
+#define TOOSMALL 2
+#define TOOLARGE 3
+
+long long
+strtonum(const char *numstr, long long minval, long long maxval,
+ const char **errstrp);
+long long
+strtonum(const char *numstr, long long minval, long long maxval,
+ const char **errstrp)
+{
+ long long ll = 0;
+ char *ep;
+ int error = 0;
+ struct errval {
+ const char *errstr;
+ int err;
+ } ev[4] = {
+ { NULL, 0 },
+ { "invalid", EINVAL },
+ { "too small", ERANGE },
+ { "too large", ERANGE },
+ };
+
+ ev[0].err = errno;
+ errno = 0;
+ if (minval > maxval)
+ error = INVALID;
+ else {
+ ll = strtoll(numstr, &ep, 10);
+ if (numstr == ep || *ep != '\0')
+ error = INVALID;
+ else if ((ll == LLONG_MIN && errno == ERANGE) || ll < minval)
+ error = TOOSMALL;
+ else if ((ll == LLONG_MAX && errno == ERANGE) || ll > maxval)
+ error = TOOLARGE;
+ }
+ if (errstrp != NULL)
+ *errstrp = ev[error].errstr;
+ errno = ev[error].err;
+ if (error)
+ ll = 0;
+
+ return (ll);
+}
+
diff --git a/usr.bin/m4/look.c b/usr.bin/m4/look.c
new file mode 100644
index 0000000..de2c282
--- /dev/null
+++ b/usr.bin/m4/look.c
@@ -0,0 +1,315 @@
+/* $NetBSD: look.c,v 1.13 2016/01/16 17:00:07 christos Exp $ */
+/* $OpenBSD: look.c,v 1.21 2009/10/14 17:23:17 sthen Exp $ */
+
+/*
+ * Copyright (c) 1989, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Ozan Yigit at York University.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+/*
+ * look.c
+ * Facility: m4 macro processor
+ * by: oz
+ */
+#if HAVE_NBTOOL_CONFIG_H
+#include "nbtool_config.h"
+#endif
+#include <sys/cdefs.h>
+__RCSID("$NetBSD: look.c,v 1.13 2016/01/16 17:00:07 christos Exp $");
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <stddef.h>
+#include <string.h>
+#include <ohash.h>
+#include "mdef.h"
+#include "stdd.h"
+#include "extern.h"
+
+static void *hash_alloc(size_t, void *);
+static void hash_free(void *, size_t, void *);
+static void *element_alloc(size_t, void *);
+static void setup_definition(struct macro_definition *, const char *,
+ const char *);
+
+static struct ohash_info macro_info = {
+ offsetof(struct ndblock, name),
+ NULL, hash_alloc, hash_free, element_alloc };
+
+struct ohash macros;
+
+/* Support routines for hash tables. */
+void *
+hash_alloc(size_t s, void *u UNUSED)
+{
+ void *storage = xalloc(s, "hash alloc");
+ if (storage)
+ memset(storage, 0, s);
+ return storage;
+}
+
+void
+hash_free(void *p, size_t s UNUSED, void *u UNUSED)
+{
+ free(p);
+}
+
+void *
+element_alloc(size_t s, void *u UNUSED)
+{
+ return xalloc(s, "element alloc");
+}
+
+void
+init_macros(void)
+{
+ ohash_init(&macros, 10, &macro_info);
+}
+
+/*
+ * find name in the hash table
+ */
+ndptr
+lookup(const char *name)
+{
+ return ohash_find(&macros, ohash_qlookup(&macros, name));
+}
+
+struct macro_definition *
+lookup_macro_definition(const char *name)
+{
+ ndptr p;
+
+ p = ohash_find(&macros, ohash_qlookup(&macros, name));
+ if (p)
+ return p->d;
+ else
+ return NULL;
+}
+
+static void
+setup_definition(struct macro_definition *d, const char *defn, const char *name)
+{
+ ndptr p;
+
+ if (strncmp(defn, BUILTIN_MARKER, sizeof(BUILTIN_MARKER)-1) == 0 &&
+ (p = macro_getbuiltin(defn+sizeof(BUILTIN_MARKER)-1)) != NULL) {
+ d->type = macro_builtin_type(p);
+ d->defn = xstrdup(defn+sizeof(BUILTIN_MARKER)-1);
+ } else {
+ if (!*defn)
+ d->defn = xstrdup(null);
+ else
+ d->defn = xstrdup(defn);
+ d->type = MACRTYPE;
+ }
+ if (STREQ(name, defn))
+ d->type |= RECDEF;
+}
+
+static ndptr
+create_entry(const char *name)
+{
+ const char *end = NULL;
+ unsigned int i;
+ ndptr n;
+
+ i = ohash_qlookupi(&macros, name, &end);
+ n = ohash_find(&macros, i);
+ if (n == NULL) {
+ n = ohash_create_entry(&macro_info, name, &end);
+ ohash_insert(&macros, i, n);
+ n->trace_flags = FLAG_NO_TRACE;
+ n->builtin_type = MACRTYPE;
+ n->d = NULL;
+ }
+ return n;
+}
+
+void
+macro_define(const char *name, const char *defn)
+{
+ ndptr n = create_entry(name);
+ if (n->d != NULL) {
+ if (n->d->defn != null)
+ free(n->d->defn);
+ } else {
+ n->d = xalloc(sizeof(struct macro_definition), NULL);
+ n->d->next = NULL;
+ }
+ setup_definition(n->d, defn, name);
+}
+
+void
+macro_pushdef(const char *name, const char *defn)
+{
+ ndptr n;
+ struct macro_definition *d;
+
+ n = create_entry(name);
+ d = xalloc(sizeof(struct macro_definition), NULL);
+ d->next = n->d;
+ n->d = d;
+ setup_definition(n->d, defn, name);
+}
+
+void
+macro_undefine(const char *name)
+{
+ ndptr n = lookup(name);
+ if (n != NULL) {
+ struct macro_definition *r, *r2;
+
+ for (r = n->d; r != NULL; r = r2) {
+ r2 = r->next;
+ if (r->defn != null)
+ free(r->defn);
+ free(r);
+ }
+ n->d = NULL;
+ }
+}
+
+void
+macro_popdef(const char *name)
+{
+ ndptr n = lookup(name);
+
+ if (n != NULL) {
+ struct macro_definition *r = n->d;
+ if (r != NULL) {
+ n->d = r->next;
+ if (r->defn != null)
+ free(r->defn);
+ free(r);
+ }
+ }
+}
+
+void
+macro_for_all(void (*f)(const char *, struct macro_definition *))
+{
+ ndptr n;
+ unsigned int i;
+
+ for (n = ohash_first(&macros, &i); n != NULL;
+ n = ohash_next(&macros, &i))
+ if (n->d != NULL)
+ f(n->name, n->d);
+}
+
+void
+setup_builtin(const char *name, unsigned int type)
+{
+ ndptr n;
+ char *name2;
+
+ if (prefix_builtins) {
+ name2 = xalloc(strlen(name)+3+1, NULL);
+ memcpy(name2, "m4_", 3);
+ memcpy(name2 + 3, name, strlen(name)+1);
+ } else
+ name2 = xstrdup(name);
+
+ n = create_entry(name2);
+ n->builtin_type = type;
+ n->d = xalloc(sizeof(struct macro_definition), NULL);
+ n->d->defn = name2;
+ n->d->type = type;
+ n->d->next = NULL;
+}
+
+void
+mark_traced(const char *name, int on)
+{
+ ndptr p;
+ unsigned int i;
+
+ if (name == NULL) {
+ if (on)
+ trace_flags |= TRACE_ALL;
+ else
+ trace_flags &= ~TRACE_ALL;
+ for (p = ohash_first(&macros, &i); p != NULL;
+ p = ohash_next(&macros, &i))
+ p->trace_flags = FLAG_NO_TRACE;
+ } else {
+ p = create_entry(name);
+ p->trace_flags = on;
+ }
+}
+
+ndptr
+macro_getbuiltin(const char *name)
+{
+ ndptr p;
+
+ p = lookup(name);
+ if (p == NULL || p->builtin_type == MACRTYPE)
+ return NULL;
+ else
+ return p;
+}
+
+#ifdef REAL_FREEZE
+static void
+recurse(FILE *f, ndptr n, struct macro_definition *d)
+{
+ if (d->next != NULL)
+ recurse(f, n, d->next);
+
+ // skip built-ins, because it is cheaper to do so
+ // and initialize them manually
+ if (d->type & (NOARGS|NEEDARGS))
+ return;
+ fprintf(f, "%c%zu,%zu\n%s%s\n",
+ (d->type & (NOARGS|NEEDARGS)) ? 'F' : 'T',
+ strlen(n->name), strlen(d->defn),
+ n->name, d->defn);
+}
+
+static void
+dump_entry(FILE *f, ndptr n)
+{
+ if (n->d == NULL)
+ return;
+ recurse(f, n, n->d);
+}
+
+void
+dump_state(FILE *f)
+{
+ ndptr n;
+ unsigned int i;
+ for (n = ohash_first(&macros, &i); n != NULL;
+ n = ohash_next(&macros, &i))
+ dump_entry(f, n);
+}
+#endif
diff --git a/usr.bin/m4/m4.1 b/usr.bin/m4/m4.1
new file mode 100644
index 0000000..f033cee
--- /dev/null
+++ b/usr.bin/m4/m4.1
@@ -0,0 +1,522 @@
+.\" $NetBSD: m4.1,v 1.27 2016/01/17 11:24:28 wiz Exp $
+.\" @(#) $OpenBSD: m4.1,v 1.56 2009/10/14 17:19:47 sthen Exp $
+.\"
+.\" Copyright (c) 1989, 1993
+.\" The Regents of the University of California. All rights reserved.
+.\"
+.\" This code is derived from software contributed to Berkeley by
+.\" Ozan Yigit at York University.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\" notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must reproduce the above copyright
+.\" notice, this list of conditions and the following disclaimer in the
+.\" documentation and/or other materials provided with the distribution.
+.\" 3. Neither the name of the University nor the names of its contributors
+.\" may be used to endorse or promote products derived from this software
+.\" without specific prior written permission.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+.\" ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+.\" SUCH DAMAGE.
+.\"
+.Dd January 16, 2015
+.Dt M4 1
+.Os
+.Sh NAME
+.Nm m4
+.Nd macro language processor
+.Sh SYNOPSIS
+.Nm m4
+.Op Fl EGgiPQsv
+.Oo
+.Sm off
+.Fl D Ar name Op No = Ar value
+.Sm on
+.Oc
+.Op Fl d Ar flags
+.Op Fl e Ar filename
+.Op Fl F Ar filename
+.Op Fl I Ar dirname
+.Op Fl L Ar number
+.Op Fl o Ar filename
+.Op Fl R Ar filename
+.Op Fl t Ar macro
+.Op Fl U Ns Ar name
+.Op Ar
+.Sh DESCRIPTION
+The
+.Nm m4
+utility is a macro processor that can be used as a front end to any
+language (e.g., C, ratfor, fortran, lex, and yacc).
+If no input files are given,
+.Nm m4
+reads from the standard input,
+otherwise files specified on the command line are
+processed in the given order.
+Input files can be regular files, files in the m4 include paths, or a
+single dash
+.Pq Sq - ,
+denoting standard input.
+.Nm m4
+writes
+the processed text to the standard output, unless told otherwise.
+.Pp
+Macro calls have the form name(argument1[, argument2, ..., argumentN]).
+.Pp
+There cannot be any space following the macro name and the open
+parenthesis
+.Sq \&( .
+If the macro name is not followed by an open
+parenthesis it is processed with no arguments.
+.Pp
+Macro names consist of a leading alphabetic or underscore
+possibly followed by alphanumeric or underscore characters, e.g.,
+valid macro names match the pattern
+.Dq [a-zA-Z_][a-zA-Z0-9_]* .
+.Pp
+In arguments to macros, leading unquoted space, tab, and newline
+.Pq Sq \en
+characters are ignored.
+To quote strings, use left and right single quotes
+.Po e.g.,\ \&
+.Sq "\ this is a string with a leading space"
+.Pc .
+You can change the quote characters with the
+.Ic changequote
+built-in macro.
+.Pp
+Most built-ins don't make any sense without arguments, and hence are not
+recognized as special when not followed by an open parenthesis.
+.Pp
+The options are as follows:
+.Bl -tag -width Ds
+.It Fl D , Fl Fl define Ar name Ns Op Pf = Ns Ar value
+Define the symbol
+.Ar name
+to have some value (or
+.Dv NULL ) .
+.It Fl d , Fl Fl debug Ar "flags"
+Set trace flags.
+.Ar flags
+may hold the following:
+.Bl -tag -width Ds
+.It Ar a
+print macro arguments.
+.It Ar c
+print macro expansion over several lines.
+.It Ar e
+print result of macro expansion.
+.It Ar f
+print filename location.
+.It Ar l
+print line number.
+.It Ar q
+quote arguments and expansion with the current quotes.
+.It Ar t
+start with all macros traced.
+.It Ar x
+number macro expansions.
+.It Ar V
+turn on all options.
+.El
+.Pp
+By default, trace is set to
+.Qq eq .
+.It Fl E , Fl Fl fatal-warnings
+Warnings make
+.Nm
+exit.
+.It Fl e , Fl Fl error-output Ar filename
+Redirect error output to filename.
+.It Fl F , Fl Fl freeze-state Ar filename
+Save the input state to
+.Ar filename .
+.It Fl G , Fl Fl traditional
+Disable GNU-m4 extensions.
+.It Fl g , Fl Fl gnu
+Activate GNU-m4 compatibility mode.
+In this mode, translit handles simple character
+ranges (e.g., a-z), regular expressions mimic emacs behavior,
+multiple m4wrap calls are handled as a stack,
+the number of diversions is unlimited,
+empty names for macro definitions are allowed,
+and eval understands
+.Sq 0rbase:value
+numbers.
+.It Fl Fl help
+Print help message and exit.
+.It Fl I , Fl Fl include Ar "dirname"
+Add directory
+.Ar dirname
+to the include path.
+.It Fl i , Fl Fl interactive
+Set unbuffered output, disable tty signals.
+.It Fl L , Fl Fl nesting-limit
+Set the nesting limit in macro expansions.
+This is unimplemented and unlimited.
+.It Fl o Ar filename
+Send trace output to
+.Ar filename .
+.It Fl P , Fl Fl prefix-builtins
+Prefix all built-in macros with
+.Sq m4_ .
+For example, instead of writing
+.Ic define ,
+use
+.Ic m4_define .
+.It Fl Q , Fl Fl quiet , Fl Fl silent
+Don't print warnings.
+.It Fl R , Fl Fl reload-state Ar filename
+Reload a previously saved state from
+.Ar filename .
+.It Fl s , Fl Fl synclines
+Output line synchronization directives, suitable for
+.Xr cpp 1 .
+.It Fl t , Fl Fl trace Ar macro
+Turn tracing on for
+.Ar macro .
+.It Fl U , Fl Fl undefine Ar "name"
+Undefine the symbol
+.Ar name .
+.It Fl v , Fl Fl version
+Print the version and exit.
+.El
+.Sh SYNTAX
+.Nm m4
+provides the following built-in macros.
+They may be redefined, losing their original meaning.
+Return values are null unless otherwise stated.
+.Bl -tag -width changequote
+.It Fn builtin name
+Calls a built-in by its
+.Fa name ,
+overriding possible redefinitions.
+.It Fn changecom startcomment endcomment
+Changes the start comment and end comment sequences.
+Comment sequences may be up to five characters long.
+The default values are the hash sign
+and the newline character.
+.Bd -literal -offset indent
+# This is a comment
+.Ed
+.Pp
+With no arguments, comments are turned off.
+With one single argument, the end comment sequence is set
+to the newline character.
+.It Fn changequote beginquote endquote
+Defines the open quote and close quote sequences.
+Quote sequences may be up to five characters long.
+The default values are the backquote character and the quote
+character.
+.Bd -literal -offset indent
+`Here is a quoted string'
+.Ed
+.Pp
+With no arguments, the default quotes are restored.
+With one single argument, the close quote sequence is set
+to the newline character.
+.It Fn decr arg
+Decrements the argument
+.Fa arg
+by 1.
+The argument
+.Fa arg
+must be a valid numeric string.
+.It Fn define name value
+Define a new macro named by the first argument
+.Fa name
+to have the
+value of the second argument
+.Fa value .
+Each occurrence of
+.Sq $n
+(where
+.Ar n
+is 0 through 9) is replaced by the
+.Ar n Ns 'th
+argument.
+.Sq $0
+is the name of the calling macro.
+Undefined arguments are replaced by a null string.
+.Sq $#
+is replaced by the number of arguments;
+.Sq $*
+is replaced by all arguments comma separated;
+.Sq $@
+is the same as
+.Sq $*
+but all arguments are quoted against further expansion.
+.It Fn defn name ...
+Returns the quoted definition for each argument.
+This can be used to rename
+macro definitions (even for built-in macros).
+.It Fn divert num
+There are 10 output queues (numbered 0-9).
+At the end of processing
+.Nm m4
+concatenates all the queues in numerical order to produce the
+final output.
+Initially the output queue is 0.
+The divert
+macro allows you to select a new output queue (an invalid argument
+passed to divert causes output to be discarded).
+.It Ic divnum
+Returns the current output queue number.
+.It Ic dnl
+Discard input characters up to and including the next newline.
+.It Fn dumpdef name ...
+Prints the names and definitions for the named items, or for everything
+if no arguments are passed.
+.It Fn errprint msg
+Prints the first argument on the standard error output stream.
+.It Fn esyscmd cmd
+Passes its first argument to a shell and returns the shell's standard output.
+Note that the shell shares its standard input and standard error with
+.Nm m4 .
+.It Fn eval expr[,radix[,minimum]]
+Computes the first argument as an arithmetic expression using 32-bit
+arithmetic.
+Operators are the standard C ternary, arithmetic, logical,
+shift, relational, bitwise, and parentheses operators.
+You can specify
+octal, decimal, and hexadecimal numbers as in C.
+The optional second argument
+.Fa radix
+specifies the radix for the result and the optional third argument
+.Fa minimum
+specifies the minimum number of digits in the result.
+.It Fn expr expr
+This is an alias for
+.Ic eval .
+.It Fn format formatstring arg1 ...
+Returns
+.Fa formatstring
+with escape sequences substituted with
+.Fa arg1
+and following arguments, in a way similar to
+.Xr printf 3 .
+This built-in is only available in GNU-m4 compatibility mode, and the only
+parameters implemented are there for autoconf compatibility:
+left-padding flag, an optional field width, a maximum field width,
+*-specified field widths, and the %s and %c data type.
+.It Fn ifdef name yes no
+If the macro named by the first argument is defined then return the second
+argument, otherwise the third.
+If there is no third argument, the value is
+.Dv NULL .
+The word
+.Qq unix
+is predefined.
+.It Fn ifelse a b yes ...
+If the first argument
+.Fa a
+matches the second argument
+.Fa b
+then
+.Fn ifelse
+returns
+the third argument
+.Fa yes .
+If the match fails the three arguments are
+discarded and the next three arguments are used until there is
+zero or one arguments left, either this last argument or
+.Dv NULL
+is returned if no other matches were found.
+.It Fn include name
+Returns the contents of the file specified in the first argument.
+If the file is not found as is, look through the include path:
+first the directories specified with
+.Fl I
+on the command line, then the environment variable
+.Ev M4PATH ,
+as a colon-separated list of directories.
+Include aborts with an error message if the file cannot be included.
+.It Fn incr arg
+Increments the argument by 1.
+The argument must be a valid numeric string.
+.It Fn index string substring
+Returns the index of the second argument in the first argument (e.g.,
+.Ic index(the quick brown fox jumped, fox)
+returns 16).
+If the second
+argument is not found index returns \-1.
+.It Fn indir macro arg1 ...
+Indirectly calls the macro whose name is passed as the first argument,
+with the remaining arguments passed as first, ... arguments.
+.It Fn len arg
+Returns the number of characters in the first argument.
+Extra arguments
+are ignored.
+.It Fn m4exit code
+Immediately exits with the return value specified by the first argument,
+0 if none.
+.It Fn m4wrap todo
+Allows you to define what happens at the final
+.Dv EOF ,
+usually for cleanup purposes (e.g.,
+.Ic m4wrap("cleanup(tempfile)")
+causes the macro cleanup to be
+invoked after all other processing is done).
+.Pp
+Multiple calls to
+.Fn m4wrap
+get inserted in sequence at the final
+.Dv EOF .
+.It Fn maketemp template
+Invokes
+.Xr mkstemp 3
+on the first argument, and returns the modified string.
+This can be used to create unique
+temporary file names.
+.It Fn paste file
+Includes the contents of the file specified by the first argument without
+any macro processing.
+Aborts with an error message if the file cannot be
+included.
+.It Fn patsubst string regexp replacement
+Substitutes a regular expression in a string with a replacement string.
+Usual substitution patterns apply: an ampersand
+.Pq Sq \&&
+is replaced by the string matching the regular expression.
+The string
+.Sq \e# ,
+where
+.Sq #
+is a digit, is replaced by the corresponding back-reference.
+.It Fn popdef arg ...
+Restores the
+.Ic pushdef Ns ed
+definition for each argument.
+.It Fn pushdef macro def
+Takes the same arguments as
+.Ic define ,
+but it saves the definition on a
+stack for later retrieval by
+.Fn popdef .
+.It Fn regexp string regexp replacement
+Finds a regular expression in a string.
+If no further arguments are given,
+it returns the first match position or \-1 if no match.
+If a third argument
+is provided, it returns the replacement string, with sub-patterns replaced.
+.It Fn shift arg1 ...
+Returns all but the first argument, the remaining arguments are
+quoted and pushed back with commas in between.
+The quoting
+nullifies the effect of the extra scan that will subsequently be
+performed.
+.It Fn sinclude file
+Similar to
+.Ic include ,
+except it ignores any errors.
+.It Fn spaste file
+Similar to
+.Fn paste ,
+except it ignores any errors.
+.It Fn substr string offset length
+Returns a substring of the first argument starting at the offset specified
+by the second argument and the length specified by the third argument.
+If no third argument is present it returns the rest of the string.
+.It Fn syscmd cmd
+Passes the first argument to the shell.
+Nothing is returned.
+.It Ic sysval
+Returns the return value from the last
+.Ic syscmd .
+.It Fn traceon arg ...
+Enables tracing of macro expansions for the given arguments, or for all
+macros if no argument is given.
+.It Fn traceoff arg ...
+Disables tracing of macro expansions for the given arguments, or for all
+macros if no argument is given.
+.It Fn translit string mapfrom mapto
+Transliterate the characters in the first argument from the set
+given by the second argument to the set given by the third.
+You cannot use
+.Xr tr 1
+style abbreviations.
+.It Fn undefine name1 ...
+Removes the definition for the macros specified by its arguments.
+.It Fn undivert arg ...
+Flushes the named output queues (or all queues if no arguments).
+.It Ic unix
+A pre-defined macro for testing the OS platform.
+.It Ic __line__
+Returns the current file's line number.
+.It Ic __file__
+Returns the current file's name.
+.El
+.Sh STANDARDS
+The
+.Nm
+utility is compliant with the
+.St -p1003.1-2008
+specification.
+.Pp
+The flags
+.Op Fl dgIot
+and the macros
+.Ic builtin ,
+.Ic esyscmd ,
+.Ic expr ,
+.Ic format ,
+.Ic indir ,
+.Ic paste ,
+.Ic patsubst ,
+.Ic regexp ,
+.Ic spaste ,
+.Ic unix ,
+.Ic __line__ ,
+and
+.Ic __file__
+are extensions to that specification.
+.Pp
+The output format of tracing and of
+.Ic dumpdef
+are not specified in any standard,
+are likely to change and should not be relied upon.
+The current format of tracing is closely modelled on
+.Nm gnu-m4 ,
+to allow
+.Nm autoconf
+to work.
+.Pp
+The built-ins
+.Ic pushdef
+and
+.Ic popdef
+handle macro definitions as a stack.
+However,
+.Ic define
+interacts with the stack in an undefined way.
+In this implementation,
+.Ic define
+replaces the top-most definition only.
+Other implementations may erase all definitions on the stack instead.
+.Pp
+All built-ins do expand without arguments in many other
+.Nm m4 .
+.Pp
+Many other
+.Nm
+have dire size limitations with respect to buffer sizes.
+.Sh AUTHORS
+.An -nosplit
+.An Ozan Yigit Aq Mt oz@sis.yorku.ca
+and
+.An Richard A. O'Keefe Aq Mt ok@goanna.cs.rmit.OZ.AU .
+.Pp
+GNU-m4 compatibility extensions by
+.An Marc Espie Aq Mt espie@cvs.openbsd.org .
diff --git a/usr.bin/m4/main.c b/usr.bin/m4/main.c
new file mode 100644
index 0000000..cba65ad
--- /dev/null
+++ b/usr.bin/m4/main.c
@@ -0,0 +1,811 @@
+/* $OpenBSD: main.c,v 1.77 2009/10/14 17:19:47 sthen Exp $ */
+/* $NetBSD: main.c,v 1.46 2016/01/23 14:24:43 christos Exp $ */
+
+/*-
+ * Copyright (c) 1989, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Ozan Yigit at York University.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+/*
+ * main.c
+ * Facility: m4 macro processor
+ * by: oz
+ */
+#if HAVE_NBTOOL_CONFIG_H
+#include "nbtool_config.h"
+#endif
+#include <sys/cdefs.h>
+__RCSID("$NetBSD: main.c,v 1.46 2016/01/23 14:24:43 christos Exp $");
+#include <assert.h>
+#include <signal.h>
+#include <getopt.h>
+#include <err.h>
+#include <errno.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <ctype.h>
+#include <string.h>
+#include <stddef.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <ohash.h>
+#include "mdef.h"
+#include "stdd.h"
+#include "extern.h"
+#include "pathnames.h"
+
+ndptr hashtab[HASHSIZE]; /* hash table for macros etc. */
+stae *mstack; /* stack of m4 machine */
+char *sstack; /* shadow stack, for string space extension */
+static size_t STACKMAX; /* current maximum size of stack */
+int sp; /* current m4 stack pointer */
+int fp; /* m4 call frame pointer */
+struct input_file infile[MAXINP];/* input file stack (0=stdin) */
+FILE **outfile; /* diversion array(0=bitbucket)*/
+int maxout;
+FILE *active; /* active output file pointer */
+int ilevel = 0; /* input file stack pointer */
+int oindex = 0; /* diversion index.. */
+const char *null = ""; /* as it says.. just a null.. */
+char **m4wraps = NULL; /* m4wraps array. */
+int maxwraps = 0; /* size of m4wraps array */
+int wrapindex = 0; /* current offset in m4wraps */
+char lquote[MAXCCHARS+1] = {LQUOTE}; /* left quote character (`) */
+char rquote[MAXCCHARS+1] = {RQUOTE}; /* right quote character (') */
+char scommt[MAXCCHARS+1] = {SCOMMT}; /* start character for comment */
+char ecommt[MAXCCHARS+1] = {ECOMMT}; /* end character for comment */
+int synch_lines = 0; /* line synchronisation for C preprocessor */
+int prefix_builtins = 0; /* -P option to prefix builtin keywords */
+int fatal_warnings = 0; /* -E option to exit on warnings */
+int quiet = 0; /* -Q option to silence warnings */
+int nesting_limit = -1; /* -L for nesting limit */
+const char *freeze = NULL; /* -F to freeze state */
+const char *reload = NULL; /* -R to reload state */
+#ifndef REAL_FREEZE
+FILE *freezef = NULL;
+int thawing = 0;
+#endif
+
+struct keyblk {
+ const char *knam; /* keyword name */
+ int ktyp; /* keyword type */
+};
+
+struct keyblk keywrds[] = { /* m4 keywords to be installed */
+ { "include", INCLTYPE },
+ { "sinclude", SINCTYPE },
+ { "define", DEFITYPE },
+ { "defn", DEFNTYPE },
+ { "divert", DIVRTYPE | NOARGS },
+ { "expr", EXPRTYPE },
+ { "eval", EXPRTYPE },
+ { "substr", SUBSTYPE },
+ { "ifelse", IFELTYPE },
+ { "ifdef", IFDFTYPE },
+ { "len", LENGTYPE },
+ { "incr", INCRTYPE },
+ { "decr", DECRTYPE },
+ { "dnl", DNLNTYPE | NOARGS },
+ { "changequote", CHNQTYPE | NOARGS },
+ { "changecom", CHNCTYPE | NOARGS },
+ { "index", INDXTYPE },
+#ifdef EXTENDED
+ { "paste", PASTTYPE },
+ { "spaste", SPASTYPE },
+ /* Newer extensions, needed to handle gnu-m4 scripts */
+ { "indir", INDIRTYPE},
+ { "builtin", BUILTINTYPE},
+ { "patsubst", PATSTYPE},
+ { "regexp", REGEXPTYPE},
+ { "esyscmd", ESYSCMDTYPE},
+ { "__file__", FILENAMETYPE | NOARGS},
+ { "__line__", LINETYPE | NOARGS},
+#endif
+ { "popdef", POPDTYPE },
+ { "pushdef", PUSDTYPE },
+ { "dumpdef", DUMPTYPE | NOARGS },
+ { "shift", SHIFTYPE | NOARGS },
+ { "translit", TRNLTYPE },
+ { "undefine", UNDFTYPE },
+ { "undivert", UNDVTYPE | NOARGS },
+ { "divnum", DIVNTYPE | NOARGS },
+ { "maketemp", MKTMTYPE },
+ { "errprint", ERRPTYPE | NOARGS },
+ { "m4wrap", M4WRTYPE | NOARGS },
+ { "m4exit", EXITTYPE | NOARGS },
+ { "syscmd", SYSCTYPE },
+ { "sysval", SYSVTYPE | NOARGS },
+ { "traceon", TRACEONTYPE | NOARGS },
+ { "traceoff", TRACEOFFTYPE | NOARGS },
+
+#if defined(unix) || defined(__unix__)
+ { "unix", SELFTYPE | NOARGS },
+#else
+#ifdef vms
+ { "vms", SELFTYPE | NOARGS },
+#endif
+#endif
+};
+
+#define MAXKEYS (sizeof(keywrds)/sizeof(struct keyblk))
+
+#define MAXRECORD 50
+static struct position {
+ char *name;
+ unsigned long line;
+} quotes[MAXRECORD], paren[MAXRECORD];
+
+static void record(struct position *, int);
+static void dump_stack(struct position *, int);
+
+static void macro(void);
+static void initkwds(void);
+static ndptr inspect(int, char *);
+static int do_look_ahead(int, const char *);
+static void reallyoutputstr(const char *);
+static void reallyputchar(int);
+
+static void enlarge_stack(void);
+static void help(void);
+
+static void
+usage(FILE *f)
+{
+ fprintf(f, "Usage: %s [-EGgiPQsv] [-Dname[=value]] [-d flags] "
+ "[-I dirname] [-o filename] [-L limit]\n"
+ "\t[-t macro] [-Uname] [file ...]\n", getprogname());
+}
+
+__dead static void
+onintr(int signo)
+{
+ char intrmessage[] = "m4: interrupted.\n";
+ write(STDERR_FILENO, intrmessage, sizeof(intrmessage)-1);
+ _exit(1);
+}
+
+#define OPT_HELP 1
+
+struct option longopts[] = {
+ { "debug", optional_argument, 0, 'd' },
+ { "define", required_argument, 0, 'D' },
+ { "error-output", required_argument, 0, 'e' },
+ { "fatal-warnings", no_argument, 0, 'E' },
+ { "freeze-state", required_argument, 0, 'F' },
+ { "gnu", no_argument, 0, 'g' },
+ { "help", no_argument, 0, OPT_HELP },
+ { "include", required_argument, 0, 'I' },
+ { "interactive", no_argument, 0, 'i' },
+ { "nesting-limit", required_argument, 0, 'L' },
+ { "prefix-builtins", no_argument, 0, 'P' },
+ { "quiet", no_argument, 0, 'Q' },
+ { "reload-state", required_argument, 0, 'R' },
+ { "silent", no_argument, 0, 'Q' },
+ { "synclines", no_argument, 0, 's' },
+ { "trace", required_argument, 0, 't' },
+ { "traditional", no_argument, 0, 'G' },
+ { "undefine", required_argument, 0, 'U' },
+ { "version", no_argument, 0, 'v' },
+#ifdef notyet
+ { "arglength", required_argument, 0, 'l' },
+ { "debugfile", optional_argument, 0, OPT_DEBUGFILE },
+ { "hashsize", required_argument, 0, 'H' },
+ { "warn-macro-sequence",optional_argument, 0, OPT_WARN_SEQUENCE },
+#endif
+ { 0, 0, 0, 0 },
+};
+
+int
+main(int argc, char *argv[])
+{
+ int c;
+ int n;
+ char *p;
+
+ setprogname(argv[0]);
+
+ if (signal(SIGINT, SIG_IGN) != SIG_IGN)
+ signal(SIGINT, onintr);
+
+ init_macros();
+ initspaces();
+ STACKMAX = INITSTACKMAX;
+
+ mstack = (stae *)xalloc(sizeof(stae) * STACKMAX, NULL);
+ sstack = (char *)xalloc(STACKMAX, NULL);
+
+ maxout = 0;
+ outfile = NULL;
+ resizedivs(MAXOUT);
+
+ while ((c = getopt_long(argc, argv, "D:d:e:EF:GgI:iL:o:PR:Qst:U:v",
+ longopts, NULL)) != -1)
+ switch(c) {
+ case 'D': /* define something..*/
+ for (p = optarg; *p; p++)
+ if (*p == '=')
+ break;
+ if (*p)
+ *p++ = EOS;
+ dodefine(optarg, p);
+ break;
+ case 'd':
+ set_trace_flags(optarg);
+ break;
+ case 'E':
+ fatal_warnings++;
+ break;
+ case 'e':
+ if (freopen(optarg, "w+", stderr) == NULL)
+ err(EXIT_FAILURE, "Can't redirect errors to `%s'",
+ optarg);
+ break;
+ case 'F':
+ freeze = optarg;
+#ifndef REAL_FREEZE
+ if ((freezef = fopen(freeze, "w")) == NULL)
+ err(EXIT_FAILURE, "Can't open `%s'", freeze);
+#endif
+ break;
+ case 'I':
+ addtoincludepath(optarg);
+ break;
+ case 'i':
+ setvbuf(stdout, NULL, _IONBF, 0);
+ signal(SIGINT, SIG_IGN);
+ break;
+ case 'G':
+ mimic_gnu = 0;
+ break;
+ case 'g':
+ mimic_gnu = 1;
+ break;
+ case 'L':
+ nesting_limit = atoi(optarg);
+ break;
+ case 'o':
+ trace_file(optarg);
+ break;
+ case 'P':
+ prefix_builtins = 1;
+ break;
+ case 'Q':
+ quiet++;
+ break;
+ case 'R':
+ reload = optarg;
+ break;
+ case 's':
+ synch_lines = 1;
+ break;
+ case 't':
+ mark_traced(optarg, 1);
+ break;
+ case 'U': /* undefine... */
+ macro_popdef(optarg);
+ break;
+ case 'v':
+ fprintf(stderr, "%s version %d\n", getprogname(),
+ VERSION);
+ return EXIT_SUCCESS;
+ case OPT_HELP:
+ help();
+ return EXIT_SUCCESS;
+ case '?':
+ default:
+ usage(stderr);
+ return EXIT_FAILURE;
+ }
+
+#ifdef REDIRECT
+ /*
+ * This is meant only for debugging; it makes all output
+ * go to a known file, even if the command line options
+ * send it elsewhere. It should not be turned of in production code.
+ */
+ if (freopen("/tmp/m4", "w+", stderr) == NULL)
+ err(EXIT_FAILURE, "Can't redirect errors to `%s'",
+ "/tmp/m4");
+#endif
+ argc -= optind;
+ argv += optind;
+
+
+ initkwds();
+ if (mimic_gnu)
+ setup_builtin("format", FORMATTYPE);
+
+ active = stdout; /* default active output */
+ bbase[0] = bufbase;
+
+ if (reload) {
+#ifdef REAL_FREEZE
+ thaw_state(reload);
+#else
+ if (fopen_trypath(infile, reload) == NULL)
+ err(1, "Can't open `%s'", reload);
+ sp = -1;
+ fp = 0;
+ thawing = 1;
+ macro();
+ thawing = 0;
+ release_input(infile);
+#endif
+ }
+
+ if (!argc) {
+ sp = -1; /* stack pointer initialized */
+ fp = 0; /* frame pointer initialized */
+ set_input(infile+0, stdin, "stdin");
+ /* default input (naturally) */
+ macro();
+ } else
+ for (; argc--; ++argv) {
+ p = *argv;
+ if (p[0] == '-' && p[1] == EOS)
+ set_input(infile, stdin, "stdin");
+ else if (fopen_trypath(infile, p) == NULL)
+ err(1, "%s", p);
+ sp = -1;
+ fp = 0;
+ macro();
+ release_input(infile);
+ }
+
+ if (wrapindex) {
+ int i;
+
+ ilevel = 0; /* in case m4wrap includes.. */
+ bufbase = bp = buf; /* use the entire buffer */
+ if (mimic_gnu) {
+ while (wrapindex != 0) {
+ for (i = 0; i < wrapindex; i++)
+ pbstr(m4wraps[i]);
+ wrapindex =0;
+ macro();
+ }
+ } else {
+ for (i = 0; i < wrapindex; i++) {
+ pbstr(m4wraps[i]);
+ macro();
+ }
+ }
+ }
+
+ if (active != stdout)
+ active = stdout; /* reset output just in case */
+ for (n = 1; n < maxout; n++) /* default wrap-up: undivert */
+ if (outfile[n] != NULL)
+ getdiv(n);
+ /* remove bitbucket if used */
+ if (outfile[0] != NULL) {
+ (void) fclose(outfile[0]);
+ }
+
+#ifdef REAL_FREEZE
+ if (freeze)
+ freeze_state(freeze);
+#else
+ if (freezef)
+ fclose(freezef);
+#endif
+
+ return 0;
+}
+
+/*
+ * Look ahead for `token'.
+ * (on input `t == token[0]')
+ * Used for comment and quoting delimiters.
+ * Returns 1 if `token' present; copied to output.
+ * 0 if `token' not found; all characters pushed back
+ */
+static int
+do_look_ahead(int t, const char *token)
+{
+ int i;
+
+ assert((unsigned char)t == (unsigned char)token[0]);
+
+ for (i = 1; *++token; i++) {
+ t = gpbc();
+ if (t == EOF || (unsigned char)t != (unsigned char)*token) {
+ pushback(t);
+ while (--i)
+ pushback(*--token);
+ return 0;
+ }
+ }
+ return 1;
+}
+
+#define LOOK_AHEAD(t, token) (t != EOF && \
+ (unsigned char)(t)==(unsigned char)(token)[0] && \
+ do_look_ahead(t,token))
+
+/*
+ * macro - the work horse..
+ */
+static void
+macro(void)
+{
+ char token[MAXTOK+1];
+ int t, l;
+ ndptr p;
+ int nlpar;
+
+ cycle {
+ t = gpbc();
+
+ if (LOOK_AHEAD(t,lquote)) { /* strip quotes */
+ nlpar = 0;
+ record(quotes, nlpar++);
+ /*
+ * Opening quote: scan forward until matching
+ * closing quote has been found.
+ */
+ do {
+
+ l = gpbc();
+ if (LOOK_AHEAD(l,rquote)) {
+ if (--nlpar > 0)
+ outputstr(rquote);
+ } else if (LOOK_AHEAD(l,lquote)) {
+ record(quotes, nlpar++);
+ outputstr(lquote);
+ } else if (l == EOF) {
+ if (!quiet) {
+ if (nlpar == 1)
+ warnx("unclosed quote:");
+ else
+ warnx(
+ "%d unclosed quotes:",
+ nlpar);
+ dump_stack(quotes, nlpar);
+ }
+ exit(EXIT_FAILURE);
+ } else {
+ if (nlpar > 0) {
+ if (sp < 0)
+ reallyputchar(l);
+ else
+ CHRSAVE(l);
+ }
+ }
+ }
+ while (nlpar != 0);
+ } else if (sp < 0 && LOOK_AHEAD(t, scommt)) {
+ reallyoutputstr(scommt);
+
+ for(;;) {
+ t = gpbc();
+ if (LOOK_AHEAD(t, ecommt)) {
+ reallyoutputstr(ecommt);
+ break;
+ }
+ if (t == EOF)
+ break;
+ reallyputchar(t);
+ }
+ } else if (t == '_' || isalpha(t)) {
+ p = inspect(t, token);
+ if (p != NULL)
+ pushback(l = gpbc());
+ if (p == NULL || (l != LPAREN &&
+ (macro_getdef(p)->type & NEEDARGS) != 0))
+ outputstr(token);
+ else {
+ /*
+ * real thing.. First build a call frame:
+ */
+ pushf(fp); /* previous call frm */
+ pushf(macro_getdef(p)->type); /* type of the call */
+ pushf(is_traced(p));
+ pushf(0); /* parenthesis level */
+ fp = sp; /* new frame pointer */
+ /*
+ * now push the string arguments:
+ */
+ pushs1(macro_getdef(p)->defn); /* defn string */
+ pushs1((char *)macro_name(p)); /* macro name */
+ pushs(ep); /* start next..*/
+
+ if (l != LPAREN && PARLEV == 0) {
+ /* no bracks */
+ chrsave(EOS);
+
+ if ((size_t)sp == STACKMAX)
+ errx(1, "internal stack overflow");
+ eval((const char **) mstack+fp+1, 2,
+ CALTYP, TRACESTATUS);
+
+ ep = PREVEP; /* flush strspace */
+ sp = PREVSP; /* previous sp.. */
+ fp = PREVFP; /* rewind stack...*/
+ }
+ }
+ } else if (t == EOF) {
+ if (sp > -1 && ilevel <= 0) {
+ if (!quiet) {
+ warnx("unexpected end of input, "
+ "unclosed parenthesis:");
+ dump_stack(paren, PARLEV);
+ }
+ exit(EXIT_FAILURE);
+ }
+ if (ilevel <= 0)
+ break; /* all done thanks.. */
+ release_input(infile+ilevel--);
+ emit_synchline();
+ bufbase = bbase[ilevel];
+ continue;
+ } else if (sp < 0) { /* not in a macro at all */
+ reallyputchar(t); /* output directly.. */
+ }
+
+ else switch(t) {
+
+ case LPAREN:
+ if (PARLEV > 0)
+ chrsave(t);
+ while (isspace(l = gpbc())) /* skip blank, tab, nl.. */
+ if (PARLEV > 0)
+ chrsave(l);
+ pushback(l);
+ record(paren, PARLEV++);
+ break;
+
+ case RPAREN:
+ if (--PARLEV > 0)
+ chrsave(t);
+ else { /* end of argument list */
+ chrsave(EOS);
+
+ if ((size_t)sp == STACKMAX)
+ errx(1, "internal stack overflow");
+
+ eval((const char **) mstack+fp+1, sp-fp,
+ CALTYP, TRACESTATUS);
+
+ ep = PREVEP; /* flush strspace */
+ sp = PREVSP; /* previous sp.. */
+ fp = PREVFP; /* rewind stack...*/
+ }
+ break;
+
+ case COMMA:
+ if (PARLEV == 1) {
+ chrsave(EOS); /* new argument */
+ while (isspace(l = gpbc()))
+ ;
+ pushback(l);
+ pushs(ep);
+ } else
+ chrsave(t);
+ break;
+
+ default:
+ if (LOOK_AHEAD(t, scommt)) {
+ char *q;
+ for (q = scommt; *q; q++)
+ chrsave(*q);
+ for(;;) {
+ t = gpbc();
+ if (LOOK_AHEAD(t, ecommt)) {
+ for (q = ecommt; *q; q++)
+ chrsave(*q);
+ break;
+ }
+ if (t == EOF)
+ break;
+ CHRSAVE(t);
+ }
+ } else
+ CHRSAVE(t); /* stack the char */
+ break;
+ }
+ }
+}
+
+/*
+ * output string directly, without pushing it for reparses.
+ */
+void
+outputstr(const char *s)
+{
+ if (sp < 0)
+ reallyoutputstr(s);
+ else
+ while (*s)
+ CHRSAVE(*s++);
+}
+
+void
+reallyoutputstr(const char *s)
+{
+ if (synch_lines) {
+ while (*s) {
+ fputc(*s, active);
+ if (*s++ == '\n') {
+ infile[ilevel].synch_lineno++;
+ if (infile[ilevel].synch_lineno !=
+ infile[ilevel].lineno)
+ do_emit_synchline();
+ }
+ }
+ } else
+ fputs(s, active);
+}
+
+void
+reallyputchar(int c)
+{
+ putc(c, active);
+ if (synch_lines && c == '\n') {
+ infile[ilevel].synch_lineno++;
+ if (infile[ilevel].synch_lineno != infile[ilevel].lineno)
+ do_emit_synchline();
+ }
+}
+
+/*
+ * build an input token..
+ * consider only those starting with _ or A-Za-z.
+ */
+static ndptr
+inspect(int c, char *tp)
+{
+ char *name = tp;
+ char *etp = tp+MAXTOK;
+ ndptr p;
+
+ *tp++ = c;
+
+ while ((isalnum(c = gpbc()) || c == '_') && tp < etp)
+ *tp++ = c;
+ if (c != EOF)
+ PUSHBACK(c);
+ *tp = EOS;
+ /* token is too long, it won't match anything, but it can still
+ * be output. */
+ if (tp == ep) {
+ outputstr(name);
+ while (isalnum(c = gpbc()) || c == '_') {
+ if (sp < 0)
+ reallyputchar(c);
+ else
+ CHRSAVE(c);
+ }
+ *name = EOS;
+ return NULL;
+ }
+
+ p = ohash_find(&macros, ohash_qlookupi(&macros, name, (void *)&tp));
+ if (p == NULL)
+ return NULL;
+ if (macro_getdef(p) == NULL)
+ return NULL;
+ return p;
+}
+
+/*
+ * initkwds - initialise m4 keywords as fast as possible.
+ * This very similar to install, but without certain overheads,
+ * such as calling lookup. Malloc is not used for storing the
+ * keyword strings, since we simply use the static pointers
+ * within keywrds block.
+ */
+static void
+initkwds(void)
+{
+ unsigned int type;
+ size_t i;
+
+ for (i = 0; i < MAXKEYS; i++) {
+ type = keywrds[i].ktyp;
+ if ((keywrds[i].ktyp & NOARGS) == 0)
+ type |= NEEDARGS;
+ setup_builtin(keywrds[i].knam, type);
+ }
+}
+
+static void
+record(struct position *t, int lev)
+{
+ if (lev < MAXRECORD) {
+ t[lev].name = CURRENT_NAME;
+ t[lev].line = CURRENT_LINE;
+ }
+}
+
+static void
+dump_stack(struct position *t, int lev)
+{
+ int i;
+
+ for (i = 0; i < lev; i++) {
+ if (i == MAXRECORD) {
+ fprintf(stderr, " ...\n");
+ break;
+ }
+ fprintf(stderr, " %s at line %lu\n",
+ t[i].name, t[i].line);
+ }
+}
+
+
+static void
+enlarge_stack(void)
+{
+ STACKMAX += STACKMAX/2;
+ mstack = xrealloc(mstack, sizeof(stae) * STACKMAX,
+ "Evaluation stack overflow (%lu)",
+ (unsigned long)STACKMAX);
+ sstack = xrealloc(sstack, STACKMAX,
+ "Evaluation stack overflow (%lu)",
+ (unsigned long)STACKMAX);
+}
+
+static const struct {
+ const char *n;
+ const char *d;
+} nd [] = {
+{ "-d, --debug[=flags]", "set debug flags" },
+{ "-D, --define=name[=value]", "define macro" },
+{ "-e, --error-output=file", "send error output to file" },
+{ "-E, --fatal-warnings", "exit on warnings" },
+{ "-F, --freeze-state=file", "save state to file" },
+{ "-g, --gnu", "enable gnu extensions" },
+{ " --help", "print this message and exit" },
+{ "-I, --include=file", "include file" },
+{ "-i, --interactive", "unbuffer output, ignore tty signals" },
+{ "-L, --nesting-limit=num", "macro expansion nesting limit (unimpl)" },
+{ "-P, --prefix-builtins", "prefix builtins with m4_" },
+{ "-Q, --quiet", "don't print warnings" },
+{ "-R, --reload-state=file", "restore state from file" },
+{ "-Q, --silent", "don't print warnings" },
+{ "-s, --synclines", "output line directives for cpp(1)" },
+{ "-t, --trace=macro", "trace the named macro" },
+{ "-G, --traditional", "disable gnu extensions" },
+{ "-U, --undefine=name", "undefine the named macro" },
+{ "-v, --version", "print the version number and exit" },
+};
+
+static void
+help(void)
+{
+ size_t i;
+ fprintf(stdout, "%s version %d\n\n", getprogname(), VERSION);
+ usage(stdout);
+
+ fprintf(stdout, "\nThe long options are:\n");
+ for (i = 0; i < __arraycount(nd); i++)
+ fprintf(stdout, "\t%-25.25s\t%s\n", nd[i].n, nd[i].d);
+}
diff --git a/usr.bin/m4/mdef.h b/usr.bin/m4/mdef.h
new file mode 100644
index 0000000..f80c208
--- /dev/null
+++ b/usr.bin/m4/mdef.h
@@ -0,0 +1,237 @@
+/* $OpenBSD: mdef.h,v 1.29 2006/03/20 20:27:45 espie Exp $ */
+/* $NetBSD: mdef.h,v 1.17 2016/01/16 18:31:29 christos Exp $ */
+
+/*
+ * Copyright (c) 1989, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Ozan Yigit at York University.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * @(#)mdef.h 8.1 (Berkeley) 6/6/93
+ */
+
+#ifdef __GNUC__
+# define UNUSED __attribute__((__unused__))
+#else
+# define UNUSED
+#endif
+
+#define MACRTYPE 1
+#define DEFITYPE 2
+#define EXPRTYPE 3
+#define SUBSTYPE 4
+#define IFELTYPE 5
+#define LENGTYPE 6
+#define CHNQTYPE 7
+#define SYSCTYPE 8
+#define UNDFTYPE 9
+#define INCLTYPE 10
+#define SINCTYPE 11
+#define PASTTYPE 12
+#define SPASTYPE 13
+#define INCRTYPE 14
+#define IFDFTYPE 15
+#define PUSDTYPE 16
+#define POPDTYPE 17
+#define SHIFTYPE 18
+#define DECRTYPE 19
+#define DIVRTYPE 20
+#define UNDVTYPE 21
+#define DIVNTYPE 22
+#define MKTMTYPE 23
+#define ERRPTYPE 24
+#define M4WRTYPE 25
+#define TRNLTYPE 26
+#define DNLNTYPE 27
+#define DUMPTYPE 28
+#define CHNCTYPE 29
+#define INDXTYPE 30
+#define SYSVTYPE 31
+#define EXITTYPE 32
+#define DEFNTYPE 33
+#define SELFTYPE 34
+#define INDIRTYPE 35
+#define BUILTINTYPE 36
+#define PATSTYPE 37
+#define FILENAMETYPE 38
+#define LINETYPE 39
+#define REGEXPTYPE 40
+#define ESYSCMDTYPE 41
+#define TRACEONTYPE 42
+#define TRACEOFFTYPE 43
+#define FORMATTYPE 44
+
+#define BUILTIN_MARKER "__builtin_"
+
+#define TYPEMASK 0xff /* Keep bits really corresponding to a type. */
+#define RECDEF 0x100 /* Pure recursive def, don't expand it */
+#define NOARGS 0x200 /* builtin needs no args */
+#define NEEDARGS 0x400 /* mark builtin that need args with this */
+
+/*
+ * m4 special characters
+ */
+
+#define ARGFLAG '$'
+#define LPAREN '('
+#define RPAREN ')'
+#define LQUOTE '`'
+#define RQUOTE '\''
+#define COMMA ','
+#define SCOMMT '#'
+#define ECOMMT '\n'
+
+#ifdef msdos
+#define system(str) (-1)
+#endif
+
+/*
+ * other important constants
+ */
+
+#define EOS '\0'
+#define MAXINP 10 /* maximum include files */
+#define MAXOUT 10 /* maximum # of diversions */
+#define BUFSIZE 4096 /* starting size of pushback buffer */
+#define INITSTACKMAX 4096 /* starting size of call stack */
+#define STRSPMAX 4096 /* starting size of string space */
+#define MAXTOK 512 /* maximum chars in a tokn */
+#define HASHSIZE 199 /* maximum size of hashtab */
+#define MAXCCHARS 5 /* max size of comment/quote delim */
+
+#define ALL 1
+#define TOP 0
+
+#ifndef TRUE
+#define TRUE 1
+#endif
+#ifndef FALSE
+#define FALSE 0
+#endif
+#define cycle for(;;)
+
+/*
+ * m4 data structures
+ */
+
+typedef struct ndblock *ndptr;
+
+struct macro_definition {
+ struct macro_definition *next;
+ char *defn; /* definition.. */
+ unsigned int type; /* type of the entry.. */
+};
+
+
+struct ndblock { /* hashtable structure */
+ unsigned int builtin_type;
+ unsigned int trace_flags;
+ struct macro_definition *d;
+ char name[1]; /* entry name.. */
+};
+
+typedef union { /* stack structure */
+ int sfra; /* frame entry */
+ char *sstr; /* string entry */
+} stae;
+
+struct input_file {
+ FILE *file;
+ char *name;
+ unsigned long lineno;
+ unsigned long synch_lineno; /* used for -s */
+ int c;
+};
+
+#define CURRENT_NAME (infile[ilevel].name)
+#define CURRENT_LINE (infile[ilevel].lineno)
+#define TOKEN_LINE(f) (f->lineno - (f->c == '\n' ? 1 : 0))
+
+/*
+ * macros for readibility and/or speed
+ *
+ * gpbc() - get a possibly pushed-back character
+ * pushf() - push a call frame entry onto stack
+ * pushs() - push a string pointer onto stack
+ */
+#define gpbc() (bp > bufbase) ? *--bp : obtain_char(infile+ilevel)
+#define pushf(x) \
+ do { \
+ if ((size_t)++sp == STACKMAX) \
+ enlarge_stack();\
+ mstack[sp].sfra = (x); \
+ sstack[sp] = 0; \
+ } while (0)
+
+#define pushs(x) \
+ do { \
+ if ((size_t)++sp == STACKMAX) \
+ enlarge_stack();\
+ mstack[sp].sstr = (x); \
+ sstack[sp] = 1; \
+ } while (0)
+
+#define pushs1(x) \
+ do { \
+ if ((size_t)++sp == STACKMAX) \
+ enlarge_stack();\
+ mstack[sp].sstr = (x); \
+ sstack[sp] = 0; \
+ } while (0)
+
+/*
+ * . .
+ * | . | <-- sp | . |
+ * +-------+ +-----+
+ * | arg 3 ----------------------->| str |
+ * +-------+ | . |
+ * | arg 2 ---PREVEP-----+ .
+ * +-------+ |
+ * . | | |
+ * +-------+ | +-----+
+ * | plev | PARLEV +-------->| str |
+ * +-------+ | . |
+ * | type | CALTYP .
+ * +-------+
+ * | prcf ---PREVFP--+
+ * +-------+ |
+ * | . | PREVSP |
+ * . |
+ * +-------+ |
+ * | <----------+
+ * +-------+
+ *
+ */
+#define PARLEV (mstack[fp].sfra)
+#define CALTYP (mstack[fp-2].sfra)
+#define TRACESTATUS (mstack[fp-1].sfra)
+#define PREVEP (mstack[fp+3].sstr)
+#define PREVSP (fp-4)
+#define PREVFP (mstack[fp-3].sfra)
+
+#define VERSION 20150116
diff --git a/usr.bin/m4/misc.c b/usr.bin/m4/misc.c
new file mode 100644
index 0000000..d7bc8cf
--- /dev/null
+++ b/usr.bin/m4/misc.c
@@ -0,0 +1,414 @@
+/* $OpenBSD: misc.c,v 1.41 2009/10/14 17:19:47 sthen Exp $ */
+/* $NetBSD: misc.c,v 1.24 2016/01/16 17:01:22 christos Exp $ */
+
+/*
+ * Copyright (c) 1989, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Ozan Yigit at York University.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+#if HAVE_NBTOOL_CONFIG_H
+#include "nbtool_config.h"
+#endif
+#include <sys/cdefs.h>
+__RCSID("$NetBSD: misc.c,v 1.24 2016/01/16 17:01:22 christos Exp $");
+#include <sys/types.h>
+#include <errno.h>
+#include <unistd.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <stddef.h>
+#include <string.h>
+#include <err.h>
+#include "mdef.h"
+#include "stdd.h"
+#include "extern.h"
+#include "pathnames.h"
+
+
+char *ep; /* first free char in strspace */
+static char *strspace; /* string space for evaluation */
+char *endest; /* end of string space */
+static size_t strsize = STRSPMAX;
+static size_t bufsize = BUFSIZE;
+
+unsigned char *buf; /* push-back buffer */
+unsigned char *bufbase; /* the base for current ilevel */
+unsigned char *bbase[MAXINP]; /* the base for each ilevel */
+unsigned char *bp; /* first available character */
+unsigned char *endpbb; /* end of push-back buffer */
+
+
+/*
+ * find the index of second str in the first str.
+ */
+ptrdiff_t
+indx(const char *s1, const char *s2)
+{
+ char *t;
+
+ t = strstr(s1, s2);
+ if (t == NULL)
+ return (-1);
+ else
+ return (t - s1);
+}
+/*
+ * pushback - push character back onto input
+ */
+void
+pushback(int c)
+{
+ if (c == EOF)
+ return;
+ if (bp >= endpbb)
+ enlarge_bufspace();
+ *bp++ = c;
+}
+
+/*
+ * pbstr - push string back onto input
+ * pushback is replicated to improve
+ * performance.
+ */
+void
+pbstr(const char *s)
+{
+ size_t n;
+
+ n = strlen(s);
+ while ((size_t)(endpbb - bp) <= n)
+ enlarge_bufspace();
+ while (n > 0)
+ *bp++ = s[--n];
+}
+
+/*
+ * pbnum - convert number to string, push back on input.
+ */
+void
+pbnum(int n)
+{
+ pbnumbase(n, 10, 0);
+}
+
+void
+pbnumbase(int n, int base, int d)
+{
+ static char digits[36] = "0123456789abcdefghijklmnopqrstuvwxyz";
+ int num;
+ int printed = 0;
+
+ if (base > 36)
+ m4errx(1, "base %d > 36: not supported.", base);
+
+ if (base < 2)
+ m4errx(1, "bad base %d for conversion.", base);
+
+ num = (n < 0) ? -n : n;
+ do {
+ pushback(digits[num % base]);
+ printed++;
+ }
+ while ((num /= base) > 0);
+
+ if (n < 0)
+ printed++;
+ while (printed++ < d)
+ pushback('0');
+
+ if (n < 0)
+ pushback('-');
+}
+
+/*
+ * pbunsigned - convert unsigned long to string, push back on input.
+ */
+void
+pbunsigned(unsigned long n)
+{
+ do {
+ pushback(n % 10 + '0');
+ }
+ while ((n /= 10) > 0);
+}
+
+void
+initspaces(void)
+{
+ int i;
+
+ strspace = xalloc(strsize+1, NULL);
+ ep = strspace;
+ endest = strspace+strsize;
+ buf = (unsigned char *)xalloc(bufsize, NULL);
+ bufbase = buf;
+ bp = buf;
+ endpbb = buf + bufsize;
+ for (i = 0; i < MAXINP; i++)
+ bbase[i] = buf;
+}
+
+void
+enlarge_strspace(void)
+{
+ char *newstrspace;
+ int i;
+
+ strsize *= 2;
+ newstrspace = malloc(strsize + 1);
+ if (!newstrspace)
+ errx(1, "string space overflow");
+ memcpy(newstrspace, strspace, strsize/2);
+ for (i = 0; i <= sp; i++)
+ if (sstack[i])
+ mstack[i].sstr = (mstack[i].sstr - strspace)
+ + newstrspace;
+ ep = (ep-strspace) + newstrspace;
+ free(strspace);
+ strspace = newstrspace;
+ endest = strspace + strsize;
+}
+
+void
+enlarge_bufspace(void)
+{
+ unsigned char *newbuf;
+ int i;
+
+ bufsize += bufsize/2;
+ newbuf = xrealloc(buf, bufsize, "too many characters pushed back");
+ for (i = 0; i < MAXINP; i++)
+ bbase[i] = (bbase[i]-buf)+newbuf;
+ bp = (bp-buf)+newbuf;
+ bufbase = (bufbase-buf)+newbuf;
+ buf = newbuf;
+ endpbb = buf+bufsize;
+}
+
+/*
+ * chrsave - put single char on string space
+ */
+void
+chrsave(int c)
+{
+ if (ep >= endest)
+ enlarge_strspace();
+ *ep++ = c;
+}
+
+/*
+ * read in a diversion file, and dispose it.
+ */
+void
+getdiv(int n)
+{
+ int c;
+
+ if (active == outfile[n])
+ m4errx(1, "undivert: diversion still active.");
+ rewind(outfile[n]);
+ while ((c = getc(outfile[n])) != EOF)
+ putc(c, active);
+ (void) fclose(outfile[n]);
+ outfile[n] = NULL;
+}
+
+/*
+ * killdiv - get rid of the diversion files
+ */
+void
+killdiv(void)
+{
+ int n;
+
+ for (n = 0; n < maxout; n++)
+ if (outfile[n] != NULL) {
+ (void) fclose(outfile[n]);
+ }
+}
+
+void
+m4errx(int exval, const char *fmt, ...)
+{
+ fprintf(stderr, "%s: ", getprogname());
+ fprintf(stderr, "%s at line %lu: ", CURRENT_NAME, CURRENT_LINE);
+ if (fmt != NULL) {
+ va_list ap;
+
+ va_start(ap, fmt);
+ vfprintf(stderr, fmt, ap);
+ va_end(ap);
+ }
+ fprintf(stderr, "\n");
+ exit(exval);
+}
+
+/*
+ * resizedivs: allocate more diversion files */
+void
+resizedivs(int n)
+{
+ int i;
+
+ outfile = (FILE **)xrealloc(outfile, sizeof(FILE *) * n,
+ "too many diverts %d", n);
+ for (i = maxout; i < n; i++)
+ outfile[i] = NULL;
+ maxout = n;
+}
+
+void *
+xalloc(size_t n, const char *fmt, ...)
+{
+ void *p = malloc(n);
+
+ if (p == NULL) {
+ if (fmt == NULL)
+ err(1, "malloc");
+ else {
+ va_list va;
+
+ va_start(va, fmt);
+ verr(1, fmt, va);
+ va_end(va);
+ }
+ }
+ return p;
+}
+
+void *
+xrealloc(void *old, size_t n, const char *fmt, ...)
+{
+ char *p = realloc(old, n);
+
+ if (p == NULL) {
+ free(old);
+ if (fmt == NULL)
+ err(1, "realloc");
+ else {
+ va_list va;
+
+ va_start(va, fmt);
+ verr(1, fmt, va);
+ va_end(va);
+ }
+ }
+ return p;
+}
+
+char *
+xstrdup(const char *s)
+{
+ char *p = strdup(s);
+ if (p == NULL)
+ err(1, "strdup");
+ return p;
+}
+
+int
+obtain_char(struct input_file *f)
+{
+ if (f->c == EOF)
+ return EOF;
+
+ f->c = fgetc(f->file);
+#ifndef REAL_FREEZE
+ if (freezef)
+ fputc(f->c, freezef);
+#endif
+ if (f->c == '\n')
+ f->lineno++;
+
+ return f->c;
+}
+
+void
+set_input(struct input_file *f, FILE *real, const char *name)
+{
+ f->file = real;
+ f->lineno = 1;
+ f->c = 0;
+ f->name = xstrdup(name);
+ emit_synchline();
+}
+
+void
+do_emit_synchline(void)
+{
+ fprintf(active, "#line %lu \"%s\"\n",
+ infile[ilevel].lineno, infile[ilevel].name);
+ infile[ilevel].synch_lineno = infile[ilevel].lineno;
+}
+
+void
+release_input(struct input_file *f)
+{
+ if (f->file != stdin)
+ fclose(f->file);
+ f->c = EOF;
+ /*
+ * XXX can't free filename, as there might still be
+ * error information pointing to it.
+ */
+}
+
+void
+doprintlineno(struct input_file *f)
+{
+ pbunsigned(TOKEN_LINE(f));
+}
+
+void
+doprintfilename(struct input_file *f)
+{
+ pbstr(rquote);
+ pbstr(f->name);
+ pbstr(lquote);
+}
+
+/*
+ * buffer_mark/dump_buffer: allows one to save a mark in a buffer,
+ * and later dump everything that was added since then to a file.
+ */
+size_t
+buffer_mark(void)
+{
+ return bp - buf;
+}
+
+
+void
+dump_buffer(FILE *f, size_t m)
+{
+ unsigned char *s;
+
+ for (s = bp; (size_t)(s - buf) > m;)
+ fputc(*--s, f);
+}
diff --git a/usr.bin/m4/parser.y b/usr.bin/m4/parser.y
new file mode 100644
index 0000000..f8256ef
--- /dev/null
+++ b/usr.bin/m4/parser.y
@@ -0,0 +1,86 @@
+%{
+/* $NetBSD: parser.y,v 1.3 2015/01/04 18:31:09 joerg Exp $ */
+/* $OpenBSD: parser.y,v 1.6 2008/08/21 21:00:14 espie Exp $ */
+/*
+ * Copyright (c) 2004 Marc Espie <espie@cvs.openbsd.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+#if HAVE_NBTOOL_CONFIG_H
+#include "nbtool_config.h"
+#endif
+#include <sys/cdefs.h>
+__RCSID("$NetBSD: parser.y,v 1.3 2015/01/04 18:31:09 joerg Exp $");
+#include <stdint.h>
+#define YYSTYPE int32_t
+extern int32_t end_result;
+extern int yylex(void);
+extern int yyerror(const char *);
+%}
+%token NUMBER
+%token ERROR
+%left LOR
+%left LAND
+%left '|'
+%left '^'
+%left '&'
+%left EQ NE
+%left '<' LE '>' GE
+%left LSHIFT RSHIFT
+%left '+' '-'
+%left '*' '/' '%'
+%right UMINUS UPLUS '!' '~'
+
+%%
+
+top : expr { end_result = $1; }
+ ;
+expr : expr '+' expr { $$ = $1 + $3; }
+ | expr '-' expr { $$ = $1 - $3; }
+ | expr '*' expr { $$ = $1 * $3; }
+ | expr '/' expr {
+ if ($3 == 0) {
+ yyerror("division by zero");
+ exit(1);
+ }
+ $$ = $1 / $3;
+ }
+ | expr '%' expr {
+ if ($3 == 0) {
+ yyerror("modulo zero");
+ exit(1);
+ }
+ $$ = $1 % $3;
+ }
+ | expr LSHIFT expr { $$ = $1 << $3; }
+ | expr RSHIFT expr { $$ = $1 >> $3; }
+ | expr '<' expr { $$ = $1 < $3; }
+ | expr '>' expr { $$ = $1 > $3; }
+ | expr LE expr { $$ = $1 <= $3; }
+ | expr GE expr { $$ = $1 >= $3; }
+ | expr EQ expr { $$ = $1 == $3; }
+ | expr NE expr { $$ = $1 != $3; }
+ | expr '&' expr { $$ = $1 & $3; }
+ | expr '^' expr { $$ = $1 ^ $3; }
+ | expr '|' expr { $$ = $1 | $3; }
+ | expr LAND expr { $$ = $1 && $3; }
+ | expr LOR expr { $$ = $1 || $3; }
+ | '(' expr ')' { $$ = $2; }
+ | '-' expr %prec UMINUS { $$ = -$2; }
+ | '+' expr %prec UPLUS { $$ = $2; }
+ | '!' expr { $$ = !$2; }
+ | '~' expr { $$ = ~$2; }
+ | NUMBER
+ ;
+%%
+
diff --git a/usr.bin/m4/pathnames.h b/usr.bin/m4/pathnames.h
new file mode 100644
index 0000000..ef788e0
--- /dev/null
+++ b/usr.bin/m4/pathnames.h
@@ -0,0 +1,40 @@
+/* $NetBSD: pathnames.h,v 1.15 2011/03/08 23:55:19 riz Exp $ */
+
+/*
+ * Copyright (c) 1989, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Ozan Yigit at York University.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * @(#)pathnames.h 8.1 (Berkeley) 6/6/93
+ */
+
+/*
+ * Definitions of diversion files.
+ */
+#define _PATH_DIVNAME "/tmp/m4.0XXXXXXXXXX" /* unix diversion files */
diff --git a/usr.bin/m4/stdd.h b/usr.bin/m4/stdd.h
new file mode 100644
index 0000000..0cbd681
--- /dev/null
+++ b/usr.bin/m4/stdd.h
@@ -0,0 +1,54 @@
+/* $NetBSD: stdd.h,v 1.4 2003/08/07 11:14:34 agc Exp $ */
+
+/*-
+ * Copyright (c) 1991, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Ozan Yigit at York University.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * @(#)stdd.h 8.1 (Berkeley) 6/6/93
+ */
+
+/*
+ * standard defines
+ */
+
+#define max(a,b) ((a) > (b)? (a): (b))
+#define min(a,b) ((a) < (b)? (a): (b))
+
+#define iswhite(c) ((c) == ' ' || (c) == '\t')
+
+/*
+ * STREQ is an optimised strcmp(a,b)==0
+ * STREQN is an optimised strncmp(a,b,n)==0; assumes n > 0
+ */
+#define STREQ(a, b) ((a)[0] == (b)[0] && strcmp(a, b) == 0)
+#define STREQN(a, b, n) ((a)[0] == (b)[0] && strncmp(a, b, n) == 0)
+
+#define YES 1
+#define NO 0
diff --git a/usr.bin/m4/tokenizer.l b/usr.bin/m4/tokenizer.l
new file mode 100644
index 0000000..fc4ebf1
--- /dev/null
+++ b/usr.bin/m4/tokenizer.l
@@ -0,0 +1,108 @@
+%{
+/* $NetBSD: tokenizer.l,v 1.6 2012/03/20 20:34:58 matt Exp $ */
+/* $OpenBSD: tokenizer.l,v 1.6 2008/08/21 21:00:14 espie Exp $ */
+/*
+ * Copyright (c) 2004 Marc Espie <espie@cvs.openbsd.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+#if HAVE_NBTOOL_CONFIG_H
+#include "nbtool_config.h"
+#endif
+#include "parser.h"
+__RCSID("$NetBSD: tokenizer.l,v 1.6 2012/03/20 20:34:58 matt Exp $");
+#include <stdlib.h>
+#include <errno.h>
+#include <stdint.h>
+#include <limits.h>
+
+extern int mimic_gnu;
+extern int32_t yylval;
+extern int yylex(void);
+extern int yywrap(void);
+
+int32_t number(void);
+int32_t parse_radix(void);
+
+%}
+
+%option nounput
+%option noinput
+
+delim [ \t\n]
+ws {delim}+
+hex 0[xX][0-9a-fA-F]+
+oct 0[0-7]*
+dec [1-9][0-9]*
+radix 0[rR][0-9]+:[0-9a-zA-Z]+
+
+%%
+{ws} {/* just skip it */}
+{hex}|{oct}|{dec} { yylval = number(); return(NUMBER); }
+{radix} { if (mimic_gnu) {
+ yylval = parse_radix(); return(NUMBER);
+ } else {
+ return(ERROR);
+ }
+ }
+"<=" { return(LE); }
+">=" { return(GE); }
+"<<" { return(LSHIFT); }
+">>" { return(RSHIFT); }
+"==" { return(EQ); }
+"!=" { return(NE); }
+"&&" { return(LAND); }
+"||" { return(LOR); }
+. { return yytext[0]; }
+%%
+
+int32_t
+number(void)
+{
+ long l;
+
+ errno = 0;
+ l = strtol(yytext, NULL, 0);
+ if (((l == LONG_MAX || l == LONG_MIN) && errno == ERANGE) ||
+ l > INT32_MAX || l < INT32_MIN) {
+ fprintf(stderr, "m4: numeric overflow in expr: %s\n", yytext);
+ }
+ return l;
+}
+
+int32_t
+parse_radix(void)
+{
+ long base;
+ char *next;
+ long l;
+
+ l = 0;
+ base = strtol(yytext+2, &next, 0);
+ if (base > 36 || next == NULL) {
+ fprintf(stderr, "m4: error in number %s\n", yytext);
+ } else {
+ next++;
+ while (*next != 0) {
+ if (*next >= '0' && *next <= '9')
+ l = base * l + *next - '0';
+ else if (*next >= 'a' && *next <= 'z')
+ l = base * l + *next - 'a' + 10;
+ else if (*next >= 'A' && *next <= 'Z')
+ l = base * l + *next - 'A' + 10;
+ next++;
+ }
+ }
+ return l;
+}
+
diff --git a/usr.bin/m4/trace.c b/usr.bin/m4/trace.c
new file mode 100644
index 0000000..bb9397f
--- /dev/null
+++ b/usr.bin/m4/trace.c
@@ -0,0 +1,203 @@
+/* $NetBSD: trace.c,v 1.8 2012/03/20 20:34:58 matt Exp $ */
+/* $OpenBSD: trace.c,v 1.15 2006/03/24 08:03:44 espie Exp $ */
+/*
+ * Copyright (c) 2001 Marc Espie.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE OPENBSD PROJECT AND CONTRIBUTORS
+ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE OPENBSD
+ * PROJECT OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+#if HAVE_NBTOOL_CONFIG_H
+#include "nbtool_config.h"
+#endif
+#include <sys/cdefs.h>
+__RCSID("$NetBSD: trace.c,v 1.8 2012/03/20 20:34:58 matt Exp $");
+
+#include <sys/types.h>
+#include <err.h>
+#include <stddef.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include "mdef.h"
+#include "stdd.h"
+#include "extern.h"
+
+FILE *traceout;
+
+#define TRACE_ARGS 1
+#define TRACE_EXPANSION 2
+#define TRACE_QUOTE 4
+#define TRACE_FILENAME 8
+#define TRACE_LINENO 16
+#define TRACE_CONT 32
+#define TRACE_ID 64
+#define TRACE_NEWFILE 128 /* not implemented yet */
+#define TRACE_INPUT 256 /* not implemented yet */
+
+static unsigned int letter_to_flag(int);
+static void print_header(struct input_file *);
+static int frame_level(void);
+
+
+unsigned int trace_flags = TRACE_QUOTE | TRACE_EXPANSION;
+
+void
+trace_file(const char *name)
+{
+
+ if (traceout && traceout != stderr)
+ fclose(traceout);
+ traceout = fopen(name, "w");
+ if (!traceout)
+ err(1, "can't open %s", name);
+}
+
+static unsigned int
+letter_to_flag(int c)
+{
+ switch(c) {
+ case 'a':
+ return TRACE_ARGS;
+ case 'e':
+ return TRACE_EXPANSION;
+ case 'q':
+ return TRACE_QUOTE;
+ case 'c':
+ return TRACE_CONT;
+ case 'x':
+ return TRACE_ID;
+ case 'f':
+ return TRACE_FILENAME;
+ case 'l':
+ return TRACE_LINENO;
+ case 'p':
+ return TRACE_NEWFILE;
+ case 'i':
+ return TRACE_INPUT;
+ case 't':
+ return TRACE_ALL;
+ case 'V':
+ return ~0;
+ default:
+ return 0;
+ }
+}
+
+void
+set_trace_flags(const char *s)
+{
+ char mode = 0;
+ unsigned int f = 0;
+
+ if (*s == '+' || *s == '-')
+ mode = *s++;
+ while (*s)
+ f |= letter_to_flag(*s++);
+ switch(mode) {
+ case 0:
+ trace_flags = f;
+ break;
+ case '+':
+ trace_flags |= f;
+ break;
+ case '-':
+ trace_flags &= ~f;
+ break;
+ }
+}
+
+static int
+frame_level(void)
+{
+ int level;
+ int framep;
+
+ for (framep = fp, level = 0; framep != 0;
+ level++,framep = mstack[framep-3].sfra)
+ ;
+ return level;
+}
+
+static void
+print_header(struct input_file *inp)
+{
+ fprintf(traceout, "m4trace:");
+ if (trace_flags & TRACE_FILENAME)
+ fprintf(traceout, "%s:", inp->name);
+ if (trace_flags & TRACE_LINENO)
+ fprintf(traceout, "%lu:", TOKEN_LINE(inp));
+ fprintf(traceout, " -%d- ", frame_level());
+ if (trace_flags & TRACE_ID)
+ fprintf(traceout, "id %lu: ", expansion_id);
+}
+
+size_t
+trace(const char *argv[], int argc, struct input_file *inp)
+{
+ if (!traceout)
+ traceout = stderr;
+ print_header(inp);
+ if (trace_flags & TRACE_CONT) {
+ fprintf(traceout, "%s ...\n", argv[1]);
+ print_header(inp);
+ }
+ fprintf(traceout, "%s", argv[1]);
+ if ((trace_flags & TRACE_ARGS) && argc > 2) {
+ char delim[3];
+ int i;
+
+ delim[0] = LPAREN;
+ delim[1] = EOS;
+ for (i = 2; i < argc; i++) {
+ fprintf(traceout, "%s%s%s%s", delim,
+ (trace_flags & TRACE_QUOTE) ? lquote : "",
+ argv[i],
+ (trace_flags & TRACE_QUOTE) ? rquote : "");
+ delim[0] = COMMA;
+ delim[1] = ' ';
+ delim[2] = EOS;
+ }
+ fprintf(traceout, "%c", RPAREN);
+ }
+ if (trace_flags & TRACE_CONT) {
+ fprintf(traceout, " -> ???\n");
+ print_header(inp);
+ fprintf(traceout, argc > 2 ? "%s(...)" : "%s", argv[1]);
+ }
+ if (trace_flags & TRACE_EXPANSION)
+ return buffer_mark();
+ else {
+ fprintf(traceout, "\n");
+ return SIZE_MAX;
+ }
+}
+
+void
+finish_trace(size_t mark)
+{
+ fprintf(traceout, " -> ");
+ if (trace_flags & TRACE_QUOTE)
+ fprintf(traceout, "%s", lquote);
+ dump_buffer(traceout, mark);
+ if (trace_flags & TRACE_QUOTE)
+ fprintf(traceout, "%s", rquote);
+ fprintf(traceout, "\n");
+}
diff --git a/usr.bin/make/Makefile.boot b/usr.bin/make/Makefile.boot
new file mode 100644
index 0000000..f5be09c
--- /dev/null
+++ b/usr.bin/make/Makefile.boot
@@ -0,0 +1,45 @@
+# $NetBSD: Makefile.boot,v 1.21 2014/02/24 07:23:44 skrll Exp $
+#
+# a very simple makefile...
+#
+# You only want to use this if you aren't running NetBSD.
+#
+# modify MACHINE and MACHINE_ARCH as appropriate for your target architecture
+#
+CC=gcc -O -g
+
+.c.o:
+ ${CC} ${CFLAGS} -c $< -o $@
+
+MACHINE=i386
+MACHINE_ARCH=i386
+# tested on HP-UX 10.20
+#MAKE_MACHINE=hppa
+#MAKE_MACHINE_ARCH=hppa
+CFLAGS= -DTARGET_MACHINE=\"${MACHINE}\" \
+ -DTARGET_MACHINE_ARCH=\"${MACHINE_ARCH}\" \
+ -DMAKE_MACHINE=\"${MACHINE}\"
+LIBS=
+
+OBJ=arch.o buf.o compat.o cond.o dir.o for.o hash.o job.o main.o make.o \
+ make_malloc.o parse.o str.o strlist.o suff.o targ.o trace.o var.o util.o
+
+LIBOBJ= lst.lib/lstAppend.o lst.lib/lstAtEnd.o lst.lib/lstAtFront.o \
+ lst.lib/lstClose.o lst.lib/lstConcat.o lst.lib/lstDatum.o \
+ lst.lib/lstDeQueue.o lst.lib/lstDestroy.o lst.lib/lstDupl.o \
+ lst.lib/lstEnQueue.o lst.lib/lstFind.o lst.lib/lstFindFrom.o \
+ lst.lib/lstFirst.o lst.lib/lstForEach.o lst.lib/lstForEachFrom.o \
+ lst.lib/lstInit.o lst.lib/lstInsert.o lst.lib/lstIsAtEnd.o \
+ lst.lib/lstIsEmpty.o lst.lib/lstLast.o lst.lib/lstMember.o \
+ lst.lib/lstNext.o lst.lib/lstOpen.o lst.lib/lstRemove.o \
+ lst.lib/lstReplace.o lst.lib/lstSucc.o lst.lib/lstPrev.o
+
+bmake: ${OBJ} ${LIBOBJ}
+# @echo 'make of make and make.0 started.'
+ ${CC} ${CFLAGS} ${OBJ} ${LIBOBJ} -o bmake ${LIBS}
+ @ls -l $@
+# nroff -h -man make.1 > make.0
+# @echo 'make of make and make.0 completed.'
+
+clean:
+ rm -f ${OBJ} ${LIBOBJ} ${PORTOBJ} bmake
diff --git a/usr.bin/make/PSD.doc/Makefile b/usr.bin/make/PSD.doc/Makefile
new file mode 100644
index 0000000..67702b8
--- /dev/null
+++ b/usr.bin/make/PSD.doc/Makefile
@@ -0,0 +1,10 @@
+# $NetBSD: Makefile,v 1.4 2014/07/05 19:22:43 dholland Exp $
+# @(#)Makefile 8.1 (Berkeley) 8/14/93
+
+SECTION=reference/ref1
+ARTICLE=make
+SRCS= tutorial.ms
+MACROS= -ms
+EXTRAHTMLFILES=make1.png make2.png
+
+.include <bsd.doc.mk>
diff --git a/usr.bin/make/PSD.doc/tutorial.ms b/usr.bin/make/PSD.doc/tutorial.ms
new file mode 100644
index 0000000..814a09a
--- /dev/null
+++ b/usr.bin/make/PSD.doc/tutorial.ms
@@ -0,0 +1,3794 @@
+.\" $NetBSD: tutorial.ms,v 1.13 2017/03/01 13:05:11 kre Exp $
+.\" Copyright (c) 1988, 1989, 1993
+.\" The Regents of the University of California. All rights reserved.
+.\"
+.\" This code is derived from software contributed to Berkeley by
+.\" Adam de Boor.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\" notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must reproduce the above copyright
+.\" notice, this list of conditions and the following disclaimer in the
+.\" documentation and/or other materials provided with the distribution.
+.\" 3. Neither the name of the University nor the names of its contributors
+.\" may be used to endorse or promote products derived from this software
+.\" without specific prior written permission.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+.\" ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+.\" SUCH DAMAGE.
+.\"
+.\" Copyright (c) 1988, 1989 by Adam de Boor
+.\" Copyright (c) 1989 by Berkeley Softworks
+.\"
+.\" This code is derived from software contributed to Berkeley by
+.\" Adam de Boor.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\" notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must reproduce the above copyright
+.\" notice, this list of conditions and the following disclaimer in the
+.\" documentation and/or other materials provided with the distribution.
+.\" 3. All advertising materials mentioning features or use of this software
+.\" must display the following acknowledgement:
+.\" This product includes software developed by the University of
+.\" California, Berkeley and its contributors.
+.\" 4. Neither the name of the University nor the names of its contributors
+.\" may be used to endorse or promote products derived from this software
+.\" without specific prior written permission.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+.\" ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+.\" SUCH DAMAGE.
+.\"
+.\" @(#)tutorial.ms 8.1 (Berkeley) 8/18/93
+.\"
+.EH 'PSD:12-%''PMake \*- A Tutorial'
+.OH 'PMake \*- A Tutorial''PSD:12-%'
+.\" Ix is an indexing macro similar to .IX but I've disabled it for now
+.\" Since that would require 2 passes and I am not in the mood for that.
+.de Ix
+..
+.\" Rd is section (region) define and Rm is region mention? Again disable for
+.\" now.
+.de Rd
+..
+.de Rm
+..
+.\" xH is a macro to provide numbered headers that are automatically stuffed
+.\" into a table-of-contents, properly indented, etc. If the first argument
+.\" is numeric, it is taken as the depth for numbering (as for .NH), else
+.\" the default (1) is assumed.
+.\"
+.\" @P The initial paragraph distance.
+.\" @Q The piece of section number to increment (or 0 if none given)
+.\" @R Section header.
+.\" @S Indent for toc entry
+.\" @T Argument to NH (can't use @Q b/c giving 0 to NH resets the counter)
+.de xH
+.NH \\$1
+\\$2 \\$3 \\$4 \\$5 \\$6 \\$7 \\$8 \\$9
+.nr PD .1v
+.XS \\n%
+.ta 0.6i
+\\*(SN \\$2 \\$3 \\$4 \\$5 \\$6 \\$7 \\$8 \\$9
+.XE
+.nr PD .3v
+..
+.\" CW is used to place a string in fixed-width or switch to a
+.\" fixed-width font.
+.\" C is a typewriter font for a laserwriter. Use something else if
+.\" you don't have one...
+.de CW
+.ie !\\n(.$ .ft C
+.el \&\\$3\fC\\$1\fP\\$2
+..
+.\" Anything I put in a display I want to be in fixed-width
+.am DS
+.CW
+..
+.\" The stuff in .No produces a little stop sign in the left margin
+.\" that says NOTE in it. Unfortunately, it does cause a break, but
+.\" hey. Can't have everything. In case you're wondering how I came
+.\" up with such weird commands, they came from running grn on a
+.\" gremlin file...
+.de No
+.br
+.ne 0.5i
+.ie n \{\
+.nr g3 \w'NOTE '
+.po -\\n(g3u
+.br
+NOTE
+.br
+.po +\\n(g3u
+.\}
+.el \{\
+.po -0.5i
+.br
+.mk
+.nr g3 \\n(.f
+.nr g4 \\n(.s
+.sp -1
+.\" .st cf
+\D't 5u'
+.sp -1
+\h'50u'
+.sp -1
+\D't 3u'
+.sp -1
+.sp 7u
+\h'53u'
+\d\D'p -0.19i 0.0i 0.0i -0.13i 0.30i 0.0i 0.0i 0.13i'
+.sp -1
+.ft R
+.ps 6
+.nr g8 \\n(.d
+.ds g9 "NOTE
+.sp 74u
+\h'85u'\v'0.85n'\h-\w\\*(g9u/2u\&\\*(g9
+.sp |\\n(g8u
+.sp 166u
+\D't 3u'
+.br
+.po
+.rt
+.ft \\n(g3
+.ps \\n(g4
+.\}
+..
+.de Bp
+.ie !\\n(.$ .IP \(bu 2
+.el .IP "\&" 2
+..
+.ie n .po +\w'NOTE 'u
+.el .po +.3i
+.TL
+PMake \*- A Tutorial
+.AU
+Adam de Boor
+.AI
+Berkeley Softworks
+2150 Shattuck Ave, Penthouse
+Berkeley, CA 94704
+adam@bsw.uu.net
+\&...!uunet!bsw!adam
+.FS
+Permission to use, copy, modify, and distribute this software and its
+documentation for any purpose and without fee is hereby granted,
+provided that the above copyright notice appears in all copies.
+The University of California, Berkeley Softworks, and Adam de Boor make no
+representations about the suitability of this software for any
+purpose. It is provided "as is" without express or implied warranty.
+.FE
+.PP
+.xH 1 Introduction
+.LP
+PMake is a program for creating other programs, or anything else you
+can think of for it to do. The basic idea behind PMake is that, for
+any given system, be it a program or a document or whatever, there
+will be some files that depend on the state of other files (on when
+they were last modified). PMake takes these dependencies, which you
+must specify, and uses them to build whatever it is you want it to
+build.
+.LP
+PMake is almost fully-compatible with Make, with which you may already
+be familiar. PMake's most important feature is its ability to run
+several different jobs at once, making the creation of systems
+considerably faster. It also has a great deal more functionality than
+Make. Throughout the text, whenever something is mentioned that is an
+important difference between PMake and Make (i.e. something that will
+cause a makefile to fail if you don't do something about it), or is
+simply important, it will be flagged with a little sign in the left
+margin, like this:
+.No
+.LP
+This tutorial is divided into three main sections corresponding to basic,
+intermediate and advanced PMake usage. If you already know Make well,
+you will only need to skim chapter 2 (there are some aspects of
+PMake that I consider basic to its use that didn't exist in Make).
+Things in chapter 3 make life much easier, while those in chapter 4
+are strictly for those who know what they are doing. Chapter 5 has
+definitions for the jargon I use and chapter 6 contains possible
+solutions to the problems presented throughout the tutorial.
+.xH 1 The Basics of PMake
+.LP
+PMake takes as input a file that tells a) which files depend on which
+other files to be complete and b) what to do about files that are
+``out-of-date.'' This file is known as a ``makefile'' and is usually
+.Ix 0 def makefile
+kept in the top-most directory of the system to be built. While you
+can call the makefile anything you want, PMake will look for
+.CW Makefile
+and
+.CW makefile
+(in that order) in the current directory if you don't tell it
+otherwise.
+.Ix 0 def makefile default
+To specify a different makefile, use the
+.B \-f
+flag (e.g.
+.CW "pmake -f program.mk" ''). ``
+.Ix 0 ref flags -f
+.Ix 0 ref makefile other
+.LP
+A makefile has four different types of lines in it:
+.RS
+.IP \(bu 2
+File dependency specifications
+.IP \(bu 2
+Creation commands
+.IP \(bu 2
+Variable assignments
+.IP \(bu 2
+Comments, include statements and conditional directives
+.RE
+.LP
+Any line may be continued over multiple lines by ending it with a
+backslash.
+.Ix 0 def "continuation line"
+The backslash, following newline and any initial whitespace
+on the following line are compressed into a single space before the
+input line is examined by PMake.
+.xH 2 Dependency Lines
+.LP
+As mentioned in the introduction, in any system, there are
+dependencies between the files that make up the system. For instance,
+in a program made up of several C source files and one header file,
+the C files will need to be re-compiled should the header file be
+changed. For a document of several chapters and one macro file, the
+chapters will need to be reprocessed if any of the macros changes.
+.Ix 0 def "dependency"
+These are dependencies and are specified by means of dependency lines in
+the makefile.
+.LP
+.Ix 0 def "dependency line"
+On a dependency line, there are targets and sources, separated by a
+one- or two-character operator.
+The targets ``depend'' on the sources and are usually created from
+them.
+.Ix 0 def target
+.Ix 0 def source
+.Ix 0 ref operator
+Any number of targets and sources may be specified on a dependency line.
+All the targets in the line are made to depend on all the sources.
+Targets and sources need not be actual files, but every source must be
+either an actual file or another target in the makefile.
+If you run out of room, use a backslash at the end of the line to continue onto
+the next one.
+.LP
+Any file may be a target and any file may be a source, but the
+relationship between the two (or however many) is determined by the
+``operator'' that separates them.
+.Ix 0 def operator
+Three types of operators exist: one specifies that the datedness of a
+target is determined by the state of its sources, while another
+specifies other files (the sources) that need to be dealt with before
+the target can be re-created. The third operator is very similar to
+the first, with the additional condition that the target is
+out-of-date if it has no sources. These operations are represented by
+the colon, the exclamation point and the double-colon, respectively, and are
+mutually exclusive. Their exact semantics are as follows:
+.IP ":"
+.Ix 0 def operator colon
+.Ix 0 def :
+If a colon is used, a target on the line is considered to be
+``out-of-date'' (and in need of creation) if
+.RS
+.IP \(bu 2
+any of the sources has been modified more recently than the target, or
+.IP \(bu 2
+the target doesn't exist.
+.RE
+.Ix 0 def out-of-date
+.IP "\&"
+Under this operation, steps will be taken to re-create the target only
+if it is found to be out-of-date by using these two rules.
+.IP "!"
+.Ix 0 def operator force
+.Ix 0 def !
+If an exclamation point is used, the target will always be re-created,
+but this will not happen until all of its sources have been examined
+and re-created, if necessary.
+.IP "::"
+.Ix 0 def operator double-colon
+.Ix 0 def ::
+If a double-colon is used, a target is out-of-date if:
+.RS
+.IP \(bu 2
+any of the sources has been modified more recently than the target, or
+.IP \(bu 2
+the target doesn't exist, or
+.IP \(bu 2
+the target has no sources.
+.RE
+.IP "\&"
+If the target is out-of-date according to these rules, it will be re-created.
+This operator also does something else to the targets, but I'll go
+into that in the next section (``Shell Commands'').
+.LP
+Enough words, now for an example. Take that C program I mentioned
+earlier. Say there are three C files
+.CW a.c , (
+.CW b.c
+and
+.CW c.c )
+each of which
+includes the file
+.CW defs.h .
+The dependencies between the files could then be expressed as follows:
+.DS
+program : a.o b.o c.o
+a.o b.o c.o : defs.h
+a.o : a.c
+b.o : b.c
+c.o : c.c
+.DE
+.LP
+You may be wondering at this point, where
+.CW a.o ,
+.CW b.o
+and
+.CW c.o
+came in and why
+.I they
+depend on
+.CW defs.h
+and the C files don't. The reason is quite simple:
+.CW program
+cannot be made by linking together .c files \*- it must be
+made from .o files. Likewise, if you change
+.CW defs.h ,
+it isn't the .c files that need to be re-created, it's the .o files.
+If you think of dependencies in these terms \*- which files (targets)
+need to be created from which files (sources) \*- you should have no problems.
+.LP
+An important thing to notice about the above example, is that all the
+\&.o files appear as targets on more than one line. This is perfectly
+all right: the target is made to depend on all the sources mentioned
+on all the dependency lines. E.g.
+.CW a.o
+depends on both
+.CW defs.h
+and
+.CW a.c .
+.Ix 0 ref dependency
+.No
+.LP
+The order of the dependency lines in the makefile is
+important: the first target on the first dependency line in the
+makefile will be the one that gets made if you don't say otherwise.
+That's why
+.CW program
+comes first in the example makefile, above.
+.LP
+Both targets and sources may contain the standard C-Shell wildcard
+characters
+.CW { , (
+.CW } ,
+.CW * ,
+.CW ? ,
+.CW [ ,
+and
+.CW ] ),
+but the non-curly-brace ones may only appear in the final component
+(the file portion) of the target or source. The characters mean the
+following things:
+.IP \fB{}\fP
+These enclose a comma-separated list of options and cause the pattern
+to be expanded once for each element of the list. Each expansion
+contains a different element. For example,
+.CW src/{whiffle,beep,fish}.c
+expands to the three words
+.CW src/whiffle.c ,
+.CW src/beep.c ,
+and
+.CW src/fish.c .
+These braces may be nested and, unlike the other wildcard characters,
+the resulting words need not be actual files. All other wildcard
+characters are expanded using the files that exist when PMake is
+started.
+.IP \fB*\fP
+This matches zero or more characters of any sort.
+.CW src/*.c
+will expand to the same three words as above as long as
+.CW src
+contains those three files (and no other files that end in
+.CW .c ).
+.IP \fB?\fP
+Matches any single character.
+.IP \fB[]\fP
+This is known as a character class and contains either a list of
+single characters, or a series of character ranges
+.CW a-z , (
+for example means all characters between a and z), or both. It matches
+any single character contained in the list. E.g.
+.CW [A-Za-z]
+will match all letters, while
+.CW [0123456789]
+will match all numbers.
+.xH 2 Shell Commands
+.LP
+``Isn't that nice,'' you say to yourself, ``but how are files
+actually `re-created,' as he likes to spell it?''
+The re-creation is accomplished by commands you place in the makefile.
+These commands are passed to the Bourne shell (better known as
+``/bin/sh'') to be executed and are
+.Ix 0 ref shell
+.Ix 0 ref re-creation
+.Ix 0 ref update
+expected to do what's necessary to update the target file (PMake
+doesn't actually check to see if the target was created. It just
+assumes it's there).
+.Ix 0 ref target
+.LP
+Shell commands in a makefile look a lot like shell commands you would
+type at a terminal, with one important exception: each command in a
+makefile
+.I must
+be preceded by at least one tab.
+.LP
+Each target has associated with it a shell script made up of
+one or more of these shell commands. The creation script for a target
+should immediately follow the dependency line for that target. While
+any given target may appear on more than one dependency line, only one
+of these dependency lines may be followed by a creation script, unless
+the `::' operator was used on the dependency line.
+.Ix 0 ref operator double-colon
+.Ix 0 ref ::
+.No
+.LP
+If the double-colon was used, each dependency line for the target
+may be followed by a shell script. That script will only be executed
+if the target on the associated dependency line is out-of-date with
+respect to the sources on that line, according to the rules I gave
+earlier.
+I'll give you a good example of this later on.
+.LP
+To expand on the earlier makefile, you might add commands as follows:
+.DS
+program : a.o b.o c.o
+ cc a.o b.o c.o \-o program
+a.o b.o c.o : defs.h
+a.o : a.c
+ cc \-c a.c
+b.o : b.c
+ cc \-c b.c
+c.o : c.c
+ cc \-c c.c
+.DE
+.LP
+Something you should remember when writing a makefile is, the
+commands will be executed if the
+.I target
+on the dependency line is out-of-date, not the sources.
+.Ix 0 ref target
+.Ix 0 ref source
+.Ix 0 ref out-of-date
+In this example, the command
+.CW "cc \-c a.c" '' ``
+will be executed if
+.CW a.o
+is out-of-date. Because of the `:' operator,
+.Ix 0 ref :
+.Ix 0 ref operator colon
+this means that should
+.CW a.c
+.I or
+.CW defs.h
+have been modified more recently than
+.CW a.o ,
+the command will be executed
+.CW a.o "\&" (
+will be considered out-of-date).
+.Ix 0 ref out-of-date
+.LP
+Remember how I said the only difference between a makefile shell
+command and a regular shell command was the leading tab? I lied. There
+is another way in which makefile commands differ from regular ones.
+The first two characters after the initial whitespace are treated
+specially.
+If they are any combination of `@' and `\-', they cause PMake to do
+different things.
+.LP
+In most cases, shell commands are printed before they're
+actually executed. This is to keep you informed of what's going on. If
+an `@' appears, however, this echoing is suppressed. In the case of an
+.CW echo
+command, say
+.CW "echo Linking index" ,'' ``
+it would be
+rather silly to see
+.DS
+echo Linking index
+Linking index
+.DE
+.LP
+so PMake allows you to place an `@' before the command
+.CW "@echo Linking index" '') (``
+to prevent the command from being printed.
+.LP
+The other special character is the `\-'. In case you didn't know,
+shell commands finish with a certain ``exit status.'' This status is
+made available by the operating system to whatever program invoked the
+command. Normally this status will be 0 if everything went ok and
+non-zero if something went wrong. For this reason, PMake will consider
+an error to have occurred if one of the shells it invokes returns a non-zero
+status. When it detects an error, PMake's usual action is to abort
+whatever it's doing and exit with a non-zero status itself (any other
+targets that were being created will continue being made, but nothing
+new will be started. PMake will exit after the last job finishes).
+This behavior can be altered, however, by placing a `\-' at the front
+of a command
+.CW "\-mv index index.old" ''), (``
+certain command-line arguments,
+or doing other things, to be detailed later. In such
+a case, the non-zero status is simply ignored and PMake keeps chugging
+along.
+.No
+.LP
+Because all the commands are given to a single shell to execute, such
+things as setting shell variables, changing directories, etc., last
+beyond the command in which they are found. This also allows shell
+compound commands (like
+.CW for
+loops) to be entered in a natural manner.
+Since this could cause problems for some makefiles that depend on
+each command being executed by a single shell, PMake has a
+.B \-B
+.Ix 0 ref compatibility
+.Ix 0 ref flags -B
+flag (it stands for backwards-compatible) that forces each command to
+be given to a separate shell. It also does several other things, all
+of which I discourage since they are now old-fashioned.\|.\|.\|.
+.No
+.LP
+A target's shell script is fed to the shell on its (the shell's) input stream.
+This means that any commands, such as
+.CW ci
+that need to get input from the terminal won't work right \*- they'll
+get the shell's input, something they probably won't find to their
+liking. A simple way around this is to give a command like this:
+.DS
+ci $(SRCS) < /dev/tty
+.DE
+This would force the program's input to come from the terminal. If you
+can't do this for some reason, your only other alternative is to use
+PMake in its fullest compatibility mode. See
+.B Compatibility
+in chapter 4.
+.Ix 0 ref compatibility
+.LP
+.xH 2 Variables
+.LP
+PMake, like Make before it, has the ability to save text in variables
+to be recalled later at your convenience. Variables in PMake are used
+much like variables in the shell and, by tradition, consist of
+all upper-case letters (you don't
+.I have
+to use all upper-case letters.
+In fact there's nothing to stop you from calling a variable
+.CW @^&$%$ .
+Just tradition). Variables are assigned-to using lines of the form
+.Ix 0 def variable assignment
+.DS
+VARIABLE = value
+.DE
+.Ix 0 def variable assignment
+appended-to by
+.DS
+VARIABLE += value
+.DE
+.Ix 0 def variable appending
+.Ix 0 def variable assignment appended
+.Ix 0 def +=
+conditionally assigned-to (if the variable isn't already defined) by
+.DS
+VARIABLE ?= value
+.DE
+.Ix 0 def variable assignment conditional
+.Ix 0 def ?=
+and assigned-to with expansion (i.e. the value is expanded (see below)
+before being assigned to the variable\*-useful for placing a value at
+the beginning of a variable, or other things) by
+.DS
+VARIABLE := value
+.DE
+.Ix 0 def variable assignment expanded
+.Ix 0 def :=
+.LP
+Any whitespace before
+.I value
+is stripped off. When appending, a space is placed between the old
+value and the stuff being appended.
+.LP
+The final way a variable may be assigned to is using
+.DS
+VARIABLE != shell-command
+.DE
+.Ix 0 def variable assignment shell-output
+.Ix 0 def !=
+In this case,
+.I shell-command
+has all its variables expanded (see below) and is passed off to a
+shell to execute. The output of the shell is then placed in the
+variable. Any newlines (other than the final one) are replaced by
+spaces before the assignment is made. This is typically used to find
+the current directory via a line like:
+.DS
+CWD != pwd
+.DE
+.LP
+.B Note:
+this is intended to be used to execute commands that produce small amounts
+of output (e.g. ``pwd''). The implementation is less than intelligent and will
+likely freeze if you execute something that produces thousands of
+bytes of output (8 Kb is the limit on many UNIX systems).
+.LP
+The value of a variable may be retrieved by enclosing the variable
+name in parentheses or curly braces and preceding the whole thing
+with a dollar sign.
+.LP
+For example, to set the variable CFLAGS to the string
+.CW "\-I/sprite/src/lib/libc \-O" ,'' ``
+you would place a line
+.DS
+CFLAGS = \-I/sprite/src/lib/libc \-O
+.DE
+in the makefile and use the word
+.CW "$(CFLAGS)"
+wherever you would like the string
+.CW "\-I/sprite/src/lib/libc \-O"
+to appear. This is called variable expansion.
+.Ix 0 def variable expansion
+.No
+.LP
+Unlike Make, PMake will not expand a variable unless it knows
+the variable exists. E.g. if you have a
+.CW "${i}"
+in a shell command and you have not assigned a value to the variable
+.CW i
+(the empty string is considered a value, by the way), where Make would have
+substituted the empty string, PMake will leave the
+.CW "${i}"
+alone.
+To keep PMake from substituting for a variable it knows, precede the
+dollar sign with another dollar sign.
+(e.g. to pass
+.CW "${HOME}"
+to the shell, use
+.CW "$${HOME}" ).
+This causes PMake, in effect, to expand the
+.CW $
+macro, which expands to a single
+.CW $ .
+For compatibility, Make's style of variable expansion will be used
+if you invoke PMake with any of the compatibility flags (\c
+.B \-V ,
+.B \-B
+or
+.B \-M .
+The
+.B \-V
+flag alters just the variable expansion).
+.Ix 0 ref flags -V
+.Ix 0 ref flags -B
+.Ix 0 ref flags -M
+.Ix 0 ref compatibility
+.LP
+.Ix 0 ref variable expansion
+There are two different times at which variable expansion occurs:
+When parsing a dependency line, the expansion occurs immediately
+upon reading the line. If any variable used on a dependency line is
+undefined, PMake will print a message and exit.
+Variables in shell commands are expanded when the command is
+executed.
+Variables used inside another variable are expanded whenever the outer
+variable is expanded (the expansion of an inner variable has no effect
+on the outer variable. I.e. if the outer variable is used on a dependency
+line and in a shell command, and the inner variable changes value
+between when the dependency line is read and the shell command is
+executed, two different values will be substituted for the outer
+variable).
+.Ix 0 def variable types
+.LP
+Variables come in four flavors, though they are all expanded the same
+and all look about the same. They are (in order of expanding scope):
+.RS
+.IP \(bu 2
+Local variables.
+.Ix 0 ref variable local
+.IP \(bu 2
+Command-line variables.
+.Ix 0 ref variable command-line
+.IP \(bu 2
+Global variables.
+.Ix 0 ref variable global
+.IP \(bu 2
+Environment variables.
+.Ix 0 ref variable environment
+.RE
+.LP
+The classification of variables doesn't matter much, except that the
+classes are searched from the top (local) to the bottom (environment)
+when looking up a variable. The first one found wins.
+.xH 3 Local Variables
+.LP
+.Ix 0 def variable local
+Each target can have as many as seven local variables. These are
+variables that are only ``visible'' within that target's shell script
+and contain such things as the target's name, all of its sources (from
+all its dependency lines), those sources that were out-of-date, etc.
+Four local variables are defined for all targets. They are:
+.RS
+.IP ".TARGET"
+.Ix 0 def variable local .TARGET
+.Ix 0 def .TARGET
+The name of the target.
+.IP ".OODATE"
+.Ix 0 def variable local .OODATE
+.Ix 0 def .OODATE
+The list of the sources for the target that were considered out-of-date.
+The order in the list is not guaranteed to be the same as the order in
+which the dependencies were given.
+.IP ".ALLSRC"
+.Ix 0 def variable local .ALLSRC
+.Ix 0 def .ALLSRC
+The list of all sources for this target in the order in which they
+were given.
+.IP ".PREFIX"
+.Ix 0 def variable local .PREFIX
+.Ix 0 def .PREFIX
+The target without its suffix and without any leading path. E.g. for
+the target
+.CW ../../lib/compat/fsRead.c ,
+this variable would contain
+.CW fsRead .
+.RE
+.LP
+Three other local variables are set only for certain targets under
+special circumstances. These are the ``.IMPSRC,''
+.Ix 0 ref variable local .IMPSRC
+.Ix 0 ref .IMPSRC
+``.ARCHIVE,''
+.Ix 0 ref variable local .ARCHIVE
+.Ix 0 ref .ARCHIVE
+and ``.MEMBER''
+.Ix 0 ref variable local .MEMBER
+.Ix 0 ref .MEMBER
+variables. When they are set and how they are used is described later.
+.LP
+Four of these variables may be used in sources as well as in shell
+scripts.
+.Ix 0 def "dynamic source"
+.Ix 0 def source dynamic
+These are ``.TARGET'', ``.PREFIX'', ``.ARCHIVE'' and ``.MEMBER''. The
+variables in the sources are expanded once for each target on the
+dependency line, providing what is known as a ``dynamic source,''
+.Rd 0
+allowing you to specify several dependency lines at once. For example,
+.DS
+$(OBJS) : $(.PREFIX).c
+.DE
+will create a dependency between each object file and its
+corresponding C source file.
+.xH 3 Command-line Variables
+.LP
+.Ix 0 def variable command-line
+Command-line variables are set when PMake is first invoked by giving a
+variable assignment as one of the arguments. For example,
+.DS
+pmake "CFLAGS = -I/sprite/src/lib/libc -O"
+.DE
+would make
+.CW CFLAGS
+be a command-line variable with the given value. Any assignments to
+.CW CFLAGS
+in the makefile will have no effect, because once it
+is set, there is (almost) nothing you can do to change a command-line
+variable (the search order, you see). Command-line variables may be
+set using any of the four assignment operators, though only
+.CW =
+and
+.CW ?=
+behave as you would expect them to, mostly because assignments to
+command-line variables are performed before the makefile is read, thus
+the values set in the makefile are unavailable at the time.
+.CW +=
+.Ix 0 ref +=
+.Ix 0 ref variable assignment appended
+is the same as
+.CW = ,
+because the old value of the variable is sought only in the scope in
+which the assignment is taking place (for reasons of efficiency that I
+won't get into here).
+.CW :=
+and
+.CW ?=
+.Ix 0 ref :=
+.Ix 0 ref ?=
+.Ix 0 ref variable assignment expanded
+.Ix 0 ref variable assignment conditional
+will work if the only variables used are in the environment.
+.CW !=
+is sort of pointless to use from the command line, since the same
+effect can no doubt be accomplished using the shell's own command
+substitution mechanisms (backquotes and all that).
+.xH 3 Global Variables
+.LP
+.Ix 0 def variable global
+Global variables are those set or appended-to in the makefile.
+There are two classes of global variables: those you set and those PMake sets.
+As I said before, the ones you set can have any name you want them to have,
+except they may not contain a colon or an exclamation point.
+The variables PMake sets (almost) always begin with a
+period and always contain upper-case letters, only. The variables are
+as follows:
+.RS
+.IP .PMAKE
+.Ix 0 def variable global .PMAKE
+.Ix 0 def .PMAKE
+.Ix 0 def variable global MAKE
+.Ix 0 def MAKE
+The name by which PMake was invoked is stored in this variable. For
+compatibility, the name is also stored in the MAKE variable.
+.IP .MAKEFLAGS
+.Ix 0 def variable global .MAKEFLAGS
+.Ix 0 def .MAKEFLAGS variable
+.Ix 0 def variable global MFLAGS
+.Ix 0 def MFLAGS
+All the relevant flags with which PMake was invoked. This does not
+include such things as
+.B \-f
+or variable assignments. Again for compatibility, this value is stored
+in the MFLAGS variable as well.
+.RE
+.LP
+Two other variables, ``.INCLUDES'' and ``.LIBS,'' are covered in the
+section on special targets in chapter 3.
+.Ix 0 ref variable global .INCLUDES
+.Ix 0 ref variable global .LIBS
+.LP
+Global variables may be deleted using lines of the form:
+.Ix 0 def #undef
+.Ix 0 def variable deletion
+.DS
+#undef \fIvariable\fP
+.DE
+The
+.CW # ' `
+must be the first character on the line. Note that this may only be
+done on global variables.
+.xH 3 Environment Variables
+.LP
+.Ix 0 def variable environment
+Environment variables are passed by the shell that invoked PMake and
+are given by PMake to each shell it invokes. They are expanded like
+any other variable, but they cannot be altered in any way.
+.LP
+One special environment variable,
+.CW PMAKE ,
+.Ix 0 def variable environment PMAKE
+is examined by PMake for command-line flags, variable assignments,
+etc., it should always use. This variable is examined before the
+actual arguments to PMake are. In addition, all flags given to PMake,
+either through the
+.CW PMAKE
+variable or on the command line, are placed in this environment
+variable and exported to each shell PMake executes. Thus recursive
+invocations of PMake automatically receive the same flags as the
+top-most one.
+.LP
+Using all these variables, you can compress the sample makefile even more:
+.DS
+OBJS = a.o b.o c.o
+program : $(OBJS)
+ cc $(.ALLSRC) \-o $(.TARGET)
+$(OBJS) : defs.h
+a.o : a.c
+ cc \-c a.c
+b.o : b.c
+ cc \-c b.c
+c.o : c.c
+ cc \-c c.c
+.DE
+.Ix 0 ref variable local .ALLSRC
+.Ix 0 ref .ALLSRC
+.Ix 0 ref variable local .TARGET
+.Ix 0 ref .TARGET
+.Rd 3
+.xH 2 Comments
+.LP
+.Ix 0 def comments
+Comments in a makefile start with a `#' character and extend to the
+end of the line. They may appear
+anywhere you want them, except in a shell command (though the shell
+will treat it as a comment, too). If, for some reason, you need to use the `#'
+in a variable or on a dependency line, put a backslash in front of it.
+PMake will compress the two into a single `#' (Note: this isn't true
+if PMake is operating in full-compatibility mode).
+.Ix 0 ref flags -M
+.Ix 0 ref compatibility
+.xH 2 Parallelism
+.No
+.LP
+PMake was specifically designed to re-create several targets at once,
+when possible. You do not have to do anything special to cause this to
+happen (unless PMake was configured to not act in parallel, in which
+case you will have to make use of the
+.B \-L
+and
+.B \-J
+flags (see below)),
+.Ix 0 ref flags -L
+.Ix 0 ref flags -J
+but you do have to be careful at times.
+.LP
+There are several problems you are likely to encounter. One is
+that some makefiles (and programs) are written in such a way that it is
+impossible for two targets to be made at once. The program
+.CW xstr ,
+for example,
+always modifies the files
+.CW strings
+and
+.CW x.c .
+There is no way to change it. Thus you cannot run two of them at once
+without something being trashed. Similarly, if you have commands
+in the makefile that always send output to the same file, you will not
+be able to make more than one target at once unless you change the
+file you use. You can, for instance, add a
+.CW $$$$
+to the end of the file name to tack on the process ID of the shell
+executing the command (each
+.CW $$
+expands to a single
+.CW $ ,
+thus giving you the shell variable
+.CW $$ ).
+Since only one shell is used for all the
+commands, you'll get the same file name for each command in the
+script.
+.LP
+The other problem comes from improperly-specified dependencies that
+worked in Make because of its sequential, depth-first way of examining
+them. While I don't want to go into depth on how PMake
+works (look in chapter 4 if you're interested), I will warn you that
+files in two different ``levels'' of the dependency tree may be
+examined in a different order in PMake than they were in Make. For
+example, given the makefile
+.DS
+a : b c
+b : d
+.DE
+PMake will examine the targets in the order
+.CW c ,
+.CW d ,
+.CW b ,
+.CW a .
+If the makefile's author expected PMake to abort before making
+.CW c
+if an error occurred while making
+.CW b ,
+or if
+.CW b
+needed to exist before
+.CW c
+was made,
+s/he will be sorely disappointed. The dependencies are
+incomplete, since in both these cases,
+.CW c
+would depend on
+.CW b .
+So watch out.
+.LP
+Another problem you may face is that, while PMake is set up to handle the
+output from multiple jobs in a graceful fashion, the same is not so for input.
+It has no way to regulate input to different jobs,
+so if you use the redirection from
+.CW /dev/tty
+I mentioned earlier, you must be careful not to run two of the jobs at once.
+.xH 2 Writing and Debugging a Makefile
+.LP
+Now you know most of what's in a makefile, what do you do next? There
+are two choices: (1) use one of the uncommonly-available makefile
+generators or (2) write your own makefile (I leave out the third choice of
+ignoring PMake and doing everything by hand as being beyond the bounds
+of common sense).
+.LP
+When faced with the writing of a makefile, it is usually best to start
+from first principles: just what
+.I are
+you trying to do? What do you want the makefile finally to produce?
+.LP
+To begin with a somewhat traditional example, let's say you need to
+write a makefile to create a program,
+.CW expr ,
+that takes standard infix expressions and converts them to prefix form (for
+no readily apparent reason). You've got three source files, in C, that
+make up the program:
+.CW main.c ,
+.CW parse.c ,
+and
+.CW output.c .
+Harking back to my pithy advice about dependency lines, you write the
+first line of the file:
+.DS
+expr : main.o parse.o output.o
+.DE
+because you remember
+.CW expr
+is made from
+.CW .o
+files, not
+.CW .c
+files. Similarly for the
+.CW .o
+files you produce the lines:
+.DS
+main.o : main.c
+parse.o : parse.c
+output.o : output.c
+main.o parse.o output.o : defs.h
+.DE
+.LP
+Great. You've now got the dependencies specified. What you need now is
+commands. These commands, remember, must produce the target on the
+dependency line, usually by using the sources you've listed.
+You remember about local variables? Good, so it should come
+to you as no surprise when you write
+.DS
+expr : main.o parse.o output.o
+ cc -o $(.TARGET) $(.ALLSRC)
+.DE
+Why use the variables? If your program grows to produce postfix
+expressions too (which, of course, requires a name change or two), it
+is one fewer place you have to change the file. You cannot do this for
+the object files, however, because they depend on their corresponding
+source files
+.I and
+.CW defs.h ,
+thus if you said
+.DS
+ cc -c $(.ALLSRC)
+.DE
+you'd get (for
+.CW main.o ):
+.DS
+ cc -c main.c defs.h
+.DE
+which is wrong. So you round out the makefile with these lines:
+.DS
+main.o : main.c
+ cc -c main.c
+parse.o : parse.c
+ cc -c parse.c
+output.o : output.c
+ cc -c output.c
+.DE
+.LP
+The makefile is now complete and will, in fact, create the program you
+want it to without unnecessary compilations or excessive typing on
+your part. There are two things wrong with it, however (aside from it
+being altogether too long, something I'll address in chapter 3):
+.IP 1)
+The string
+.CW "main.o parse.o output.o" '' ``
+is repeated twice, necessitating two changes when you add postfix
+(you were planning on that, weren't you?). This is in direct violation
+of de Boor's First Rule of writing makefiles:
+.QP
+.I
+Anything that needs to be written more than once
+should be placed in a variable.
+.IP "\&"
+I cannot emphasize this enough as being very important to the
+maintenance of a makefile and its program.
+.IP 2)
+There is no way to alter the way compilations are performed short of
+editing the makefile and making the change in all places. This is evil
+and violates de Boor's Second Rule, which follows directly from the
+first:
+.QP
+.I
+Any flags or programs used inside a makefile should be placed in a variable so
+they may be changed, temporarily or permanently, with the greatest ease.
+.LP
+The makefile should more properly read:
+.DS
+OBJS = main.o parse.o output.o
+expr : $(OBJS)
+ $(CC) $(CFLAGS) -o $(.TARGET) $(.ALLSRC)
+main.o : main.c
+ $(CC) $(CFLAGS) -c main.c
+parse.o : parse.c
+ $(CC) $(CFLAGS) -c parse.c
+output.o : output.c
+ $(CC) $(CFLAGS) -c output.c
+$(OBJS) : defs.h
+.DE
+Alternatively, if you like the idea of dynamic sources mentioned in
+section 2.3.1,
+.Rm 0 2.3.1
+.Rd 4
+.Ix 0 ref "dynamic source"
+.Ix 0 ref source dynamic
+you could write it like this:
+.DS
+OBJS = main.o parse.o output.o
+expr : $(OBJS)
+ $(CC) $(CFLAGS) -o $(.TARGET) $(.ALLSRC)
+$(OBJS) : $(.PREFIX).c defs.h
+ $(CC) $(CFLAGS) -c $(.PREFIX).c
+.DE
+These two rules and examples lead to de Boor's First Corollary:
+.QP
+.I
+Variables are your friends.
+.LP
+Once you've written the makefile comes the sometimes-difficult task of
+.Ix 0 ref debugging
+making sure the darn thing works. Your most helpful tool to make sure
+the makefile is at least syntactically correct is the
+.B \-n
+.Ix 0 ref flags -n
+flag, which allows you to see if PMake will choke on the makefile. The
+second thing the
+.B \-n
+flag lets you do is see what PMake would do without it actually doing
+it, thus you can make sure the right commands would be executed were
+you to give PMake its head.
+.LP
+When you find your makefile isn't behaving as you hoped, the first
+question that comes to mind (after ``What time is it, anyway?'') is
+``Why not?'' In answering this, two flags will serve you well:
+.CW "-d m" '' ``
+.Ix 0 ref flags -d
+and
+.CW "-p 2" .'' ``
+.Ix 0 ref flags -p
+The first causes PMake to tell you as it examines each target in the
+makefile and indicate why it is deciding whatever it is deciding. You
+can then use the information printed for other targets to see where
+you went wrong. The
+.CW "-p 2" '' ``
+flag makes PMake print out its internal state when it is done,
+allowing you to see that you forgot to make that one chapter depend on
+that file of macros you just got a new version of. The output from
+.CW "-p 2" '' ``
+is intended to resemble closely a real makefile, but with additional
+information provided and with variables expanded in those commands
+PMake actually printed or executed.
+.LP
+Something to be especially careful about is circular dependencies.
+.Ix 0 def dependency circular
+E.g.
+.DS
+a : b
+b : c d
+d : a
+.DE
+In this case, because of how PMake works,
+.CW c
+is the only thing PMake will examine, because
+.CW d
+and
+.CW a
+will effectively fall off the edge of the universe, making it
+impossible to examine
+.CW b
+(or them, for that matter).
+PMake will tell you (if run in its normal mode) all the targets
+involved in any cycle it looked at (i.e. if you have two cycles in the
+graph (naughty, naughty), but only try to make a target in one of
+them, PMake will only tell you about that one. You'll have to try to
+make the other to find the second cycle). When run as Make, it will
+only print the first target in the cycle.
+.xH 2 Invoking PMake
+.LP
+.Ix 0 ref flags
+.Ix 0 ref arguments
+.Ix 0 ref usage
+PMake comes with a wide variety of flags to choose from.
+They may appear in any order, interspersed with command-line variable
+assignments and targets to create.
+The flags are as follows:
+.IP "\fB\-d\fP \fIwhat\fP"
+.Ix 0 def flags -d
+.Ix 0 ref debugging
+This causes PMake to spew out debugging information that
+may prove useful to you. If you can't
+figure out why PMake is doing what it's doing, you might try using
+this flag. The
+.I what
+parameter is a string of single characters that tell PMake what
+aspects you are interested in. Most of what I describe will make
+little sense to you, unless you've dealt with Make before. Just
+remember where this table is and come back to it as you read on.
+The characters and the information they produce are as follows:
+.RS
+.IP a
+Archive searching and caching.
+.IP c
+Conditional evaluation.
+.IP d
+The searching and caching of directories.
+.IP j
+Various snippets of information related to the running of the multiple
+shells. Not particularly interesting.
+.IP m
+The making of each target: what target is being examined; when it was
+last modified; whether it is out-of-date; etc.
+.IP p
+Makefile parsing.
+.IP r
+Remote execution.
+.IP s
+The application of suffix-transformation rules. (See chapter 3)
+.IP t
+The maintenance of the list of targets.
+.IP v
+Variable assignment.
+.RE
+.IP "\&"
+Of these all, the
+.CW m
+and
+.CW s
+letters will be most useful to you.
+If the
+.B \-d
+is the final argument or the argument from which it would get these
+key letters (see below for a note about which argument would be used)
+begins with a
+.B \- ,
+all of these debugging flags will be set, resulting in massive amounts
+of output.
+.IP "\fB\-f\fP \fImakefile\fP"
+.Ix 0 def flags -f
+Specify a makefile to read different from the standard makefiles
+.CW Makefile "\&" (
+or
+.CW makefile ).
+.Ix 0 ref makefile default
+.Ix 0 ref makefile other
+If
+.I makefile
+is ``\-'', PMake uses the standard input. This is useful for making
+quick and dirty makefiles.\|.\|.
+.Ix 0 ref makefile "quick and dirty"
+.IP \fB\-h\fP
+.Ix 0 def flags -h
+Prints out a summary of the various flags PMake accepts. It can also
+be used to find out what level of concurrency was compiled into the
+version of PMake you are using (look at
+.B \-J
+and
+.B \-L )
+and various other information on how PMake was configured.
+.Ix 0 ref configuration
+.Ix 0 ref makefile system
+.IP \fB\-i\fP
+.Ix 0 def flags -i
+If you give this flag, PMake will ignore non-zero status returned
+by any of its shells. It's like placing a `\-' before all the commands
+in the makefile.
+.IP \fB\-k\fP
+.Ix 0 def flags -k
+This is similar to
+.B \-i
+in that it allows PMake to continue when it sees an error, but unlike
+.B \-i ,
+where PMake continues blithely as if nothing went wrong,
+.B \-k
+causes it to recognize the error and only continue work on those
+things that don't depend on the target, either directly or indirectly (through
+depending on something that depends on it), whose creation returned the error.
+The `k' is for ``keep going''.\|.\|.
+.Ix 0 ref target
+.IP \fB\-l\fP
+.Ix 0 def flags -l
+PMake has the ability to lock a directory against other
+people executing it in the same directory (by means of a file called
+``LOCK.make'' that it creates and checks for in the directory). This
+is a Good Thing because two people doing the same thing in the same place
+can be disastrous for the final product (too many cooks and all that).
+Whether this locking is the default is up to your system
+administrator. If locking is on,
+.B \-l
+will turn it off, and vice versa. Note that this locking will not
+prevent \fIyou\fP from invoking PMake twice in the same place \*- if
+you own the lock file, PMake will warn you about it but continue to execute.
+.IP "\fB\-m\fP \fIdirectory\fP"
+.Ix 0 def flags -m
+Tells PMake another place to search for included makefiles via the <...>
+style. Several
+.B \-m
+options can be given to form a search path. If this construct is used the
+default system makefile search path is completely overridden.
+To be explained in chapter 3, section 3.2.
+.Rm 2 3.2
+.IP \fB\-n\fP
+.Ix 0 def flags -n
+This flag tells PMake not to execute the commands needed to update the
+out-of-date targets in the makefile. Rather, PMake will simply print
+the commands it would have executed and exit. This is particularly
+useful for checking the correctness of a makefile. If PMake doesn't do
+what you expect it to, it's a good chance the makefile is wrong.
+.IP "\fB\-p\fP \fInumber\fP"
+.Ix 0 def flags -p
+.Ix 0 ref debugging
+This causes PMake to print its input in a reasonable form, though
+not necessarily one that would make immediate sense to anyone but me. The
+.I number
+is a bitwise-or of 1 and 2 where 1 means it should print the input
+before doing any processing and 2 says it should print it after
+everything has been re-created. Thus
+.CW "\-p 3"
+would print it twice\*-once before processing and once after (you
+might find the difference between the two interesting). This is mostly
+useful to me, but you may find it informative in some bizarre circumstances.
+.IP \fB\-q\fP
+.Ix 0 def flags -q
+If you give PMake this flag, it will not try to re-create anything. It
+will just see if anything is out-of-date and exit non-zero if so.
+.IP \fB\-r\fP
+.Ix 0 def flags -r
+When PMake starts up, it reads a default makefile that tells it what
+sort of system it's on and gives it some idea of what to do if you
+don't tell it anything. I'll tell you about it in chapter 3. If you
+give this flag, PMake won't read the default makefile.
+.IP \fB\-s\fP
+.Ix 0 def flags -s
+This causes PMake to not print commands before they're executed. It
+is the equivalent of putting an `@' before every command in the
+makefile.
+.IP \fB\-t\fP
+.Ix 0 def flags -t
+Rather than try to re-create a target, PMake will simply ``touch'' it
+so as to make it appear up-to-date. If the target didn't exist before,
+it will when PMake finishes, but if the target did exist, it will
+appear to have been updated.
+.IP \fB\-v\fP
+.Ix 0 def flags -v
+This is a mixed-compatibility flag intended to mimic the System V
+version of Make. It is the same as giving
+.B \-B ,
+and
+.B \-V
+as well as turning off directory locking. Targets can still be created
+in parallel, however. This is the mode PMake will enter if it is
+invoked either as
+.CW smake '' ``
+or
+.CW vmake ''. ``
+.IP \fB\-x\fP
+.Ix 0 def flags -x
+This tells PMake it's ok to export jobs to other machines, if they're
+available. It is used when running in Make mode, as exporting in this
+mode tends to make things run slower than if the commands were just
+executed locally.
+.IP \fB\-B\fP
+.Ix 0 ref compatibility
+.Ix 0 def flags -B
+Forces PMake to be as backwards-compatible with Make as possible while
+still being itself.
+This includes:
+.RS
+.IP \(bu 2
+Executing one shell per shell command
+.IP \(bu 2
+Expanding anything that looks even vaguely like a variable, with the
+empty string replacing any variable PMake doesn't know.
+.IP \(bu 2
+Refusing to allow you to escape a `#' with a backslash.
+.IP \(bu 2
+Permitting undefined variables on dependency lines and conditionals
+(see below). Normally this causes PMake to abort.
+.RE
+.IP \fB\-C\fP
+.Ix 0 def flags -C
+This nullifies any and all compatibility mode flags you may have given
+or implied up to the time the
+.B \-C
+is encountered. It is useful mostly in a makefile that you wrote for PMake
+to avoid bad things happening when someone runs PMake as
+.CW make '' ``
+or has things set in the environment that tell it to be compatible.
+.B \-C
+is
+.I not
+placed in the
+.CW PMAKE
+environment variable or the
+.CW .MAKEFLAGS
+or
+.CW MFLAGS
+global variables.
+.Ix 0 ref variable environment PMAKE
+.Ix 0 ref variable global .MAKEFLAGS
+.Ix 0 ref variable global MFLAGS
+.Ix 0 ref .MAKEFLAGS variable
+.Ix 0 ref MFLAGS
+.IP "\fB\-D\fP \fIvariable\fP"
+.Ix 0 def flags -D
+Allows you to define a variable to have
+.CW 1 '' ``
+as its value. The variable is a global variable, not a command-line
+variable. This is useful mostly for people who are used to the C
+compiler arguments and those using conditionals, which I'll get into
+in section 4.3
+.Rm 1 4.3
+.IP "\fB\-I\fP \fIdirectory\fP"
+.Ix 0 def flags -I
+Tells PMake another place to search for included makefiles. Yet
+another thing to be explained in chapter 3 (section 3.2, to be
+precise).
+.Rm 2 3.2
+.IP "\fB\-J\fP \fInumber\fP"
+.Ix 0 def flags -J
+Gives the absolute maximum number of targets to create at once on both
+local and remote machines.
+.IP "\fB\-L\fP \fInumber\fP"
+.Ix 0 def flags -L
+This specifies the maximum number of targets to create on the local
+machine at once. This may be 0, though you should be wary of doing
+this, as PMake may hang until a remote machine becomes available, if
+one is not available when it is started.
+.IP \fB\-M\fP
+.Ix 0 ref compatibility
+.Ix 0 def flags -M
+This is the flag that provides absolute, complete, full compatibility
+with Make. It still allows you to use all but a few of the features of
+PMake, but it is non-parallel. This is the mode PMake enters if you
+call it
+.CW make .'' ``
+.IP \fB\-P\fP
+.Ix 0 def flags -P
+.Ix 0 ref "output control"
+When creating targets in parallel, several shells are executing at
+once, each wanting to write its own two cent's-worth to the screen.
+This output must be captured by PMake in some way in order to prevent
+the screen from being filled with garbage even more indecipherable
+than you usually see. PMake has two ways of doing this, one of which
+provides for much cleaner output and a clear separation between the
+output of different jobs, the other of which provides a more immediate
+response so one can tell what is really happening. The former is done
+by notifying you when the creation of a target starts, capturing the
+output and transferring it to the screen all at once when the job
+finishes. The latter is done by catching the output of the shell (and
+its children) and buffering it until an entire line is received, then
+printing that line preceded by an indication of which job produced
+the output. Since I prefer this second method, it is the one used by
+default. The first method will be used if you give the
+.B \-P
+flag to PMake.
+.IP \fB\-V\fP
+.Ix 0 def flags -V
+As mentioned before, the
+.B \-V
+flag tells PMake to use Make's style of expanding variables,
+substituting the empty string for any variable it doesn't know.
+.IP \fB\-W\fP
+.Ix 0 def flags -W
+There are several times when PMake will print a message at you that is
+only a warning, i.e. it can continue to work in spite of your having
+done something silly (such as forgotten a leading tab for a shell
+command). Sometimes you are well aware of silly things you have done
+and would like PMake to stop bothering you. This flag tells it to shut
+up about anything non-fatal.
+.IP \fB\-X\fP
+.Ix 0 def flags -X
+This flag causes PMake to not attempt to export any jobs to another
+machine.
+.LP
+Several flags may follow a single `\-'. Those flags that require
+arguments take them from successive parameters. E.g.
+.DS
+pmake -fDnI server.mk DEBUG /chip2/X/server/include
+.DE
+will cause PMake to read
+.CW server.mk
+as the input makefile, define the variable
+.CW DEBUG
+as a global variable and look for included makefiles in the directory
+.CW /chip2/X/server/include .
+.xH 2 Summary
+.LP
+A makefile is made of four types of lines:
+.RS
+.IP \(bu 2
+Dependency lines
+.IP \(bu 2
+Creation commands
+.IP \(bu 2
+Variable assignments
+.IP \(bu 2
+Comments, include statements and conditional directives
+.RE
+.LP
+A dependency line is a list of one or more targets, an operator
+.CW : ', (`
+.CW :: ', `
+or
+.CW ! '), `
+and a list of zero or more sources. Sources may contain wildcards and
+certain local variables.
+.LP
+A creation command is a regular shell command preceded by a tab. In
+addition, if the first two characters after the tab (and other
+whitespace) are a combination of
+.CW @ ' `
+or
+.CW - ', `
+PMake will cause the command to not be printed (if the character is
+.CW @ ') `
+or errors from it to be ignored (if
+.CW - '). `
+A blank line, dependency line or variable assignment terminates a
+creation script. There may be only one creation script for each target
+with a
+.CW : ' `
+or
+.CW ! ' `
+operator.
+.LP
+Variables are places to store text. They may be unconditionally
+assigned-to using the
+.CW = ' `
+.Ix 0 ref =
+.Ix 0 ref variable assignment
+operator, appended-to using the
+.CW += ' `
+.Ix 0 ref +=
+.Ix 0 ref variable assignment appended
+operator, conditionally (if the variable is undefined) assigned-to
+with the
+.CW ?= ' `
+.Ix 0 ref ?=
+.Ix 0 ref variable assignment conditional
+operator, and assigned-to with variable expansion with the
+.CW := ' `
+.Ix 0 ref :=
+.Ix 0 ref variable assignment expanded
+operator. The output of a shell command may be assigned to a variable
+using the
+.CW != ' `
+.Ix 0 ref !=
+.Ix 0 ref variable assignment shell-output
+operator. Variables may be expanded (their value inserted) by enclosing
+their name in parentheses or curly braces, preceded by a dollar sign.
+A dollar sign may be escaped with another dollar sign. Variables are
+not expanded if PMake doesn't know about them. There are seven local
+variables:
+.CW .TARGET ,
+.CW .ALLSRC ,
+.CW .OODATE ,
+.CW .PREFIX ,
+.CW .IMPSRC ,
+.CW .ARCHIVE ,
+and
+.CW .MEMBER .
+Four of them
+.CW .TARGET , (
+.CW .PREFIX ,
+.CW .ARCHIVE ,
+and
+.CW .MEMBER )
+may be used to specify ``dynamic sources.''
+.Ix 0 ref "dynamic source"
+.Ix 0 ref source dynamic
+Variables are good. Know them. Love them. Live them.
+.LP
+Debugging of makefiles is best accomplished using the
+.B \-n ,
+.B "\-d m" ,
+and
+.B "\-p 2"
+flags.
+.xH 2 Exercises
+.ce
+\s+4\fBTBA\fP\s0
+.xH 1 Short-cuts and Other Nice Things
+.LP
+Based on what I've told you so far, you may have gotten the impression
+that PMake is just a way of storing away commands and making sure you
+don't forget to compile something. Good. That's just what it is.
+However, the ways I've described have been inelegant, at best, and
+painful, at worst.
+This chapter contains things that make the
+writing of makefiles easier and the makefiles themselves shorter and
+easier to modify (and, occasionally, simpler). In this chapter, I
+assume you are somewhat more
+familiar with Sprite (or UNIX, if that's what you're using) than I did
+in chapter 2, just so you're on your toes.
+So without further ado...
+.xH 2 Transformation Rules
+.LP
+As you know, a file's name consists of two parts: a base name, which
+gives some hint as to the contents of the file, and a suffix, which
+usually indicates the format of the file.
+Over the years, as
+.UX
+has developed,
+naming conventions, with regard to suffixes, have also developed that have
+become almost as incontrovertible as Law. E.g. a file ending in
+.CW .c
+is assumed to contain C source code; one with a
+.CW .o
+suffix is assumed to be a compiled, relocatable object file that may
+be linked into any program; a file with a
+.CW .ms
+suffix is usually a text file to be processed by Troff with the \-ms
+macro package, and so on.
+One of the best aspects of both Make and PMake comes from their
+understanding of how the suffix of a file pertains to its contents and
+their ability to do things with a file based solely on its suffix. This
+ability comes from something known as a transformation rule. A
+transformation rule specifies how to change a file with one suffix
+into a file with another suffix.
+.LP
+A transformation rule looks much like a dependency line, except the
+target is made of two known suffixes stuck together. Suffixes are made
+known to PMake by placing them as sources on a dependency line whose
+target is the special target
+.CW .SUFFIXES .
+E.g.
+.DS
+\&.SUFFIXES : .o .c
+\&.c.o :
+ $(CC) $(CFLAGS) -c $(.IMPSRC)
+.DE
+The creation script attached to the target is used to transform a file with
+the first suffix (in this case,
+.CW .c )
+into a file with the second suffix (here,
+.CW .o ).
+In addition, the target inherits whatever attributes have been applied
+to the transformation rule.
+The simple rule given above says that to transform a C source file
+into an object file, you compile it using
+.CW cc
+with the
+.CW \-c
+flag.
+This rule is taken straight from the system makefile. Many
+transformation rules (and suffixes) are defined there, and I refer you
+to it for more examples (type
+.CW "pmake -h" '' ``
+to find out where it is).
+.LP
+There are several things to note about the transformation rule given
+above:
+.RS
+.IP 1)
+The
+.CW .IMPSRC
+variable.
+.Ix 0 def variable local .IMPSRC
+.Ix 0 def .IMPSRC
+This variable is set to the ``implied source'' (the file from which
+the target is being created; the one with the first suffix), which, in this
+case, is the .c file.
+.IP 2)
+The
+.CW CFLAGS
+variable. Almost all of the transformation rules in the system
+makefile are set up using variables that you can alter in your
+makefile to tailor the rule to your needs. In this case, if you want
+all your C files to be compiled with the
+.B \-g
+flag, to provide information for
+.CW dbx ,
+you would set the
+.CW CFLAGS
+variable to contain
+.CW -g
+.CW "CFLAGS = -g" '') (``
+and PMake would take care of the rest.
+.RE
+.LP
+To give you a quick example, the makefile in 2.3.4
+.Rm 3 2.3.4
+could be changed to this:
+.DS
+OBJS = a.o b.o c.o
+program : $(OBJS)
+ $(CC) -o $(.TARGET) $(.ALLSRC)
+$(OBJS) : defs.h
+.DE
+The transformation rule I gave above takes the place of the 6 lines\**
+.FS
+This is also somewhat cleaner, I think, than the dynamic source
+solution presented in 2.6
+.FE
+.Rm 4 2.6
+.DS
+a.o : a.c
+ cc -c a.c
+b.o : b.c
+ cc -c b.c
+c.o : c.c
+ cc -c c.c
+.DE
+.LP
+Now you may be wondering about the dependency between the
+.CW .o
+and
+.CW .c
+files \*- it's not mentioned anywhere in the new makefile. This is
+because it isn't needed: one of the effects of applying a
+transformation rule is the target comes to depend on the implied
+source. That's why it's called the implied
+.I source .
+.LP
+For a more detailed example. Say you have a makefile like this:
+.DS
+a.out : a.o b.o
+ $(CC) $(.ALLSRC)
+.DE
+and a directory set up like this:
+.DS
+total 4
+-rw-rw-r-- 1 deboor 34 Sep 7 00:43 Makefile
+-rw-rw-r-- 1 deboor 119 Oct 3 19:39 a.c
+-rw-rw-r-- 1 deboor 201 Sep 7 00:43 a.o
+-rw-rw-r-- 1 deboor 69 Sep 7 00:43 b.c
+.DE
+While just typing
+.CW pmake '' ``
+will do the right thing, it's much more informative to type
+.CW "pmake -d s" ''. ``
+This will show you what PMake is up to as it processes the files. In
+this case, PMake prints the following:
+.DS
+Suff_FindDeps (a.out)
+ using existing source a.o
+ applying .o -> .out to "a.o"
+Suff_FindDeps (a.o)
+ trying a.c...got it
+ applying .c -> .o to "a.c"
+Suff_FindDeps (b.o)
+ trying b.c...got it
+ applying .c -> .o to "b.c"
+Suff_FindDeps (a.c)
+ trying a.y...not there
+ trying a.l...not there
+ trying a.c,v...not there
+ trying a.y,v...not there
+ trying a.l,v...not there
+Suff_FindDeps (b.c)
+ trying b.y...not there
+ trying b.l...not there
+ trying b.c,v...not there
+ trying b.y,v...not there
+ trying b.l,v...not there
+--- a.o ---
+cc -c a.c
+--- b.o ---
+cc -c b.c
+--- a.out ---
+cc a.o b.o
+.DE
+.LP
+.CW Suff_FindDeps
+is the name of a function in PMake that is called to check for implied
+sources for a target using transformation rules.
+The transformations it tries are, naturally
+enough, limited to the ones that have been defined (a transformation
+may be defined multiple times, by the way, but only the most recent
+one will be used). You will notice, however, that there is a definite
+order to the suffixes that are tried. This order is set by the
+relative positions of the suffixes on the
+.CW .SUFFIXES
+line \*- the earlier a suffix appears, the earlier it is checked as
+the source of a transformation. Once a suffix has been defined, the
+only way to change its position in the pecking order is to remove all
+the suffixes (by having a
+.CW .SUFFIXES
+dependency line with no sources) and redefine them in the order you
+want. (Previously-defined transformation rules will be automatically
+redefined as the suffixes they involve are re-entered.)
+.LP
+Another way to affect the search order is to make the dependency
+explicit. In the above example,
+.CW a.out
+depends on
+.CW a.o
+and
+.CW b.o .
+Since a transformation exists from
+.CW .o
+to
+.CW .out ,
+PMake uses that, as indicated by the
+.CW "using existing source a.o" '' ``
+message.
+.LP
+The search for a transformation starts from the suffix of the target
+and continues through all the defined transformations, in the order
+dictated by the suffix ranking, until an existing file with the same
+base (the target name minus the suffix and any leading directories) is
+found. At that point, one or more transformation rules will have been
+found to change the one existing file into the target.
+.LP
+For example, ignoring what's in the system makefile for now, say you
+have a makefile like this:
+.DS
+\&.SUFFIXES : .out .o .c .y .l
+\&.l.c :
+ lex $(.IMPSRC)
+ mv lex.yy.c $(.TARGET)
+\&.y.c :
+ yacc $(.IMPSRC)
+ mv y.tab.c $(.TARGET)
+\&.c.o :
+ cc -c $(.IMPSRC)
+\&.o.out :
+ cc -o $(.TARGET) $(.IMPSRC)
+.DE
+and the single file
+.CW jive.l .
+If you were to type
+.CW "pmake -rd ms jive.out" ,'' ``
+you would get the following output for
+.CW jive.out :
+.DS
+Suff_FindDeps (jive.out)
+ trying jive.o...not there
+ trying jive.c...not there
+ trying jive.y...not there
+ trying jive.l...got it
+ applying .l -> .c to "jive.l"
+ applying .c -> .o to "jive.c"
+ applying .o -> .out to "jive.o"
+.DE
+and this is why: PMake starts with the target
+.CW jive.out ,
+figures out its suffix
+.CW .out ) (
+and looks for things it can transform to a
+.CW .out
+file. In this case, it only finds
+.CW .o ,
+so it looks for the file
+.CW jive.o .
+It fails to find it, so it looks for transformations into a
+.CW .o
+file. Again it has only one choice:
+.CW .c .
+So it looks for
+.CW jive.c
+and, as you know, fails to find it. At this point it has two choices:
+it can create the
+.CW .c
+file from either a
+.CW .y
+file or a
+.CW .l
+file. Since
+.CW .y
+came first on the
+.CW .SUFFIXES
+line, it checks for
+.CW jive.y
+first, but can't find it, so it looks for
+.CW jive.l
+and, lo and behold, there it is.
+At this point, it has defined a transformation path as follows:
+.CW .l
+\(->
+.CW .c
+\(->
+.CW .o
+\(->
+.CW .out
+and applies the transformation rules accordingly. For completeness,
+and to give you a better idea of what PMake actually did with this
+three-step transformation, this is what PMake printed for the rest of
+the process:
+.DS
+Suff_FindDeps (jive.o)
+ using existing source jive.c
+ applying .c -> .o to "jive.c"
+Suff_FindDeps (jive.c)
+ using existing source jive.l
+ applying .l -> .c to "jive.l"
+Suff_FindDeps (jive.l)
+Examining jive.l...modified 17:16:01 Oct 4, 1987...up-to-date
+Examining jive.c...non-existent...out-of-date
+--- jive.c ---
+lex jive.l
+\&.\|.\|. meaningless lex output deleted .\|.\|.
+mv lex.yy.c jive.c
+Examining jive.o...non-existent...out-of-date
+--- jive.o ---
+cc -c jive.c
+Examining jive.out...non-existent...out-of-date
+--- jive.out ---
+cc -o jive.out jive.o
+.DE
+.LP
+One final question remains: what does PMake do with targets that have
+no known suffix? PMake simply pretends it actually has a known suffix
+and searches for transformations accordingly.
+The suffix it chooses is the source for the
+.CW .NULL
+.Ix 0 ref .NULL
+target mentioned later. In the system makefile,
+.CW .out
+is chosen as the ``null suffix''
+.Ix 0 def suffix null
+.Ix 0 def "null suffix"
+because most people use PMake to create programs. You are, however,
+free and welcome to change it to a suffix of your own choosing.
+The null suffix is ignored, however, when PMake is in compatibility
+mode (see chapter 4).
+.xH 2 Including Other Makefiles
+.Ix 0 def makefile inclusion
+.Rd 2
+.LP
+Just as for programs, it is often useful to extract certain parts of a
+makefile into another file and just include it in other makefiles
+somehow. Many compilers allow you say something like
+.DS
+#include "defs.h"
+.DE
+to include the contents of
+.CW defs.h
+in the source file. PMake allows you to do the same thing for
+makefiles, with the added ability to use variables in the filenames.
+An include directive in a makefile looks either like this:
+.DS
+#include <file>
+.DE
+or this
+.DS
+#include "file"
+.DE
+The difference between the two is where PMake searches for the file:
+the first way, PMake will look for
+the file only in the system makefile directory (or directories)
+(to find out what that directory is, give PMake the
+.B \-h
+flag).
+.Ix 0 ref flags -h
+The system makefile directory search path can be overridden via the
+.B \-m
+option.
+.Ix 0 ref flags -m
+For files in double-quotes, the search is more complex:
+.RS
+.IP 1)
+The directory of the makefile that's including the file.
+.IP 2)
+The current directory (the one in which you invoked PMake).
+.IP 3)
+The directories given by you using
+.B \-I
+flags, in the order in which you gave them.
+.IP 4)
+Directories given by
+.CW .PATH
+dependency lines (see chapter 4).
+.IP 5)
+The system makefile directory.
+.RE
+.LP
+in that order.
+.LP
+You are free to use PMake variables in the filename\*-PMake will
+expand them before searching for the file. You must specify the
+searching method with either angle brackets or double-quotes
+.I outside
+of a variable expansion. I.e. the following
+.DS
+SYSTEM = <command.mk>
+
+#include $(SYSTEM)
+.DE
+won't work.
+.xH 2 Saving Commands
+.LP
+.Ix 0 def ...
+There may come a time when you will want to save certain commands to
+be executed when everything else is done. For instance: you're
+making several different libraries at one time and you want to create the
+members in parallel. Problem is,
+.CW ranlib
+is another one of those programs that can't be run more than once in
+the same directory at the same time (each one creates a file called
+.CW __.SYMDEF
+into which it stuffs information for the linker to use. Two of them
+running at once will overwrite each other's file and the result will
+be garbage for both parties). You might want a way to save the ranlib
+commands til the end so they can be run one after the other, thus
+keeping them from trashing each other's file. PMake allows you to do
+this by inserting an ellipsis (``.\|.\|.'') as a command between
+commands to be run at once and those to be run later.
+.LP
+So for the
+.CW ranlib
+case above, you might do this:
+.Rd 5
+.DS
+lib1.a : $(LIB1OBJS)
+ rm -f $(.TARGET)
+ ar cr $(.TARGET) $(.ALLSRC)
+ ...
+ ranlib $(.TARGET)
+
+lib2.a : $(LIB2OBJS)
+ rm -f $(.TARGET)
+ ar cr $(.TARGET) $(.ALLSRC)
+ ...
+ ranlib $(.TARGET)
+.DE
+.Ix 0 ref variable local .TARGET
+.Ix 0 ref variable local .ALLSRC
+This would save both
+.DS
+ranlib $(.TARGET)
+.DE
+commands until the end, when they would run one after the other
+(using the correct value for the
+.CW .TARGET
+variable, of course).
+.LP
+Commands saved in this manner are only executed if PMake manages to
+re-create everything without an error.
+.xH 2 Target Attributes
+.LP
+PMake allows you to give attributes to targets by means of special
+sources. Like everything else PMake uses, these sources begin with a
+period and are made up of all upper-case letters. There are various
+reasons for using them, and I will try to give examples for most of
+them. Others you'll have to find uses for yourself. Think of it as ``an
+exercise for the reader.'' By placing one (or more) of these as a source on a
+dependency line, you are ``marking the target(s) with that
+attribute.'' That's just the way I phrase it, so you know.
+.LP
+Any attributes given as sources for a transformation rule are applied
+to the target of the transformation rule when the rule is applied.
+.Ix 0 def attributes
+.Ix 0 ref source
+.Ix 0 ref target
+.nr pw 12
+.IP .DONTCARE \n(pw
+.Ix 0 def attributes .DONTCARE
+.Ix 0 def .DONTCARE
+If a target is marked with this attribute and PMake can't figure out
+how to create it, it will ignore this fact and assume the file isn't
+really needed or actually exists and PMake just can't find it. This may prove
+wrong, but the error will be noted later on, not when PMake tries to create
+the target so marked. This attribute also prevents PMake from
+attempting to touch the target if it is given the
+.B \-t
+flag.
+.Ix 0 ref flags -t
+.IP .EXEC \n(pw
+.Ix 0 def attributes .EXEC
+.Ix 0 def .EXEC
+This attribute causes its shell script to be executed while having no
+effect on targets that depend on it. This makes the target into a sort
+of subroutine. An example. Say you have some LISP files that need to
+be compiled and loaded into a LISP process. To do this, you echo LISP
+commands into a file and execute a LISP with this file as its input
+when everything's done. Say also that you have to load other files
+from another system before you can compile your files and further,
+that you don't want to go through the loading and dumping unless one
+of
+.I your
+files has changed. Your makefile might look a little bit
+like this (remember, this is an educational example, and don't worry
+about the
+.CW COMPILE
+rule, all will soon become clear, grasshopper):
+.DS
+system : init a.fasl b.fasl c.fasl
+ for i in $(.ALLSRC);
+ do
+ echo -n '(load "' >> input
+ echo -n ${i} >> input
+ echo '")' >> input
+ done
+ echo '(dump "$(.TARGET)")' >> input
+ lisp < input
+
+a.fasl : a.l init COMPILE
+b.fasl : b.l init COMPILE
+c.fasl : c.l init COMPILE
+COMPILE : .USE
+ echo '(compile "$(.ALLSRC)")' >> input
+init : .EXEC
+ echo '(load-system)' > input
+.DE
+.Ix 0 ref .USE
+.Ix 0 ref attributes .USE
+.Ix 0 ref variable local .ALLSRC
+.IP "\&"
+.CW .EXEC
+sources, don't appear in the local variables of targets that depend on
+them (nor are they touched if PMake is given the
+.B \-t
+flag).
+.Ix 0 ref flags -t
+Note that all the rules, not just that for
+.CW system ,
+include
+.CW init
+as a source. This is because none of the other targets can be made
+until
+.CW init
+has been made, thus they depend on it.
+.IP .EXPORT \n(pw
+.Ix 0 def attributes .EXPORT
+.Ix 0 def .EXPORT
+This is used to mark those targets whose creation should be sent to
+another machine if at all possible. This may be used by some
+exportation schemes if the exportation is expensive. You should ask
+your system administrator if it is necessary.
+.IP .EXPORTSAME \n(pw
+.Ix 0 def attributes .EXPORTSAME
+.Ix 0 def .EXPORTSAME
+Tells the export system that the job should be exported to a machine
+of the same architecture as the current one. Certain operations (e.g.
+running text through
+.CW nroff )
+can be performed the same on any architecture (CPU and
+operating system type), while others (e.g. compiling a program with
+.CW cc )
+must be performed on a machine with the same architecture. Not all
+export systems will support this attribute.
+.IP .IGNORE \n(pw
+.Ix 0 def attributes .IGNORE
+.Ix 0 def .IGNORE attribute
+Giving a target the
+.CW .IGNORE
+attribute causes PMake to ignore errors from any of the target's commands, as
+if they all had `\-' before them.
+.IP .INVISIBLE \n(pw
+.Ix 0 def attributes .INVISIBLE
+.Ix 0 def .INVISIBLE
+This allows you to specify one target as a source for another without
+the one affecting the other's local variables. Useful if, say, you
+have a makefile that creates two programs, one of which is used to
+create the other, so it must exist before the other is created. You
+could say
+.DS
+prog1 : $(PROG1OBJS) prog2 MAKEINSTALL
+prog2 : $(PROG2OBJS) .INVISIBLE MAKEINSTALL
+.DE
+where
+.CW MAKEINSTALL
+is some complex .USE rule (see below) that depends on the
+.Ix 0 ref .USE
+.CW .ALLSRC
+variable containing the right things. Without the
+.CW .INVISIBLE
+attribute for
+.CW prog2 ,
+the
+.CW MAKEINSTALL
+rule couldn't be applied. This is not as useful as it should be, and
+the semantics may change (or the whole thing go away) in the
+not-too-distant future.
+.IP .JOIN \n(pw
+.Ix 0 def attributes .JOIN
+.Ix 0 def .JOIN
+This is another way to avoid performing some operations in parallel
+while permitting everything else to be done so. Specifically it
+forces the target's shell script to be executed only if one or more of the
+sources was out-of-date. In addition, the target's name,
+in both its
+.CW .TARGET
+variable and all the local variables of any target that depends on it,
+is replaced by the value of its
+.CW .ALLSRC
+variable.
+As an example, suppose you have a program that has four libraries that
+compile in the same directory along with, and at the same time as, the
+program. You again have the problem with
+.CW ranlib
+that I mentioned earlier, only this time it's more severe: you
+can't just put the ranlib off to the end since the program
+will need those libraries before it can be re-created. You can do
+something like this:
+.DS
+program : $(OBJS) libraries
+ cc -o $(.TARGET) $(.ALLSRC)
+
+libraries : lib1.a lib2.a lib3.a lib4.a .JOIN
+ ranlib $(.OODATE)
+.DE
+.Ix 0 ref variable local .TARGET
+.Ix 0 ref variable local .ALLSRC
+.Ix 0 ref variable local .OODATE
+.Ix 0 ref .TARGET
+.Ix 0 ref .ALLSRC
+.Ix 0 ref .OODATE
+In this case, PMake will re-create the
+.CW $(OBJS)
+as necessary, along with
+.CW lib1.a ,
+.CW lib2.a ,
+.CW lib3.a
+and
+.CW lib4.a .
+It will then execute
+.CW ranlib
+on any library that was changed and set
+.CW program 's
+.CW .ALLSRC
+variable to contain what's in
+.CW $(OBJS)
+followed by
+.CW "lib1.a lib2.a lib3.a lib4.a" .'' ``
+In case you're wondering, it's called
+.CW .JOIN
+because it joins together different threads of the ``input graph'' at
+the target marked with the attribute.
+Another aspect of the .JOIN attribute is it keeps the target from
+being created if the
+.B \-t
+flag was given.
+.Ix 0 ref flags -t
+.IP .MAKE \n(pw
+.Ix 0 def attributes .MAKE
+.Ix 0 def .MAKE
+The
+.CW .MAKE
+attribute marks its target as being a recursive invocation of PMake.
+This forces PMake to execute the script associated with the target (if
+it's out-of-date) even if you gave the
+.B \-n
+or
+.B \-t
+flag. By doing this, you can start at the top of a system and type
+.DS
+pmake -n
+.DE
+and have it descend the directory tree (if your makefiles are set up
+correctly), printing what it would have executed if you hadn't
+included the
+.B \-n
+flag.
+.IP .NOEXPORT \n(pw
+.Ix 0 def attributes .NOEXPORT
+.Ix 0 def .NOEXPORT attribute
+If possible, PMake will attempt to export the creation of all targets to
+another machine (this depends on how PMake was configured). Sometimes,
+the creation is so simple, it is pointless to send it to another
+machine. If you give the target the
+.CW .NOEXPORT
+attribute, it will be run locally, even if you've given PMake the
+.B "\-L 0"
+flag.
+.IP .NOTMAIN \n(pw
+.Ix 0 def attributes .NOTMAIN
+.Ix 0 def .NOTMAIN
+Normally, if you do not specify a target to make in any other way,
+PMake will take the first target on the first dependency line of a
+makefile as the target to create. That target is known as the ``Main
+Target'' and is labeled as such if you print the dependencies out
+using the
+.B \-p
+flag.
+.Ix 0 ref flags -p
+Giving a target this attribute tells PMake that the target is
+definitely
+.I not
+the Main Target.
+This allows you to place targets in an included makefile and
+have PMake create something else by default.
+.IP .PRECIOUS \n(pw
+.Ix 0 def attributes .PRECIOUS
+.Ix 0 def .PRECIOUS attribute
+When PMake is interrupted (you type control-C at the keyboard), it
+will attempt to clean up after itself by removing any half-made
+targets. If a target has the
+.CW .PRECIOUS
+attribute, however, PMake will leave it alone. An additional side
+effect of the `::' operator is to mark the targets as
+.CW .PRECIOUS .
+.Ix 0 ref operator double-colon
+.Ix 0 ref ::
+.IP .SILENT \n(pw
+.Ix 0 def attributes .SILENT
+.Ix 0 def .SILENT attribute
+Marking a target with this attribute keeps its commands from being
+printed when they're executed, just as if they had an `@' in front of them.
+.IP .USE \n(pw
+.Ix 0 def attributes .USE
+.Ix 0 def .USE
+By giving a target this attribute, you turn it into PMake's equivalent
+of a macro. When the target is used as a source for another target,
+the other target acquires the commands, sources and attributes (except
+.CW .USE )
+of the source.
+If the target already has commands, the
+.CW .USE
+target's commands are added to the end. If more than one .USE-marked
+source is given to a target, the rules are applied sequentially.
+.IP "\&" \n(pw
+The typical .USE rule (as I call them) will use the sources of the
+target to which it is applied (as stored in the
+.CW .ALLSRC
+variable for the target) as its ``arguments,'' if you will.
+For example, you probably noticed that the commands for creating
+.CW lib1.a
+and
+.CW lib2.a
+in the example in section 3.3
+.Rm 5 3.3
+were exactly the same. You can use the
+.CW .USE
+attribute to eliminate the repetition, like so:
+.DS
+lib1.a : $(LIB1OBJS) MAKELIB
+lib2.a : $(LIB2OBJS) MAKELIB
+
+MAKELIB : .USE
+ rm -f $(.TARGET)
+ ar cr $(.TARGET) $(.ALLSRC)
+ ...
+ ranlib $(.TARGET)
+.DE
+.Ix 0 ref variable local .TARGET
+.Ix 0 ref variable local .ALLSRC
+.IP "\&" \n(pw
+Several system makefiles (not to be confused with The System Makefile)
+make use of these .USE rules to make your
+life easier (they're in the default, system makefile directory...take a look).
+Note that the .USE rule source itself
+.CW MAKELIB ) (
+does not appear in any of the targets's local variables.
+There is no limit to the number of times I could use the
+.CW MAKELIB
+rule. If there were more libraries, I could continue with
+.CW "lib3.a : $(LIB3OBJS) MAKELIB" '' ``
+and so on and so forth.
+.xH 2 Special Targets
+.LP
+As there were in Make, so there are certain targets that have special
+meaning to PMake. When you use one on a dependency line, it is the
+only target that may appear on the left-hand-side of the operator.
+.Ix 0 ref target
+.Ix 0 ref operator
+As for the attributes and variables, all the special targets
+begin with a period and consist of upper-case letters only.
+I won't describe them all in detail because some of them are rather
+complex and I'll describe them in more detail than you'll want in
+chapter 4.
+The targets are as follows:
+.nr pw 10
+.IP .BEGIN \n(pw
+.Ix 0 def .BEGIN
+Any commands attached to this target are executed before anything else
+is done. You can use it for any initialization that needs doing.
+.IP .DEFAULT \n(pw
+.Ix 0 def .DEFAULT
+This is sort of a .USE rule for any target (that was used only as a
+source) that PMake can't figure out any other way to create. It's only
+``sort of'' a .USE rule because only the shell script attached to the
+.CW .DEFAULT
+target is used. The
+.CW .IMPSRC
+variable of a target that inherits
+.CW .DEFAULT 's
+commands is set to the target's own name.
+.Ix 0 ref .IMPSRC
+.Ix 0 ref variable local .IMPSRC
+.IP .END \n(pw
+.Ix 0 def .END
+This serves a function similar to
+.CW .BEGIN ,
+in that commands attached to it are executed once everything has been
+re-created (so long as no errors occurred). It also serves the extra
+function of being a place on which PMake can hang commands you put off
+to the end. Thus the script for this target will be executed before
+any of the commands you save with the ``.\|.\|.''.
+.Ix 0 ref ...
+.IP .EXPORT \n(pw
+The sources for this target are passed to the exportation system compiled
+into PMake. Some systems will use these sources to configure
+themselves. You should ask your system administrator about this.
+.IP .IGNORE \n(pw
+.Ix 0 def .IGNORE target
+.Ix 0 ref .IGNORE attribute
+.Ix 0 ref attributes .IGNORE
+This target marks each of its sources with the
+.CW .IGNORE
+attribute. If you don't give it any sources, then it is like
+giving the
+.B \-i
+flag when you invoke PMake \*- errors are ignored for all commands.
+.Ix 0 ref flags -i
+.IP .INCLUDES \n(pw
+.Ix 0 def .INCLUDES target
+.Ix 0 def variable global .INCLUDES
+.Ix 0 def .INCLUDES variable
+The sources for this target are taken to be suffixes that indicate a
+file that can be included in a program source file.
+The suffix must have already been declared with
+.CW .SUFFIXES
+(see below).
+Any suffix so marked will have the directories on its search path
+(see
+.CW .PATH ,
+below) placed in the
+.CW .INCLUDES
+variable, each preceded by a
+.B \-I
+flag. This variable can then be used as an argument for the compiler
+in the normal fashion. The
+.CW .h
+suffix is already marked in this way in the system makefile.
+.Ix 0 ref makefile system
+E.g. if you have
+.DS
+\&.SUFFIXES : .bitmap
+\&.PATH.bitmap : /usr/local/X/lib/bitmaps
+\&.INCLUDES : .bitmap
+.DE
+PMake will place
+.CW "-I/usr/local/X/lib/bitmaps" '' ``
+in the
+.CW .INCLUDES
+variable and you can then say
+.DS
+cc $(.INCLUDES) -c xprogram.c
+.DE
+(Note: the
+.CW .INCLUDES
+variable is not actually filled in until the entire makefile has been read.)
+.IP .INTERRUPT \n(pw
+.Ix 0 def .INTERRUPT
+When PMake is interrupted,
+it will execute the commands in the script for this target, if it
+exists.
+.IP .LIBS \n(pw
+.Ix 0 def .LIBS target
+.Ix 0 def .LIBS variable
+.Ix 0 def variable global .LIBS
+This does for libraries what
+.CW .INCLUDES
+does for include files, except the flag used is
+.B \-L ,
+as required by those linkers that allow you to tell them where to find
+libraries. The variable used is
+.CW .LIBS .
+Be forewarned that PMake may not have been compiled to do this if the
+linker on your system doesn't accept the
+.B \-L
+flag, though the
+.CW .LIBS
+variable will always be defined once the makefile has been read.
+.IP .MAIN \n(pw
+.Ix 0 def .MAIN
+If you didn't give a target (or targets) to create when you invoked
+PMake, it will take the sources of this target as the targets to
+create.
+.IP .MAKEFLAGS \n(pw
+.Ix 0 def .MAKEFLAGS target
+This target provides a way for you to always specify flags for PMake
+when the makefile is used. The flags are just as they would be typed
+to the shell (except you can't use shell variables unless they're in
+the environment),
+though the
+.B \-f
+and
+.B \-r
+flags have no effect.
+.IP .NULL \n(pw
+.Ix 0 def .NULL
+.Ix 0 ref suffix null
+.Ix 0 ref "null suffix"
+This allows you to specify what suffix PMake should pretend a file has
+if, in fact, it has no known suffix. Only one suffix may be so
+designated. The last source on the dependency line is the suffix that
+is used (you should, however, only give one suffix.\|.\|.).
+.IP .PATH \n(pw
+.Ix 0 def .PATH
+If you give sources for this target, PMake will take them as
+directories in which to search for files it cannot find in the current
+directory. If you give no sources, it will clear out any directories
+added to the search path before. Since the effects of this all get
+very complex, I'll leave it til chapter four to give you a complete
+explanation.
+.IP .PATH\fIsuffix\fP \n(pw
+.Ix 0 ref .PATH
+This does a similar thing to
+.CW .PATH ,
+but it does it only for files with the given suffix. The suffix must
+have been defined already. Look at
+.B "Search Paths"
+(section 4.1)
+.Rm 6 4.1
+for more information.
+.IP .PRECIOUS \n(pw
+.Ix 0 def .PRECIOUS target
+.Ix 0 ref .PRECIOUS attribute
+.Ix 0 ref attributes .PRECIOUS
+Similar to
+.CW .IGNORE ,
+this gives the
+.CW .PRECIOUS
+attribute to each source on the dependency line, unless there are no
+sources, in which case the
+.CW .PRECIOUS
+attribute is given to every target in the file.
+.IP .RECURSIVE \n(pw
+.Ix 0 def .RECURSIVE
+.Ix 0 ref attributes .MAKE
+.Ix 0 ref .MAKE
+This target applies the
+.CW .MAKE
+attribute to all its sources. It does nothing if you don't give it any sources.
+.IP .SHELL \n(pw
+.Ix 0 def .SHELL
+PMake is not constrained to only using the Bourne shell to execute
+the commands you put in the makefile. You can tell it some other shell
+to use with this target. Check out
+.B "A Shell is a Shell is a Shell"
+(section 4.4)
+.Rm 7 4.4
+for more information.
+.IP .SILENT \n(pw
+.Ix 0 def .SILENT target
+.Ix 0 ref .SILENT attribute
+.Ix 0 ref attributes .SILENT
+When you use
+.CW .SILENT
+as a target, it applies the
+.CW .SILENT
+attribute to each of its sources. If there are no sources on the
+dependency line, then it is as if you gave PMake the
+.B \-s
+flag and no commands will be echoed.
+.IP .SUFFIXES \n(pw
+.Ix 0 def .SUFFIXES
+This is used to give new file suffixes for PMake to handle. Each
+source is a suffix PMake should recognize. If you give a
+.CW .SUFFIXES
+dependency line with no sources, PMake will forget about all the
+suffixes it knew (this also nukes the null suffix).
+For those targets that need to have suffixes defined, this is how you do it.
+.LP
+In addition to these targets, a line of the form
+.DS
+\fIattribute\fP : \fIsources\fP
+.DE
+applies the
+.I attribute
+to all the targets listed as
+.I sources .
+.xH 2 Modifying Variable Expansion
+.LP
+.Ix 0 def variable expansion modified
+.Ix 0 ref variable expansion
+.Ix 0 def variable modifiers
+Variables need not always be expanded verbatim. PMake defines several
+modifiers that may be applied to a variable's value before it is
+expanded. You apply a modifier by placing it after the variable name
+with a colon between the two, like so:
+.DS
+${\fIVARIABLE\fP:\fImodifier\fP}
+.DE
+Each modifier is a single character followed by something specific to
+the modifier itself.
+You may apply as many modifiers as you want \*- each one is applied to
+the result of the previous and is separated from the previous by
+another colon.
+.LP
+There are seven ways to modify a variable's expansion, most of which
+come from the C shell variable modification characters:
+.RS
+.IP "M\fIpattern\fP"
+.Ix 0 def :M
+.Ix 0 def modifier match
+This is used to select only those words (a word is a series of
+characters that are neither spaces nor tabs) that match the given
+.I pattern .
+The pattern is a wildcard pattern like that used by the shell, where
+.CW *
+means 0 or more characters of any sort;
+.CW ?
+is any single character;
+.CW [abcd]
+matches any single character that is either `a', `b', `c' or `d'
+(there may be any number of characters between the brackets);
+.CW [0-9]
+matches any single character that is between `0' and `9' (i.e. any
+digit. This form may be freely mixed with the other bracket form), and
+`\\' is used to escape any of the characters `*', `?', `[' or `:',
+leaving them as regular characters to match themselves in a word.
+For example, the system makefile
+.CW <makedepend.mk>
+uses
+.CW "$(CFLAGS:M-[ID]*)" '' ``
+to extract all the
+.CW \-I
+and
+.CW \-D
+flags that would be passed to the C compiler. This allows it to
+properly locate include files and generate the correct dependencies.
+.IP "N\fIpattern\fP"
+.Ix 0 def :N
+.Ix 0 def modifier nomatch
+This is identical to
+.CW :M
+except it substitutes all words that don't match the given pattern.
+.IP "S/\fIsearch-string\fP/\fIreplacement-string\fP/[g]"
+.Ix 0 def :S
+.Ix 0 def modifier substitute
+Causes the first occurrence of
+.I search-string
+in the variable to be replaced by
+.I replacement-string ,
+unless the
+.CW g
+flag is given at the end, in which case all occurrences of the string
+are replaced. The substitution is performed on each word in the
+variable in turn. If
+.I search-string
+begins with a
+.CW ^ ,
+the string must match starting at the beginning of the word. If
+.I search-string
+ends with a
+.CW $ ,
+the string must match to the end of the word (these two may be
+combined to force an exact match). If a backslash precedes these two
+characters, however, they lose their special meaning. Variable
+expansion also occurs in the normal fashion inside both the
+.I search-string
+and the
+.I replacement-string ,
+.B except
+that a backslash is used to prevent the expansion of a
+.CW $ ,
+not another dollar sign, as is usual.
+Note that
+.I search-string
+is just a string, not a pattern, so none of the usual
+regular-expression/wildcard characters have any special meaning save
+.CW ^
+and
+.CW $ .
+In the replacement string,
+the
+.CW &
+character is replaced by the
+.I search-string
+unless it is preceded by a backslash.
+You are allowed to use any character except
+colon or exclamation point to separate the two strings. This so-called
+delimiter character may be placed in either string by preceding it
+with a backslash.
+.IP T
+.Ix 0 def :T
+.Ix 0 def modifier tail
+Replaces each word in the variable expansion by its last
+component (its ``tail''). For example, given
+.DS
+OBJS = ../lib/a.o b /usr/lib/libm.a
+TAILS = $(OBJS:T)
+.DE
+the variable
+.CW TAILS
+would expand to
+.CW "a.o b libm.a" .'' ``
+.IP H
+.Ix 0 def :H
+.Ix 0 def modifier head
+This is similar to
+.CW :T ,
+except that every word is replaced by everything but the tail (the
+``head''). Using the same definition of
+.CW OBJS ,
+the string
+.CW "$(OBJS:H)" '' ``
+would expand to
+.CW "../lib /usr/lib" .'' ``
+Note that the final slash on the heads is removed and
+anything without a head is replaced by the empty string.
+.IP E
+.Ix 0 def :E
+.Ix 0 def modifier extension
+.Ix 0 def modifier suffix
+.Ix 0 ref suffix "variable modifier"
+.CW :E
+replaces each word by its suffix (``extension''). So
+.CW "$(OBJS:E)" '' ``
+would give you
+.CW ".o .a" .'' ``
+.IP R
+.Ix 0 def :R
+.Ix 0 def modifier root
+.Ix 0 def modifier base
+This replaces each word by everything but the suffix (the ``root'' of
+the word).
+.CW "$(OBJS:R)" '' ``
+expands to ``
+.CW "../lib/a b /usr/lib/libm" .''
+.RE
+.LP
+In addition, the System V style of substitution is also supported.
+This looks like:
+.DS
+$(\fIVARIABLE\fP:\fIsearch-string\fP=\fIreplacement\fP)
+.DE
+It must be the last modifier in the chain. The search is anchored at
+the end of each word, so only suffixes or whole words may be replaced.
+.xH 2 More on Debugging
+.xH 2 More Exercises
+.IP (3.1)
+You've got a set programs, each of which is created from its own
+assembly-language source file (suffix
+.CW .asm ).
+Each program can be assembled into two versions, one with error-checking
+code assembled in and one without. You could assemble them into files
+with different suffixes
+.CW .eobj \& (
+and
+.CW .obj ,
+for instance), but your linker only understands files that end in
+.CW .obj .
+To top it all off, the final executables
+.I must
+have the suffix
+.CW .exe .
+How can you still use transformation rules to make your life easier
+(Hint: assume the error-checking versions have
+.CW ec
+tacked onto their prefix)?
+.IP (3.2)
+Assume, for a moment or two, you want to perform a sort of
+``indirection'' by placing the name of a variable into another one,
+then you want to get the value of the first by expanding the second
+somehow. Unfortunately, PMake doesn't allow constructs like
+.DS I
+$($(FOO))
+.DE
+What do you do? Hint: no further variable expansion is performed after
+modifiers are applied, thus if you cause a $ to occur in the
+expansion, that's what will be in the result.
+.xH 1 PMake for Gods
+.LP
+This chapter is devoted to those facilities in PMake that allow you to
+do a great deal in a makefile with very little work, as well as do
+some things you couldn't do in Make without a great deal of work (and
+perhaps the use of other programs). The problem with these features,
+is they must be handled with care, or you will end up with a mess.
+.LP
+Once more, I assume a greater familiarity with
+.UX
+or Sprite than I did in the previous two chapters.
+.xH 2 Search Paths
+.Rd 6
+.LP
+PMake supports the dispersal of files into multiple directories by
+allowing you to specify places to look for sources with
+.CW .PATH
+targets in the makefile. The directories you give as sources for these
+targets make up a ``search path.'' Only those files used exclusively
+as sources are actually sought on a search path, the assumption being
+that anything listed as a target in the makefile can be created by the
+makefile and thus should be in the current directory.
+.LP
+There are two types of search paths
+in PMake: one is used for all types of files (including included
+makefiles) and is specified with a plain
+.CW .PATH
+target (e.g.
+.CW ".PATH : RCS" ''), ``
+while the other is specific to a certain type of file, as indicated by
+the file's suffix. A specific search path is indicated by immediately following
+the
+.CW .PATH
+with the suffix of the file. For instance
+.DS
+\&.PATH.h : /sprite/lib/include /sprite/att/lib/include
+.DE
+would tell PMake to look in the directories
+.CW /sprite/lib/include
+and
+.CW /sprite/att/lib/include
+for any files whose suffix is
+.CW .h .
+.LP
+The current directory is always consulted first to see if a file
+exists. Only if it cannot be found there are the directories in the
+specific search path, followed by those in the general search path,
+consulted.
+.LP
+A search path is also used when expanding wildcard characters. If the
+pattern has a recognizable suffix on it, the path for that suffix will
+be used for the expansion. Otherwise the default search path is employed.
+.LP
+When a file is found in some directory other than the current one, all
+local variables that would have contained the target's name
+.CW .ALLSRC , (
+and
+.CW .IMPSRC )
+will instead contain the path to the file, as found by PMake.
+Thus if you have a file
+.CW ../lib/mumble.c
+and a makefile
+.DS
+\&.PATH.c : ../lib
+mumble : mumble.c
+ $(CC) -o $(.TARGET) $(.ALLSRC)
+.DE
+the command executed to create
+.CW mumble
+would be
+.CW "cc -o mumble ../lib/mumble.c" .'' ``
+(As an aside, the command in this case isn't strictly necessary, since
+it will be found using transformation rules if it isn't given. This is because
+.CW .out
+is the null suffix by default and a transformation exists from
+.CW .c
+to
+.CW .out .
+Just thought I'd throw that in.)
+.LP
+If a file exists in two directories on the same search path, the file
+in the first directory on the path will be the one PMake uses. So if
+you have a large system spread over many directories, it would behoove
+you to follow a naming convention that avoids such conflicts.
+.LP
+Something you should know about the way search paths are implemented
+is that each directory is read, and its contents cached, exactly once
+\&\*- when it is first encountered \*- so any changes to the
+directories while PMake is running will not be noted when searching
+for implicit sources, nor will they be found when PMake attempts to
+discover when the file was last modified, unless the file was created in the
+current directory. While people have suggested that PMake should read
+the directories each time, my experience suggests that the caching seldom
+causes problems. In addition, not caching the directories slows things
+down enormously because of PMake's attempts to apply transformation
+rules through non-existent files \*- the number of extra file-system
+searches is truly staggering, especially if many files without
+suffixes are used and the null suffix isn't changed from
+.CW .out .
+.xH 2 Archives and Libraries
+.LP
+.UX
+and Sprite allow you to merge files into an archive using the
+.CW ar
+command. Further, if the files are relocatable object files, you can
+run
+.CW ranlib
+on the archive and get yourself a library that you can link into any
+program you want. The main problem with archives is they double the
+space you need to store the archived files, since there's one copy in
+the archive and one copy out by itself. The problem with libraries is
+you usually think of them as
+.CW -lm
+rather than
+.CW /usr/lib/libm.a
+and the linker thinks they're out-of-date if you so much as look at
+them.
+.LP
+PMake solves the problem with archives by allowing you to tell it to
+examine the files in the archives (so you can remove the individual
+files without having to regenerate them later). To handle the problem
+with libraries, PMake adds an additional way of deciding if a library
+is out-of-date:
+.IP \(bu 2
+If the table of contents is older than the library, or is missing, the
+library is out-of-date.
+.LP
+A library is any target that looks like
+.CW \-l name'' ``
+or that ends in a suffix that was marked as a library using the
+.CW .LIBS
+target.
+.CW .a
+is so marked in the system makefile.
+.LP
+Members of an archive are specified as
+``\fIarchive\fP(\fImember\fP[ \fImember\fP...])''.
+Thus
+.CW libdix.a(window.o) '' ``'
+specifies the file
+.CW window.o
+in the archive
+.CW libdix.a .
+You may also use wildcards to specify the members of the archive. Just
+remember that most the wildcard characters will only find
+.I existing
+files.
+.LP
+A file that is a member of an archive is treated specially. If the
+file doesn't exist, but it is in the archive, the modification time
+recorded in the archive is used for the file when determining if the
+file is out-of-date. When figuring out how to make an archived member target
+(not the file itself, but the file in the archive \*- the
+\fIarchive\fP(\fImember\fP) target), special care is
+taken with the transformation rules, as follows:
+.IP \(bu 2
+\&\fIarchive\fP(\fImember\fP) is made to depend on \fImember\fP.
+.IP \(bu 2
+The transformation from the \fImember\fP's suffix to the
+\fIarchive\fP's suffix is applied to the \fIarchive\fP(\fImember\fP) target.
+.IP \(bu 2
+The \fIarchive\fP(\fImember\fP)'s
+.CW .TARGET
+variable is set to the name of the \fImember\fP if \fImember\fP is
+actually a target, or the path to the member file if \fImember\fP is
+only a source.
+.IP \(bu 2
+The
+.CW .ARCHIVE
+variable for the \fIarchive\fP(\fImember\fP) target is set to the name
+of the \fIarchive\fP.
+.Ix 0 def variable local .ARCHIVE
+.Ix 0 def .ARCHIVE
+.IP \(bu 2
+The
+.CW .MEMBER
+variable is set to the actual string inside the parentheses. In most
+cases, this will be the same as the
+.CW .TARGET
+variable.
+.Ix 0 def variable local .MEMBER
+.Ix 0 def .MEMBER
+.IP \(bu 2
+The \fIarchive\fP(\fImember\fP)'s place in the local variables of the
+targets that depend on it is taken by the value of its
+.CW .TARGET
+variable.
+.LP
+Thus, a program library could be created with the following makefile:
+.DS
+\&.o.a :
+ ...
+ rm -f $(.TARGET:T)
+OBJS = obj1.o obj2.o obj3.o
+libprog.a : libprog.a($(OBJS))
+ ar cru $(.TARGET) $(.OODATE)
+ ranlib $(.TARGET)
+.DE
+This will cause the three object files to be compiled (if the
+corresponding source files were modified after the object file or, if
+that doesn't exist, the archived object file), the out-of-date ones
+archived in
+.CW libprog.a ,
+a table of contents placed in the archive and the newly-archived
+object files to be removed.
+.LP
+All this is used in the
+.CW makelib.mk
+system makefile to create a single library with ease. This makefile
+looks like this:
+.DS
+.SM
+#
+# Rules for making libraries. The object files that make up the library
+# are removed once they are archived.
+#
+# To make several libraries in parallel, you should define the variable
+# "many_libraries". This will serialize the invocations of ranlib.
+#
+# To use, do something like this:
+#
+# OBJECTS = <files in the library>
+#
+# fish.a: fish.a($(OBJECTS)) MAKELIB
+#
+#
+
+#ifndef _MAKELIB_MK
+_MAKELIB_MK =
+
+#include <po.mk>
+
+\&.po.a .o.a :
+ ...
+ rm -f $(.MEMBER)
+
+ARFLAGS ?= crl
+
+#
+# Re-archive the out-of-date members and recreate the library's table of
+# contents using ranlib. If many_libraries is defined, put the ranlib
+# off til the end so many libraries can be made at once.
+#
+MAKELIB : .USE .PRECIOUS
+ ar $(ARFLAGS) $(.TARGET) $(.OODATE)
+#ifndef no_ranlib
+# ifdef many_libraries
+ ...
+# endif /* many_libraries */
+ ranlib $(.TARGET)
+#endif /* no_ranlib */
+
+#endif /* _MAKELIB_MK */
+.DE
+.xH 2 On the Condition...
+.Rd 1
+.LP
+Like the C compiler before it, PMake allows you to configure the makefile,
+based on the current environment, using conditional statements. A
+conditional looks like this:
+.DS
+#if \fIboolean expression\fP
+\fIlines\fP
+#elif \fIanother boolean expression\fP
+\fImore lines\fP
+#else
+\fIstill more lines\fP
+#endif
+.DE
+They may be nested to a maximum depth of 30 and may occur anywhere
+(except in a comment, of course). The
+.CW # '' ``
+must the very first character on the line.
+.LP
+Each
+.I "boolean expression"
+is made up of terms that look like function calls, the standard C
+boolean operators
+.CW && ,
+.CW || ,
+and
+.CW ! ,
+and the standard relational operators
+.CW == ,
+.CW != ,
+.CW > ,
+.CW >= ,
+.CW < ,
+and
+.CW <= ,
+with
+.CW ==
+and
+.CW !=
+being overloaded to allow string comparisons as well.
+.CW &&
+represents logical AND;
+.CW ||
+is logical OR and
+.CW !
+is logical NOT. The arithmetic and string operators take precedence
+over all three of these operators, while NOT takes precedence over
+AND, which takes precedence over OR. This precedence may be
+overridden with parentheses, and an expression may be parenthesized to
+your heart's content. Each term looks like a call on one of four
+functions:
+.nr pw 9
+.Ix 0 def make
+.Ix 0 def conditional make
+.Ix 0 def if make
+.IP make \n(pw
+The syntax is
+.CW make( \fItarget\fP\c
+.CW )
+where
+.I target
+is a target in the makefile. This is true if the given target was
+specified on the command line, or as the source for a
+.CW .MAIN
+target (note that the sources for
+.CW .MAIN
+are only used if no targets were given on the command line).
+.IP defined \n(pw
+.Ix 0 def defined
+.Ix 0 def conditional defined
+.Ix 0 def if defined
+The syntax is
+.CW defined( \fIvariable\fP\c
+.CW )
+and is true if
+.I variable
+is defined. Certain variables are defined in the system makefile that
+identify the system on which PMake is being run.
+.IP exists \n(pw
+.Ix 0 def exists
+.Ix 0 def conditional exists
+.Ix 0 def if exists
+The syntax is
+.CW exists( \fIfile\fP\c
+.CW )
+and is true if the file can be found on the global search path
+(i.e. that defined by
+.CW .PATH
+targets, not by
+.CW .PATH \fIsuffix\fP
+targets).
+.IP empty \n(pw
+.Ix 0 def empty
+.Ix 0 def conditional empty
+.Ix 0 def if empty
+This syntax is much like the others, except the string inside the
+parentheses is of the same form as you would put between parentheses
+when expanding a variable, complete with modifiers and everything. The
+function returns true if the resulting string is empty (NOTE: an undefined
+variable in this context will cause at the very least a warning
+message about a malformed conditional, and at the worst will cause the
+process to stop once it has read the makefile. If you want to check
+for a variable being defined or empty, use the expression
+.CW !defined( \fIvar\fP\c ``
+.CW ") || empty(" \fIvar\fP\c
+.CW ) ''
+as the definition of
+.CW ||
+will prevent the
+.CW empty()
+from being evaluated and causing an error, if the variable is
+undefined). This can be used to see if a variable contains a given
+word, for example:
+.DS
+#if !empty(\fIvar\fP:M\fIword\fP)
+.DE
+.LP
+The arithmetic and string operators may only be used to test the value
+of a variable. The lefthand side must contain the variable expansion,
+while the righthand side contains either a string, enclosed in
+double-quotes, or a number. The standard C numeric conventions (except
+for specifying an octal number) apply to both sides. E.g.
+.DS
+#if $(OS) == 4.3
+
+#if $(MACHINE) == "sun3"
+
+#if $(LOAD_ADDR) < 0xc000
+.DE
+are all valid conditionals. In addition, the numeric value of a
+variable can be tested as a boolean as follows:
+.DS
+#if $(LOAD)
+.DE
+would see if
+.CW LOAD
+contains a non-zero value and
+.DS
+#if !$(LOAD)
+.DE
+would test if
+.CW LOAD
+contains a zero value.
+.LP
+In addition to the bare
+.CW #if ,'' ``
+there are other forms that apply one of the first two functions to each
+term. They are as follows:
+.DS
+ ifdef \fRdefined\fP
+ ifndef \fR!defined\fP
+ ifmake \fRmake\fP
+ ifnmake \fR!make\fP
+.DE
+There are also the ``else if'' forms:
+.CW elif ,
+.CW elifdef ,
+.CW elifndef ,
+.CW elifmake ,
+and
+.CW elifnmake .
+.LP
+For instance, if you wish to create two versions of a program, one of which
+is optimized (the production version) and the other of which is for debugging
+(has symbols for dbx), you have two choices: you can create two
+makefiles, one of which uses the
+.CW \-g
+flag for the compilation, while the other uses the
+.CW \-O
+flag, or you can use another target (call it
+.CW debug )
+to create the debug version. The construct below will take care of
+this for you. I have also made it so defining the variable
+.CW DEBUG
+(say with
+.CW "pmake -D DEBUG" )
+will also cause the debug version to be made.
+.DS
+#if defined(DEBUG) || make(debug)
+CFLAGS += -g
+#else
+CFLAGS += -O
+#endif
+.DE
+There are, of course, problems with this approach. The most glaring
+annoyance is that if you want to go from making a debug version to
+making a production version, you have to remove all the object files,
+or you will get some optimized and some debug versions in the same
+program. Another annoyance is you have to be careful not to make two
+targets that ``conflict'' because of some conditionals in the
+makefile. For instance
+.DS
+#if make(print)
+FORMATTER = ditroff -Plaser_printer
+#endif
+#if make(draft)
+FORMATTER = nroff -Pdot_matrix_printer
+#endif
+.DE
+would wreak havoc if you tried
+.CW "pmake draft print" '' ``
+since you would use the same formatter for each target. As I said,
+this all gets somewhat complicated.
+.xH 2 A Shell is a Shell is a Shell
+.Rd 7
+.LP
+In normal operation, the Bourne Shell (better known as
+.CW sh '') ``
+is used to execute the commands to re-create targets. PMake also allows you
+to specify a different shell for it to use when executing these
+commands. There are several things PMake must know about the shell you
+wish to use. These things are specified as the sources for the
+.CW .SHELL
+.Ix 0 ref .SHELL
+.Ix 0 ref target .SHELL
+target by keyword, as follows:
+.IP "\fBpath=\fP\fIpath\fP"
+PMake needs to know where the shell actually resides, so it can
+execute it. If you specify this and nothing else, PMake will use the
+last component of the path and look in its table of the shells it
+knows and use the specification it finds, if any. Use this if you just
+want to use a different version of the Bourne or C Shell (yes, PMake knows
+how to use the C Shell too).
+.IP "\fBname=\fP\fIname\fP"
+This is the name by which the shell is to be known. It is a single
+word and, if no other keywords are specified (other than
+.B path ),
+it is the name by which PMake attempts to find a specification for
+it (as mentioned above). You can use this if you would just rather use
+the C Shell than the Bourne Shell
+.CW ".SHELL: name=csh" '' (``
+will do it).
+.IP "\fBquiet=\fP\fIecho-off command\fP"
+As mentioned before, PMake actually controls whether commands are
+printed by introducing commands into the shell's input stream. This
+keyword, and the next two, control what those commands are. The
+.B quiet
+keyword is the command used to turn echoing off. Once it is turned
+off, echoing is expected to remain off until the echo-on command is given.
+.IP "\fBecho=\fP\fIecho-on command\fP"
+The command PMake should give to turn echoing back on again.
+.IP "\fBfilter=\fP\fIprinted echo-off command\fP"
+Many shells will echo the echo-off command when it is given. This
+keyword tells PMake in what format the shell actually prints the
+echo-off command. Wherever PMake sees this string in the shell's
+output, it will delete it and any following whitespace, up to and
+including the next newline. See the example at the end of this section
+for more details.
+.IP "\fBechoFlag=\fP\fIflag to turn echoing on\fP"
+Unless a target has been marked
+.CW .SILENT ,
+PMake wants to start the shell running with echoing on. To do this, it
+passes this flag to the shell as one of its arguments. If either this
+or the next flag begins with a `\-', the flags will be passed to the
+shell as separate arguments. Otherwise, the two will be concatenated
+(if they are used at the same time, of course).
+.IP "\fBerrFlag=\fP\fIflag to turn error checking on\fP"
+Likewise, unless a target is marked
+.CW .IGNORE ,
+PMake wishes error-checking to be on from the very start. To this end,
+it will pass this flag to the shell as an argument. The same rules for
+an initial `\-' apply as for the
+.B echoFlag .
+.IP "\fBcheck=\fP\fIcommand to turn error checking on\fP"
+Just as for echo-control, error-control is achieved by inserting
+commands into the shell's input stream. This is the command to make
+the shell check for errors. It also serves another purpose if the
+shell doesn't have error-control as commands, but I'll get into that
+in a minute. Again, once error checking has been turned on, it is
+expected to remain on until it is turned off again.
+.IP "\fBignore=\fP\fIcommand to turn error checking off\fP"
+This is the command PMake uses to turn error checking off. It has
+another use if the shell doesn't do error-control, but I'll tell you
+about that.\|.\|.\|now.
+.IP "\fBhasErrCtl=\fP\fIyes or no\fP"
+This takes a value that is either
+.B yes
+or
+.B no .
+Now you might think that the existence of the
+.B check
+and
+.B ignore
+keywords would be enough to tell PMake if the shell can do
+error-control, but you'd be wrong. If
+.B hasErrCtl
+is
+.B yes ,
+PMake uses the check and ignore commands in a straight-forward manner.
+If this is
+.B no ,
+however, their use is rather different. In this case, the check
+command is used as a template, in which the string
+.B %s
+is replaced by the command that's about to be executed, to produce a
+command for the shell that will echo the command to be executed. The
+ignore command is also used as a template, again with
+.B %s
+replaced by the command to be executed, to produce a command that will
+execute the command to be executed and ignore any error it returns.
+When these strings are used as templates, you must provide newline(s)
+.CW \en '') (``
+in the appropriate place(s).
+.LP
+The strings that follow these keywords may be enclosed in single or
+double quotes (the quotes will be stripped off) and may contain the
+usual C backslash-characters (\en is newline, \er is return, \eb is
+backspace, \e' escapes a single-quote inside single-quotes, \e"
+escapes a double-quote inside double-quotes). Now for an example.
+.LP
+This is actually the contents of the
+.CW <shx.mk>
+system makefile, and causes PMake to use the Bourne Shell in such a
+way that each command is printed as it is executed. That is, if more
+than one command is given on a line, each will be printed separately.
+Similarly, each time the body of a loop is executed, the commands
+within that loop will be printed, etc. The specification runs like
+this:
+.DS
+#
+# This is a shell specification to have the Bourne shell echo
+# the commands just before executing them, rather than when it reads
+# them. Useful if you want to see how variables are being expanded, etc.
+#
+\&.SHELL : path=/bin/sh \e
+ quiet="set -" \e
+ echo="set -x" \e
+ filter="+ set - " \e
+ echoFlag=x \e
+ errFlag=e \e
+ hasErrCtl=yes \e
+ check="set -e" \e
+ ignore="set +e"
+.DE
+.LP
+It tells PMake the following:
+.Bp
+The shell is located in the file
+.CW /bin/sh .
+It need not tell PMake that the name of the shell is
+.CW sh
+as PMake can figure that out for itself (it's the last component of
+the path).
+.Bp
+The command to stop echoing is
+.CW "set -" .
+.Bp
+The command to start echoing is
+.CW "set -x" .
+.Bp
+When the echo off command is executed, the shell will print
+.CW "+ set - "
+(The `+' comes from using the
+.CW \-x
+flag (rather than the
+.CW \-v
+flag PMake usually uses)). PMake will remove all occurrences of this
+string from the output, so you don't notice extra commands you didn't
+put there.
+.Bp
+The flag the Bourne Shell will take to start echoing in this way is
+the
+.CW \-x
+flag. The Bourne Shell will only take its flag arguments concatenated
+as its first argument, so neither this nor the
+.B errFlag
+specification begins with a \-.
+.Bp
+The flag to use to turn error-checking on from the start is
+.CW \-e .
+.Bp
+The shell can turn error-checking on and off, and the commands to do
+so are
+.CW "set +e"
+and
+.CW "set -e" ,
+respectively.
+.LP
+I should note that this specification is for Bourne Shells that are
+not part of Berkeley
+.UX ,
+as shells from Berkeley don't do error control. You can get a similar
+effect, however, by changing the last three lines to be:
+.DS
+ hasErrCtl=no \e
+ check="echo \e"+ %s\e"\en" \e
+ ignore="sh -c '%s || exit 0\en"
+.DE
+.LP
+This will cause PMake to execute the two commands
+.DS
+echo "+ \fIcmd\fP"
+sh -c '\fIcmd\fP || true'
+.DE
+for each command for which errors are to be ignored. (In case you are
+wondering, the thing for
+.CW ignore
+tells the shell to execute another shell without error checking on and
+always exit 0, since the
+.B ||
+causes the
+.CW "exit 0"
+to be executed only if the first command exited non-zero, and if the
+first command exited zero, the shell will also exit zero, since that's
+the last command it executed).
+.xH 2 Compatibility
+.Ix 0 ref compatibility
+.LP
+There are three (well, 3 \(12) levels of backwards-compatibility built
+into PMake. Most makefiles will need none at all. Some may need a
+little bit of work to operate correctly when run in parallel. Each
+level encompasses the previous levels (e.g.
+.B \-B
+(one shell per command) implies
+.B \-V )
+The three levels are described in the following three sections.
+.xH 3 DEFCON 3 \*- Variable Expansion
+.Ix 0 ref compatibility
+.LP
+As noted before, PMake will not expand a variable unless it knows of a
+value for it. This can cause problems for makefiles that expect to
+leave variables undefined except in special circumstances (e.g. if
+more flags need to be passed to the C compiler or the output from a
+text processor should be sent to a different printer). If the
+variables are enclosed in curly braces
+.CW ${PRINTER} ''), (``
+the shell will let them pass. If they are enclosed in parentheses,
+however, the shell will declare a syntax error and the make will come
+to a grinding halt.
+.LP
+You have two choices: change the makefile to define the variables
+(their values can be overridden on the command line, since that's
+where they would have been set if you used Make, anyway) or always give the
+.B \-V
+flag (this can be done with the
+.CW .MAKEFLAGS
+target, if you want).
+.xH 3 DEFCON 2 \*- The Number of the Beast
+.Ix 0 ref compatibility
+.LP
+Then there are the makefiles that expect certain commands, such as
+changing to a different directory, to not affect other commands in a
+target's creation script. You can solve this is either by going
+back to executing one shell per command (which is what the
+.B \-B
+flag forces PMake to do), which slows the process down a good bit and
+requires you to use semicolons and escaped newlines for shell constructs, or
+by changing the makefile to execute the offending command(s) in a subshell
+(by placing the line inside parentheses), like so:
+.DS
+install :: .MAKE
+ (cd src; $(.PMAKE) install)
+ (cd lib; $(.PMAKE) install)
+ (cd man; $(.PMAKE) install)
+.DE
+.Ix 0 ref operator double-colon
+.Ix 0 ref variable global .PMAKE
+.Ix 0 ref .PMAKE
+.Ix 0 ref .MAKE
+.Ix 0 ref attribute .MAKE
+This will always execute the three makes (even if the
+.B \-n
+flag was given) because of the combination of the ``::'' operator and
+the
+.CW .MAKE
+attribute. Each command will change to the proper directory to perform
+the install, leaving the main shell in the directory in which it started.
+.xH 3 "DEFCON 1 \*- Imitation is the Not the Highest Form of Flattery"
+.Ix 0 ref compatibility
+.LP
+The final category of makefile is the one where every command requires
+input, the dependencies are incompletely specified, or you simply
+cannot create more than one target at a time, as mentioned earlier. In
+addition, you may not have the time or desire to upgrade the makefile
+to run smoothly with PMake. If you are the conservative sort, this is
+the compatibility mode for you. It is entered either by giving PMake
+the
+.B \-M
+flag (for Make), or by executing PMake as
+.CW make .'' ``
+In either case, PMake performs things exactly like Make (while still
+supporting most of the nice new features PMake provides). This
+includes:
+.IP \(bu 2
+No parallel execution.
+.IP \(bu 2
+Targets are made in the exact order specified by the makefile. The
+sources for each target are made in strict left-to-right order, etc.
+.IP \(bu 2
+A single Bourne shell is used to execute each command, thus the
+shell's
+.CW $$
+variable is useless, changing directories doesn't work across command
+lines, etc.
+.IP \(bu 2
+If no special characters exist in a command line, PMake will break the
+command into words itself and execute the command directly, without
+executing a shell first. The characters that cause PMake to execute a
+shell are:
+.CW # ,
+.CW = ,
+.CW | ,
+.CW ^ ,
+.CW ( ,
+.CW ) ,
+.CW { ,
+.CW } ,
+.CW ; ,
+.CW & ,
+.CW < ,
+.CW > ,
+.CW * ,
+.CW ? ,
+.CW [ ,
+.CW ] ,
+.CW : ,
+.CW $ ,
+.CW ` ,
+and
+.CW \e .
+You should notice that these are all the characters that are given
+special meaning by the shell (except
+.CW '
+and
+.CW " ,
+which PMake deals with all by its lonesome).
+.IP \(bu 2
+The use of the null suffix is turned off.
+.Ix 0 ref "null suffix"
+.Ix 0 ref suffix null
+.xH 2 The Way Things Work
+.LP
+When PMake reads the makefile, it parses sources and targets into
+nodes in a graph. The graph is directed only in the sense that PMake
+knows which way is up. Each node contains not only links to all its
+parents and children (the nodes that depend on it and those on which
+it depends, respectively), but also a count of the number of its
+children that have already been processed.
+.LP
+The most important thing to know about how PMake uses this graph is
+that the traversal is breadth-first and occurs in two passes.
+.LP
+After PMake has parsed the makefile, it begins with the nodes the user
+has told it to make (either on the command line, or via a
+.CW .MAIN
+target, or by the target being the first in the file not labeled with
+the
+.CW .NOTMAIN
+attribute) placed in a queue. It continues to take the node off the
+front of the queue, mark it as something that needs to be made, pass
+the node to
+.CW Suff_FindDeps
+(mentioned earlier) to find any implicit sources for the node, and
+place all the node's children that have yet to be marked at the end of
+the queue. If any of the children is a
+.CW .USE
+rule, its attributes are applied to the parent, then its commands are
+appended to the parent's list of commands and its children are linked
+to its parent. The parent's unmade children counter is then decremented
+(since the
+.CW .USE
+node has been processed). You will note that this allows a
+.CW .USE
+node to have children that are
+.CW .USE
+nodes and the rules will be applied in sequence.
+If the node has no children, it is placed at the end of
+another queue to be examined in the second pass. This process
+continues until the first queue is empty.
+.LP
+At this point, all the leaves of the graph are in the examination
+queue. PMake removes the node at the head of the queue and sees if it
+is out-of-date. If it is, it is passed to a function that will execute
+the commands for the node asynchronously. When the commands have
+completed, all the node's parents have their unmade children counter
+decremented and, if the counter is then 0, they are placed on the
+examination queue. Likewise, if the node is up-to-date. Only those
+parents that were marked on the downward pass are processed in this
+way. Thus PMake traverses the graph back up to the nodes the user
+instructed it to create. When the examination queue is empty and no
+shells are running to create a target, PMake is finished.
+.LP
+Once all targets have been processed, PMake executes the commands
+attached to the
+.CW .END
+target, either explicitly or through the use of an ellipsis in a shell
+script. If there were no errors during the entire process but there
+are still some targets unmade (PMake keeps a running count of how many
+targets are left to be made), there is a cycle in the graph. PMake does
+a depth-first traversal of the graph to find all the targets that
+weren't made and prints them out one by one.
+.xH 1 Answers to Exercises
+.IP (3.1)
+This is something of a trick question, for which I apologize. The
+trick comes from the UNIX definition of a suffix, which PMake doesn't
+necessarily share. You will have noticed that all the suffixes used in
+this tutorial (and in UNIX in general) begin with a period
+.CW .ms , (
+.CW .c ,
+etc.). Now, PMake's idea of a suffix is more like English's: it's the
+characters at the end of a word. With this in mind, one possible
+.Ix 0 def suffix
+solution to this problem goes as follows:
+.DS I
+\&.SUFFIXES : ec.exe .exe ec.obj .obj .asm
+ec.objec.exe .obj.exe :
+ link -o $(.TARGET) $(.IMPSRC)
+\&.asmec.obj :
+ asm -o $(.TARGET) -DDO_ERROR_CHECKING $(.IMPSRC)
+\&.asm.obj :
+ asm -o $(.TARGET) $(.IMPSRC)
+.DE
+.IP (3.2)
+The trick to this one lies in the ``:='' variable-assignment operator
+and the ``:S'' variable-expansion modifier.
+.Ix 0 ref variable assignment expanded
+.Ix 0 ref variable expansion modified
+.Ix 0 ref modifier substitute
+.Ix 0 ref :S
+.Ix 0 ref :=
+Basically what you want is to take the pointer variable, so to speak,
+and transform it into an invocation of the variable at which it
+points. You might try something like
+.DS I
+$(PTR:S/^/\e$(/:S/$/))
+.DE
+which places
+.CW $( '' ``
+at the front of the variable name and
+.CW ) '' ``
+at the end, thus transforming
+.CW VAR ,'' ``
+for example, into
+.CW $(VAR) ,'' ``
+which is just what we want. Unfortunately (as you know if you've tried
+it), since, as it says in the hint, PMake does no further substitution
+on the result of a modified expansion, that's \fIall\fP you get. The
+solution is to make use of ``:='' to place that string into yet
+another variable, then invoke the other variable directly:
+.DS I
+*PTR := $(PTR:S/^/\e$(/:S/$/)/)
+.DE
+You can then use
+.CW $(*PTR) '' ``
+to your heart's content.
+.de Gp
+.XP
+\&\fB\\$1:\fP
+..
+.xH 1 Glossary of Jargon
+.Gp "attribute"
+A property given to a target that causes PMake to treat it differently.
+.Gp "command script"
+The lines immediately following a dependency line that specify
+commands to execute to create each of the targets on the dependency
+line. Each line in the command script must begin with a tab.
+.Gp "command-line variable"
+A variable defined in an argument when PMake is first executed.
+Overrides all assignments to the same variable name in the makefile.
+.Gp "conditional"
+A construct much like that used in C that allows a makefile to be
+configured on the fly based on the local environment, or on what is being
+made by that invocation of PMake.
+.Gp "creation script"
+Commands used to create a target. See ``command script.''
+.Gp "dependency"
+The relationship between a source and a target. This comes in three
+flavors, as indicated by the operator between the target and the
+source. `:' gives a straight time-wise dependency (if the target is
+older than the source, the target is out-of-date), while `!' provides
+simply an ordering and always considers the target out-of-date. `::'
+is much like `:', save it creates multiple instances of a target each
+of which depends on its own list of sources.
+.Gp "dynamic source"
+This refers to a source that has a local variable invocation in it. It
+allows a single dependency line to specify a different source for each
+target on the line.
+.Gp "global variable"
+Any variable defined in a makefile. Takes precedence over variables
+defined in the environment, but not over command-line or local variables.
+.Gp "input graph"
+What PMake constructs from a makefile. Consists of nodes made of the
+targets in the makefile, and the links between them (the
+dependencies). The links are directed (from source to target) and
+there may not be any cycles (loops) in the graph.
+.Gp "local variable"
+A variable defined by PMake visible only in a target's shell script.
+There are seven local variables, not all of which are defined for
+every target:
+.CW .TARGET ,
+.CW .ALLSRC ,
+.CW .OODATE ,
+.CW .PREFIX ,
+.CW .IMPSRC ,
+.CW .ARCHIVE ,
+and
+.CW .MEMBER .
+.CW .TARGET ,
+.CW .PREFIX ,
+.CW .ARCHIVE ,
+and
+.CW .MEMBER
+may be used on dependency lines to create ``dynamic sources.''
+.Gp "makefile"
+A file that describes how a system is built. If you don't know what it
+is after reading this tutorial.\|.\|.\|.
+.Gp "modifier"
+A letter, following a colon, used to alter how a variable is expanded.
+It has no effect on the variable itself.
+.Gp "operator"
+What separates a source from a target (on a dependency line) and specifies
+the relationship between the two. There are three:
+.CW : ', `
+.CW :: ', `
+and
+.CW ! '. `
+.Gp "search path"
+A list of directories in which a file should be sought. PMake's view
+of the contents of directories in a search path does not change once
+the makefile has been read. A file is sought on a search path only if
+it is exclusively a source.
+.Gp "shell"
+A program to which commands are passed in order to create targets.
+.Gp "source"
+Anything to the right of an operator on a dependency line. Targets on
+the dependency line are usually created from the sources.
+.Gp "special target"
+A target that causes PMake to do special things when it's encountered.
+.Gp "suffix"
+The tail end of a file name. Usually begins with a period,
+.CW .c
+or
+.CW .ms ,
+e.g.
+.Gp "target"
+A word to the left of the operator on a dependency line. More
+generally, any file that PMake might create. A file may be (and often
+is) both a target and a source (what it is depends on how PMake is
+looking at it at the time \*- sort of like the wave/particle duality
+of light, you know).
+.Gp "transformation rule"
+A special construct in a makefile that specifies how to create a file
+of one type from a file of another, as indicated by their suffixes.
+.Gp "variable expansion"
+The process of substituting the value of a variable for a reference to
+it. Expansion may be altered by means of modifiers.
+.Gp "variable"
+A place in which to store text that may be retrieved later. Also used
+to define the local environment. Conditionals exist that test whether
+a variable is defined or not.
+.bp
+.\" Output table of contents last, with an entry for the index, making
+.\" sure to save and restore the last real page number for the index...
+.nr @n \n(PN+1
+.\" We are not generating an index
+.\" .XS \n(@n
+.\" Index
+.\" .XE
+.nr %% \n%
+.PX
+.nr % \n(%%
diff --git a/usr.bin/make/arch.c b/usr.bin/make/arch.c
new file mode 100644
index 0000000..8241434
--- /dev/null
+++ b/usr.bin/make/arch.c
@@ -0,0 +1,1369 @@
+/* $NetBSD: arch.c,v 1.70 2017/04/16 20:49:09 riastradh Exp $ */
+
+/*
+ * Copyright (c) 1988, 1989, 1990, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Adam de Boor.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+/*
+ * Copyright (c) 1989 by Berkeley Softworks
+ * All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Adam de Boor.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. All advertising materials mentioning features or use of this software
+ * must display the following acknowledgement:
+ * This product includes software developed by the University of
+ * California, Berkeley and its contributors.
+ * 4. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#ifndef MAKE_NATIVE
+static char rcsid[] = "$NetBSD: arch.c,v 1.70 2017/04/16 20:49:09 riastradh Exp $";
+#else
+#include <sys/cdefs.h>
+#ifndef lint
+#if 0
+static char sccsid[] = "@(#)arch.c 8.2 (Berkeley) 1/2/94";
+#else
+__RCSID("$NetBSD: arch.c,v 1.70 2017/04/16 20:49:09 riastradh Exp $");
+#endif
+#endif /* not lint */
+#endif
+
+/*-
+ * arch.c --
+ * Functions to manipulate libraries, archives and their members.
+ *
+ * Once again, cacheing/hashing comes into play in the manipulation
+ * of archives. The first time an archive is referenced, all of its members'
+ * headers are read and hashed and the archive closed again. All hashed
+ * archives are kept on a list which is searched each time an archive member
+ * is referenced.
+ *
+ * The interface to this module is:
+ * Arch_ParseArchive Given an archive specification, return a list
+ * of GNode's, one for each member in the spec.
+ * FAILURE is returned if the specification is
+ * invalid for some reason.
+ *
+ * Arch_Touch Alter the modification time of the archive
+ * member described by the given node to be
+ * the current time.
+ *
+ * Arch_TouchLib Update the modification time of the library
+ * described by the given node. This is special
+ * because it also updates the modification time
+ * of the library's table of contents.
+ *
+ * Arch_MTime Find the modification time of a member of
+ * an archive *in the archive*. The time is also
+ * placed in the member's GNode. Returns the
+ * modification time.
+ *
+ * Arch_MemTime Find the modification time of a member of
+ * an archive. Called when the member doesn't
+ * already exist. Looks in the archive for the
+ * modification time. Returns the modification
+ * time.
+ *
+ * Arch_FindLib Search for a library along a path. The
+ * library name in the GNode should be in
+ * -l<name> format.
+ *
+ * Arch_LibOODate Special function to decide if a library node
+ * is out-of-date.
+ *
+ * Arch_Init Initialize this module.
+ *
+ * Arch_End Cleanup this module.
+ */
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/time.h>
+#include <sys/param.h>
+
+#include <ar.h>
+#include <ctype.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <utime.h>
+
+#include "make.h"
+#include "hash.h"
+#include "dir.h"
+#include "config.h"
+
+#ifdef TARGET_MACHINE
+#undef MAKE_MACHINE
+#define MAKE_MACHINE TARGET_MACHINE
+#endif
+#ifdef TARGET_MACHINE_ARCH
+#undef MAKE_MACHINE_ARCH
+#define MAKE_MACHINE_ARCH TARGET_MACHINE_ARCH
+#endif
+
+static Lst archives; /* Lst of archives we've already examined */
+
+typedef struct Arch {
+ char *name; /* Name of archive */
+ Hash_Table members; /* All the members of the archive described
+ * by <name, struct ar_hdr *> key/value pairs */
+ char *fnametab; /* Extended name table strings */
+ size_t fnamesize; /* Size of the string table */
+} Arch;
+
+static int ArchFindArchive(const void *, const void *);
+#ifdef CLEANUP
+static void ArchFree(void *);
+#endif
+static struct ar_hdr *ArchStatMember(char *, char *, Boolean);
+static FILE *ArchFindMember(char *, char *, struct ar_hdr *, const char *);
+#if defined(__svr4__) || defined(__SVR4) || defined(__ELF__)
+#define SVR4ARCHIVES
+static int ArchSVR4Entry(Arch *, char *, size_t, FILE *);
+#endif
+
+#ifdef CLEANUP
+/*-
+ *-----------------------------------------------------------------------
+ * ArchFree --
+ * Free memory used by an archive
+ *
+ * Results:
+ * None.
+ *
+ * Side Effects:
+ * None.
+ *
+ *-----------------------------------------------------------------------
+ */
+static void
+ArchFree(void *ap)
+{
+ Arch *a = (Arch *)ap;
+ Hash_Search search;
+ Hash_Entry *entry;
+
+ /* Free memory from hash entries */
+ for (entry = Hash_EnumFirst(&a->members, &search);
+ entry != NULL;
+ entry = Hash_EnumNext(&search))
+ free(Hash_GetValue(entry));
+
+ free(a->name);
+ free(a->fnametab);
+ Hash_DeleteTable(&a->members);
+ free(a);
+}
+#endif
+
+
+
+/*-
+ *-----------------------------------------------------------------------
+ * Arch_ParseArchive --
+ * Parse the archive specification in the given line and find/create
+ * the nodes for the specified archive members, placing their nodes
+ * on the given list.
+ *
+ * Input:
+ * linePtr Pointer to start of specification
+ * nodeLst Lst on which to place the nodes
+ * ctxt Context in which to expand variables
+ *
+ * Results:
+ * SUCCESS if it was a valid specification. The linePtr is updated
+ * to point to the first non-space after the archive spec. The
+ * nodes for the members are placed on the given list.
+ *
+ * Side Effects:
+ * Some nodes may be created. The given list is extended.
+ *
+ *-----------------------------------------------------------------------
+ */
+ReturnStatus
+Arch_ParseArchive(char **linePtr, Lst nodeLst, GNode *ctxt)
+{
+ char *cp; /* Pointer into line */
+ GNode *gn; /* New node */
+ char *libName; /* Library-part of specification */
+ char *memName; /* Member-part of specification */
+ char *nameBuf; /* temporary place for node name */
+ char saveChar; /* Ending delimiter of member-name */
+ Boolean subLibName; /* TRUE if libName should have/had
+ * variable substitution performed on it */
+
+ libName = *linePtr;
+
+ subLibName = FALSE;
+
+ for (cp = libName; *cp != '(' && *cp != '\0'; cp++) {
+ if (*cp == '$') {
+ /*
+ * Variable spec, so call the Var module to parse the puppy
+ * so we can safely advance beyond it...
+ */
+ int length;
+ void *freeIt;
+ char *result;
+
+ result = Var_Parse(cp, ctxt, VARF_UNDEFERR|VARF_WANTRES,
+ &length, &freeIt);
+ free(freeIt);
+
+ if (result == var_Error) {
+ return(FAILURE);
+ } else {
+ subLibName = TRUE;
+ }
+
+ cp += length-1;
+ }
+ }
+
+ *cp++ = '\0';
+ if (subLibName) {
+ libName = Var_Subst(NULL, libName, ctxt, VARF_UNDEFERR|VARF_WANTRES);
+ }
+
+
+ for (;;) {
+ /*
+ * First skip to the start of the member's name, mark that
+ * place and skip to the end of it (either white-space or
+ * a close paren).
+ */
+ Boolean doSubst = FALSE; /* TRUE if need to substitute in memName */
+
+ while (*cp != '\0' && *cp != ')' && isspace ((unsigned char)*cp)) {
+ cp++;
+ }
+ memName = cp;
+ while (*cp != '\0' && *cp != ')' && !isspace ((unsigned char)*cp)) {
+ if (*cp == '$') {
+ /*
+ * Variable spec, so call the Var module to parse the puppy
+ * so we can safely advance beyond it...
+ */
+ int length;
+ void *freeIt;
+ char *result;
+
+ result = Var_Parse(cp, ctxt, VARF_UNDEFERR|VARF_WANTRES,
+ &length, &freeIt);
+ free(freeIt);
+
+ if (result == var_Error) {
+ return(FAILURE);
+ } else {
+ doSubst = TRUE;
+ }
+
+ cp += length;
+ } else {
+ cp++;
+ }
+ }
+
+ /*
+ * If the specification ends without a closing parenthesis,
+ * chances are there's something wrong (like a missing backslash),
+ * so it's better to return failure than allow such things to happen
+ */
+ if (*cp == '\0') {
+ printf("No closing parenthesis in archive specification\n");
+ return (FAILURE);
+ }
+
+ /*
+ * If we didn't move anywhere, we must be done
+ */
+ if (cp == memName) {
+ break;
+ }
+
+ saveChar = *cp;
+ *cp = '\0';
+
+ /*
+ * XXX: This should be taken care of intelligently by
+ * SuffExpandChildren, both for the archive and the member portions.
+ */
+ /*
+ * If member contains variables, try and substitute for them.
+ * This will slow down archive specs with dynamic sources, of course,
+ * since we'll be (non-)substituting them three times, but them's
+ * the breaks -- we need to do this since SuffExpandChildren calls
+ * us, otherwise we could assume the thing would be taken care of
+ * later.
+ */
+ if (doSubst) {
+ char *buf;
+ char *sacrifice;
+ char *oldMemName = memName;
+ size_t sz;
+
+ memName = Var_Subst(NULL, memName, ctxt,
+ VARF_UNDEFERR|VARF_WANTRES);
+
+ /*
+ * Now form an archive spec and recurse to deal with nested
+ * variables and multi-word variable values.... The results
+ * are just placed at the end of the nodeLst we're returning.
+ */
+ sz = strlen(memName)+strlen(libName)+3;
+ buf = sacrifice = bmake_malloc(sz);
+
+ snprintf(buf, sz, "%s(%s)", libName, memName);
+
+ if (strchr(memName, '$') && strcmp(memName, oldMemName) == 0) {
+ /*
+ * Must contain dynamic sources, so we can't deal with it now.
+ * Just create an ARCHV node for the thing and let
+ * SuffExpandChildren handle it...
+ */
+ gn = Targ_FindNode(buf, TARG_CREATE);
+
+ if (gn == NULL) {
+ free(buf);
+ return(FAILURE);
+ } else {
+ gn->type |= OP_ARCHV;
+ (void)Lst_AtEnd(nodeLst, gn);
+ }
+ } else if (Arch_ParseArchive(&sacrifice, nodeLst, ctxt)!=SUCCESS) {
+ /*
+ * Error in nested call -- free buffer and return FAILURE
+ * ourselves.
+ */
+ free(buf);
+ return(FAILURE);
+ }
+ /*
+ * Free buffer and continue with our work.
+ */
+ free(buf);
+ } else if (Dir_HasWildcards(memName)) {
+ Lst members = Lst_Init(FALSE);
+ char *member;
+ size_t sz = MAXPATHLEN, nsz;
+ nameBuf = bmake_malloc(sz);
+
+ Dir_Expand(memName, dirSearchPath, members);
+ while (!Lst_IsEmpty(members)) {
+ member = (char *)Lst_DeQueue(members);
+ nsz = strlen(libName) + strlen(member) + 3;
+ if (sz > nsz)
+ nameBuf = bmake_realloc(nameBuf, sz = nsz * 2);
+
+ snprintf(nameBuf, sz, "%s(%s)", libName, member);
+ free(member);
+ gn = Targ_FindNode(nameBuf, TARG_CREATE);
+ if (gn == NULL) {
+ free(nameBuf);
+ return (FAILURE);
+ } else {
+ /*
+ * We've found the node, but have to make sure the rest of
+ * the world knows it's an archive member, without having
+ * to constantly check for parentheses, so we type the
+ * thing with the OP_ARCHV bit before we place it on the
+ * end of the provided list.
+ */
+ gn->type |= OP_ARCHV;
+ (void)Lst_AtEnd(nodeLst, gn);
+ }
+ }
+ Lst_Destroy(members, NULL);
+ free(nameBuf);
+ } else {
+ size_t sz = strlen(libName) + strlen(memName) + 3;
+ nameBuf = bmake_malloc(sz);
+ snprintf(nameBuf, sz, "%s(%s)", libName, memName);
+ gn = Targ_FindNode(nameBuf, TARG_CREATE);
+ free(nameBuf);
+ if (gn == NULL) {
+ return (FAILURE);
+ } else {
+ /*
+ * We've found the node, but have to make sure the rest of the
+ * world knows it's an archive member, without having to
+ * constantly check for parentheses, so we type the thing with
+ * the OP_ARCHV bit before we place it on the end of the
+ * provided list.
+ */
+ gn->type |= OP_ARCHV;
+ (void)Lst_AtEnd(nodeLst, gn);
+ }
+ }
+ if (doSubst) {
+ free(memName);
+ }
+
+ *cp = saveChar;
+ }
+
+ /*
+ * If substituted libName, free it now, since we need it no longer.
+ */
+ if (subLibName) {
+ free(libName);
+ }
+
+ /*
+ * We promised the pointer would be set up at the next non-space, so
+ * we must advance cp there before setting *linePtr... (note that on
+ * entrance to the loop, cp is guaranteed to point at a ')')
+ */
+ do {
+ cp++;
+ } while (*cp != '\0' && isspace ((unsigned char)*cp));
+
+ *linePtr = cp;
+ return (SUCCESS);
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * ArchFindArchive --
+ * See if the given archive is the one we are looking for. Called
+ * From ArchStatMember and ArchFindMember via Lst_Find.
+ *
+ * Input:
+ * ar Current list element
+ * archName Name we want
+ *
+ * Results:
+ * 0 if it is, non-zero if it isn't.
+ *
+ * Side Effects:
+ * None.
+ *
+ *-----------------------------------------------------------------------
+ */
+static int
+ArchFindArchive(const void *ar, const void *archName)
+{
+ return (strcmp(archName, ((const Arch *)ar)->name));
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * ArchStatMember --
+ * Locate a member of an archive, given the path of the archive and
+ * the path of the desired member.
+ *
+ * Input:
+ * archive Path to the archive
+ * member Name of member. If it is a path, only the last
+ * component is used.
+ * hash TRUE if archive should be hashed if not already so.
+ *
+ * Results:
+ * A pointer to the current struct ar_hdr structure for the member. Note
+ * That no position is returned, so this is not useful for touching
+ * archive members. This is mostly because we have no assurances that
+ * The archive will remain constant after we read all the headers, so
+ * there's not much point in remembering the position...
+ *
+ * Side Effects:
+ *
+ *-----------------------------------------------------------------------
+ */
+static struct ar_hdr *
+ArchStatMember(char *archive, char *member, Boolean hash)
+{
+#define AR_MAX_NAME_LEN (sizeof(arh.ar_name)-1)
+ FILE * arch; /* Stream to archive */
+ int size; /* Size of archive member */
+ char *cp; /* Useful character pointer */
+ char magic[SARMAG];
+ LstNode ln; /* Lst member containing archive descriptor */
+ Arch *ar; /* Archive descriptor */
+ Hash_Entry *he; /* Entry containing member's description */
+ struct ar_hdr arh; /* archive-member header for reading archive */
+ char memName[MAXPATHLEN+1];
+ /* Current member name while hashing. */
+
+ /*
+ * Because of space constraints and similar things, files are archived
+ * using their final path components, not the entire thing, so we need
+ * to point 'member' to the final component, if there is one, to make
+ * the comparisons easier...
+ */
+ cp = strrchr(member, '/');
+ if (cp != NULL) {
+ member = cp + 1;
+ }
+
+ ln = Lst_Find(archives, archive, ArchFindArchive);
+ if (ln != NULL) {
+ ar = (Arch *)Lst_Datum(ln);
+
+ he = Hash_FindEntry(&ar->members, member);
+
+ if (he != NULL) {
+ return ((struct ar_hdr *)Hash_GetValue(he));
+ } else {
+ /* Try truncated name */
+ char copy[AR_MAX_NAME_LEN+1];
+ size_t len = strlen(member);
+
+ if (len > AR_MAX_NAME_LEN) {
+ len = AR_MAX_NAME_LEN;
+ strncpy(copy, member, AR_MAX_NAME_LEN);
+ copy[AR_MAX_NAME_LEN] = '\0';
+ }
+ if ((he = Hash_FindEntry(&ar->members, copy)) != NULL)
+ return ((struct ar_hdr *)Hash_GetValue(he));
+ return NULL;
+ }
+ }
+
+ if (!hash) {
+ /*
+ * Caller doesn't want the thing hashed, just use ArchFindMember
+ * to read the header for the member out and close down the stream
+ * again. Since the archive is not to be hashed, we assume there's
+ * no need to allocate extra room for the header we're returning,
+ * so just declare it static.
+ */
+ static struct ar_hdr sarh;
+
+ arch = ArchFindMember(archive, member, &sarh, "r");
+
+ if (arch == NULL) {
+ return NULL;
+ } else {
+ fclose(arch);
+ return (&sarh);
+ }
+ }
+
+ /*
+ * We don't have this archive on the list yet, so we want to find out
+ * everything that's in it and cache it so we can get at it quickly.
+ */
+ arch = fopen(archive, "r");
+ if (arch == NULL) {
+ return NULL;
+ }
+
+ /*
+ * We use the ARMAG string to make sure this is an archive we
+ * can handle...
+ */
+ if ((fread(magic, SARMAG, 1, arch) != 1) ||
+ (strncmp(magic, ARMAG, SARMAG) != 0)) {
+ fclose(arch);
+ return NULL;
+ }
+
+ ar = bmake_malloc(sizeof(Arch));
+ ar->name = bmake_strdup(archive);
+ ar->fnametab = NULL;
+ ar->fnamesize = 0;
+ Hash_InitTable(&ar->members, -1);
+ memName[AR_MAX_NAME_LEN] = '\0';
+
+ while (fread((char *)&arh, sizeof(struct ar_hdr), 1, arch) == 1) {
+ if (strncmp( arh.ar_fmag, ARFMAG, sizeof(arh.ar_fmag)) != 0) {
+ /*
+ * The header is bogus, so the archive is bad
+ * and there's no way we can recover...
+ */
+ goto badarch;
+ } else {
+ /*
+ * We need to advance the stream's pointer to the start of the
+ * next header. Files are padded with newlines to an even-byte
+ * boundary, so we need to extract the size of the file from the
+ * 'size' field of the header and round it up during the seek.
+ */
+ arh.ar_size[sizeof(arh.ar_size)-1] = '\0';
+ size = (int)strtol(arh.ar_size, NULL, 10);
+
+ (void)strncpy(memName, arh.ar_name, sizeof(arh.ar_name));
+ for (cp = &memName[AR_MAX_NAME_LEN]; *cp == ' '; cp--) {
+ continue;
+ }
+ cp[1] = '\0';
+
+#ifdef SVR4ARCHIVES
+ /*
+ * svr4 names are slash terminated. Also svr4 extended AR format.
+ */
+ if (memName[0] == '/') {
+ /*
+ * svr4 magic mode; handle it
+ */
+ switch (ArchSVR4Entry(ar, memName, size, arch)) {
+ case -1: /* Invalid data */
+ goto badarch;
+ case 0: /* List of files entry */
+ continue;
+ default: /* Got the entry */
+ break;
+ }
+ }
+ else {
+ if (cp[0] == '/')
+ cp[0] = '\0';
+ }
+#endif
+
+#ifdef AR_EFMT1
+ /*
+ * BSD 4.4 extended AR format: #1/<namelen>, with name as the
+ * first <namelen> bytes of the file
+ */
+ if (strncmp(memName, AR_EFMT1, sizeof(AR_EFMT1) - 1) == 0 &&
+ isdigit((unsigned char)memName[sizeof(AR_EFMT1) - 1])) {
+
+ unsigned int elen = atoi(&memName[sizeof(AR_EFMT1)-1]);
+
+ if (elen > MAXPATHLEN)
+ goto badarch;
+ if (fread(memName, elen, 1, arch) != 1)
+ goto badarch;
+ memName[elen] = '\0';
+ if (fseek(arch, -elen, SEEK_CUR) != 0)
+ goto badarch;
+ if (DEBUG(ARCH) || DEBUG(MAKE)) {
+ fprintf(debug_file, "ArchStat: Extended format entry for %s\n", memName);
+ }
+ }
+#endif
+
+ he = Hash_CreateEntry(&ar->members, memName, NULL);
+ Hash_SetValue(he, bmake_malloc(sizeof(struct ar_hdr)));
+ memcpy(Hash_GetValue(he), &arh, sizeof(struct ar_hdr));
+ }
+ if (fseek(arch, (size + 1) & ~1, SEEK_CUR) != 0)
+ goto badarch;
+ }
+
+ fclose(arch);
+
+ (void)Lst_AtEnd(archives, ar);
+
+ /*
+ * Now that the archive has been read and cached, we can look into
+ * the hash table to find the desired member's header.
+ */
+ he = Hash_FindEntry(&ar->members, member);
+
+ if (he != NULL) {
+ return ((struct ar_hdr *)Hash_GetValue(he));
+ } else {
+ return NULL;
+ }
+
+badarch:
+ fclose(arch);
+ Hash_DeleteTable(&ar->members);
+ free(ar->fnametab);
+ free(ar);
+ return NULL;
+}
+
+#ifdef SVR4ARCHIVES
+/*-
+ *-----------------------------------------------------------------------
+ * ArchSVR4Entry --
+ * Parse an SVR4 style entry that begins with a slash.
+ * If it is "//", then load the table of filenames
+ * If it is "/<offset>", then try to substitute the long file name
+ * from offset of a table previously read.
+ *
+ * Results:
+ * -1: Bad data in archive
+ * 0: A table was loaded from the file
+ * 1: Name was successfully substituted from table
+ * 2: Name was not successfully substituted from table
+ *
+ * Side Effects:
+ * If a table is read, the file pointer is moved to the next archive
+ * member
+ *
+ *-----------------------------------------------------------------------
+ */
+static int
+ArchSVR4Entry(Arch *ar, char *name, size_t size, FILE *arch)
+{
+#define ARLONGNAMES1 "//"
+#define ARLONGNAMES2 "/ARFILENAMES"
+ size_t entry;
+ char *ptr, *eptr;
+
+ if (strncmp(name, ARLONGNAMES1, sizeof(ARLONGNAMES1) - 1) == 0 ||
+ strncmp(name, ARLONGNAMES2, sizeof(ARLONGNAMES2) - 1) == 0) {
+
+ if (ar->fnametab != NULL) {
+ if (DEBUG(ARCH)) {
+ fprintf(debug_file, "Attempted to redefine an SVR4 name table\n");
+ }
+ return -1;
+ }
+
+ /*
+ * This is a table of archive names, so we build one for
+ * ourselves
+ */
+ ar->fnametab = bmake_malloc(size);
+ ar->fnamesize = size;
+
+ if (fread(ar->fnametab, size, 1, arch) != 1) {
+ if (DEBUG(ARCH)) {
+ fprintf(debug_file, "Reading an SVR4 name table failed\n");
+ }
+ return -1;
+ }
+ eptr = ar->fnametab + size;
+ for (entry = 0, ptr = ar->fnametab; ptr < eptr; ptr++)
+ switch (*ptr) {
+ case '/':
+ entry++;
+ *ptr = '\0';
+ break;
+
+ case '\n':
+ break;
+
+ default:
+ break;
+ }
+ if (DEBUG(ARCH)) {
+ fprintf(debug_file, "Found svr4 archive name table with %lu entries\n",
+ (unsigned long)entry);
+ }
+ return 0;
+ }
+
+ if (name[1] == ' ' || name[1] == '\0')
+ return 2;
+
+ entry = (size_t)strtol(&name[1], &eptr, 0);
+ if ((*eptr != ' ' && *eptr != '\0') || eptr == &name[1]) {
+ if (DEBUG(ARCH)) {
+ fprintf(debug_file, "Could not parse SVR4 name %s\n", name);
+ }
+ return 2;
+ }
+ if (entry >= ar->fnamesize) {
+ if (DEBUG(ARCH)) {
+ fprintf(debug_file, "SVR4 entry offset %s is greater than %lu\n",
+ name, (unsigned long)ar->fnamesize);
+ }
+ return 2;
+ }
+
+ if (DEBUG(ARCH)) {
+ fprintf(debug_file, "Replaced %s with %s\n", name, &ar->fnametab[entry]);
+ }
+
+ (void)strncpy(name, &ar->fnametab[entry], MAXPATHLEN);
+ name[MAXPATHLEN] = '\0';
+ return 1;
+}
+#endif
+
+
+/*-
+ *-----------------------------------------------------------------------
+ * ArchFindMember --
+ * Locate a member of an archive, given the path of the archive and
+ * the path of the desired member. If the archive is to be modified,
+ * the mode should be "r+", if not, it should be "r".
+ *
+ * Input:
+ * archive Path to the archive
+ * member Name of member. If it is a path, only the last
+ * component is used.
+ * arhPtr Pointer to header structure to be filled in
+ * mode The mode for opening the stream
+ *
+ * Results:
+ * An FILE *, opened for reading and writing, positioned at the
+ * start of the member's struct ar_hdr, or NULL if the member was
+ * nonexistent. The current struct ar_hdr for member.
+ *
+ * Side Effects:
+ * The passed struct ar_hdr structure is filled in.
+ *
+ *-----------------------------------------------------------------------
+ */
+static FILE *
+ArchFindMember(char *archive, char *member, struct ar_hdr *arhPtr,
+ const char *mode)
+{
+ FILE * arch; /* Stream to archive */
+ int size; /* Size of archive member */
+ char *cp; /* Useful character pointer */
+ char magic[SARMAG];
+ size_t len, tlen;
+
+ arch = fopen(archive, mode);
+ if (arch == NULL) {
+ return NULL;
+ }
+
+ /*
+ * We use the ARMAG string to make sure this is an archive we
+ * can handle...
+ */
+ if ((fread(magic, SARMAG, 1, arch) != 1) ||
+ (strncmp(magic, ARMAG, SARMAG) != 0)) {
+ fclose(arch);
+ return NULL;
+ }
+
+ /*
+ * Because of space constraints and similar things, files are archived
+ * using their final path components, not the entire thing, so we need
+ * to point 'member' to the final component, if there is one, to make
+ * the comparisons easier...
+ */
+ cp = strrchr(member, '/');
+ if (cp != NULL) {
+ member = cp + 1;
+ }
+ len = tlen = strlen(member);
+ if (len > sizeof(arhPtr->ar_name)) {
+ tlen = sizeof(arhPtr->ar_name);
+ }
+
+ while (fread((char *)arhPtr, sizeof(struct ar_hdr), 1, arch) == 1) {
+ if (strncmp(arhPtr->ar_fmag, ARFMAG, sizeof(arhPtr->ar_fmag) ) != 0) {
+ /*
+ * The header is bogus, so the archive is bad
+ * and there's no way we can recover...
+ */
+ fclose(arch);
+ return NULL;
+ } else if (strncmp(member, arhPtr->ar_name, tlen) == 0) {
+ /*
+ * If the member's name doesn't take up the entire 'name' field,
+ * we have to be careful of matching prefixes. Names are space-
+ * padded to the right, so if the character in 'name' at the end
+ * of the matched string is anything but a space, this isn't the
+ * member we sought.
+ */
+ if (tlen != sizeof(arhPtr->ar_name) && arhPtr->ar_name[tlen] != ' '){
+ goto skip;
+ } else {
+ /*
+ * To make life easier, we reposition the file at the start
+ * of the header we just read before we return the stream.
+ * In a more general situation, it might be better to leave
+ * the file at the actual member, rather than its header, but
+ * not here...
+ */
+ if (fseek(arch, -sizeof(struct ar_hdr), SEEK_CUR) != 0) {
+ fclose(arch);
+ return NULL;
+ }
+ return (arch);
+ }
+ } else
+#ifdef AR_EFMT1
+ /*
+ * BSD 4.4 extended AR format: #1/<namelen>, with name as the
+ * first <namelen> bytes of the file
+ */
+ if (strncmp(arhPtr->ar_name, AR_EFMT1,
+ sizeof(AR_EFMT1) - 1) == 0 &&
+ isdigit((unsigned char)arhPtr->ar_name[sizeof(AR_EFMT1) - 1])) {
+
+ unsigned int elen = atoi(&arhPtr->ar_name[sizeof(AR_EFMT1)-1]);
+ char ename[MAXPATHLEN + 1];
+
+ if (elen > MAXPATHLEN) {
+ fclose(arch);
+ return NULL;
+ }
+ if (fread(ename, elen, 1, arch) != 1) {
+ fclose(arch);
+ return NULL;
+ }
+ ename[elen] = '\0';
+ if (DEBUG(ARCH) || DEBUG(MAKE)) {
+ fprintf(debug_file, "ArchFind: Extended format entry for %s\n", ename);
+ }
+ if (strncmp(ename, member, len) == 0) {
+ /* Found as extended name */
+ if (fseek(arch, -sizeof(struct ar_hdr) - elen,
+ SEEK_CUR) != 0) {
+ fclose(arch);
+ return NULL;
+ }
+ return (arch);
+ }
+ if (fseek(arch, -elen, SEEK_CUR) != 0) {
+ fclose(arch);
+ return NULL;
+ }
+ goto skip;
+ } else
+#endif
+ {
+skip:
+ /*
+ * This isn't the member we're after, so we need to advance the
+ * stream's pointer to the start of the next header. Files are
+ * padded with newlines to an even-byte boundary, so we need to
+ * extract the size of the file from the 'size' field of the
+ * header and round it up during the seek.
+ */
+ arhPtr->ar_size[sizeof(arhPtr->ar_size)-1] = '\0';
+ size = (int)strtol(arhPtr->ar_size, NULL, 10);
+ if (fseek(arch, (size + 1) & ~1, SEEK_CUR) != 0) {
+ fclose(arch);
+ return NULL;
+ }
+ }
+ }
+
+ /*
+ * We've looked everywhere, but the member is not to be found. Close the
+ * archive and return NULL -- an error.
+ */
+ fclose(arch);
+ return NULL;
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * Arch_Touch --
+ * Touch a member of an archive.
+ *
+ * Input:
+ * gn Node of member to touch
+ *
+ * Results:
+ * The 'time' field of the member's header is updated.
+ *
+ * Side Effects:
+ * The modification time of the entire archive is also changed.
+ * For a library, this could necessitate the re-ranlib'ing of the
+ * whole thing.
+ *
+ *-----------------------------------------------------------------------
+ */
+void
+Arch_Touch(GNode *gn)
+{
+ FILE * arch; /* Stream open to archive, positioned properly */
+ struct ar_hdr arh; /* Current header describing member */
+ char *p1, *p2;
+
+ arch = ArchFindMember(Var_Value(ARCHIVE, gn, &p1),
+ Var_Value(MEMBER, gn, &p2),
+ &arh, "r+");
+
+ free(p1);
+ free(p2);
+
+ snprintf(arh.ar_date, sizeof(arh.ar_date), "%-12ld", (long) now);
+
+ if (arch != NULL) {
+ (void)fwrite((char *)&arh, sizeof(struct ar_hdr), 1, arch);
+ fclose(arch);
+ }
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * Arch_TouchLib --
+ * Given a node which represents a library, touch the thing, making
+ * sure that the table of contents also is touched.
+ *
+ * Input:
+ * gn The node of the library to touch
+ *
+ * Results:
+ * None.
+ *
+ * Side Effects:
+ * Both the modification time of the library and of the RANLIBMAG
+ * member are set to 'now'.
+ *
+ *-----------------------------------------------------------------------
+ */
+void
+#if !defined(RANLIBMAG)
+Arch_TouchLib(GNode *gn MAKE_ATTR_UNUSED)
+#else
+Arch_TouchLib(GNode *gn)
+#endif
+{
+#ifdef RANLIBMAG
+ FILE * arch; /* Stream open to archive */
+ struct ar_hdr arh; /* Header describing table of contents */
+ struct utimbuf times; /* Times for utime() call */
+
+ arch = ArchFindMember(gn->path, UNCONST(RANLIBMAG), &arh, "r+");
+ snprintf(arh.ar_date, sizeof(arh.ar_date), "%-12ld", (long) now);
+
+ if (arch != NULL) {
+ (void)fwrite((char *)&arh, sizeof(struct ar_hdr), 1, arch);
+ fclose(arch);
+
+ times.actime = times.modtime = now;
+ utime(gn->path, &times);
+ }
+#endif
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * Arch_MTime --
+ * Return the modification time of a member of an archive.
+ *
+ * Input:
+ * gn Node describing archive member
+ *
+ * Results:
+ * The modification time(seconds).
+ *
+ * Side Effects:
+ * The mtime field of the given node is filled in with the value
+ * returned by the function.
+ *
+ *-----------------------------------------------------------------------
+ */
+time_t
+Arch_MTime(GNode *gn)
+{
+ struct ar_hdr *arhPtr; /* Header of desired member */
+ time_t modTime; /* Modification time as an integer */
+ char *p1, *p2;
+
+ arhPtr = ArchStatMember(Var_Value(ARCHIVE, gn, &p1),
+ Var_Value(MEMBER, gn, &p2),
+ TRUE);
+
+ free(p1);
+ free(p2);
+
+ if (arhPtr != NULL) {
+ modTime = (time_t)strtol(arhPtr->ar_date, NULL, 10);
+ } else {
+ modTime = 0;
+ }
+
+ gn->mtime = modTime;
+ return (modTime);
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * Arch_MemMTime --
+ * Given a non-existent archive member's node, get its modification
+ * time from its archived form, if it exists.
+ *
+ * Results:
+ * The modification time.
+ *
+ * Side Effects:
+ * The mtime field is filled in.
+ *
+ *-----------------------------------------------------------------------
+ */
+time_t
+Arch_MemMTime(GNode *gn)
+{
+ LstNode ln;
+ GNode *pgn;
+ char *nameStart,
+ *nameEnd;
+
+ if (Lst_Open(gn->parents) != SUCCESS) {
+ gn->mtime = 0;
+ return (0);
+ }
+ while ((ln = Lst_Next(gn->parents)) != NULL) {
+ pgn = (GNode *)Lst_Datum(ln);
+
+ if (pgn->type & OP_ARCHV) {
+ /*
+ * If the parent is an archive specification and is being made
+ * and its member's name matches the name of the node we were
+ * given, record the modification time of the parent in the
+ * child. We keep searching its parents in case some other
+ * parent requires this child to exist...
+ */
+ nameStart = strchr(pgn->name, '(') + 1;
+ nameEnd = strchr(nameStart, ')');
+
+ if ((pgn->flags & REMAKE) &&
+ strncmp(nameStart, gn->name, nameEnd - nameStart) == 0) {
+ gn->mtime = Arch_MTime(pgn);
+ }
+ } else if (pgn->flags & REMAKE) {
+ /*
+ * Something which isn't a library depends on the existence of
+ * this target, so it needs to exist.
+ */
+ gn->mtime = 0;
+ break;
+ }
+ }
+
+ Lst_Close(gn->parents);
+
+ return (gn->mtime);
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * Arch_FindLib --
+ * Search for a library along the given search path.
+ *
+ * Input:
+ * gn Node of library to find
+ * path Search path
+ *
+ * Results:
+ * None.
+ *
+ * Side Effects:
+ * The node's 'path' field is set to the found path (including the
+ * actual file name, not -l...). If the system can handle the -L
+ * flag when linking (or we cannot find the library), we assume that
+ * the user has placed the .LIBRARIES variable in the final linking
+ * command (or the linker will know where to find it) and set the
+ * TARGET variable for this node to be the node's name. Otherwise,
+ * we set the TARGET variable to be the full path of the library,
+ * as returned by Dir_FindFile.
+ *
+ *-----------------------------------------------------------------------
+ */
+void
+Arch_FindLib(GNode *gn, Lst path)
+{
+ char *libName; /* file name for archive */
+ size_t sz = strlen(gn->name) + 6 - 2;
+
+ libName = bmake_malloc(sz);
+ snprintf(libName, sz, "lib%s.a", &gn->name[2]);
+
+ gn->path = Dir_FindFile(libName, path);
+
+ free(libName);
+
+#ifdef LIBRARIES
+ Var_Set(TARGET, gn->name, gn, 0);
+#else
+ Var_Set(TARGET, gn->path == NULL ? gn->name : gn->path, gn, 0);
+#endif /* LIBRARIES */
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * Arch_LibOODate --
+ * Decide if a node with the OP_LIB attribute is out-of-date. Called
+ * from Make_OODate to make its life easier.
+ *
+ * There are several ways for a library to be out-of-date that are
+ * not available to ordinary files. In addition, there are ways
+ * that are open to regular files that are not available to
+ * libraries. A library that is only used as a source is never
+ * considered out-of-date by itself. This does not preclude the
+ * library's modification time from making its parent be out-of-date.
+ * A library will be considered out-of-date for any of these reasons,
+ * given that it is a target on a dependency line somewhere:
+ * Its modification time is less than that of one of its
+ * sources (gn->mtime < gn->cmgn->mtime).
+ * Its modification time is greater than the time at which the
+ * make began (i.e. it's been modified in the course
+ * of the make, probably by archiving).
+ * The modification time of one of its sources is greater than
+ * the one of its RANLIBMAG member (i.e. its table of contents
+ * is out-of-date). We don't compare of the archive time
+ * vs. TOC time because they can be too close. In my
+ * opinion we should not bother with the TOC at all since
+ * this is used by 'ar' rules that affect the data contents
+ * of the archive, not by ranlib rules, which affect the
+ * TOC.
+ *
+ * Input:
+ * gn The library's graph node
+ *
+ * Results:
+ * TRUE if the library is out-of-date. FALSE otherwise.
+ *
+ * Side Effects:
+ * The library will be hashed if it hasn't been already.
+ *
+ *-----------------------------------------------------------------------
+ */
+Boolean
+Arch_LibOODate(GNode *gn)
+{
+ Boolean oodate;
+
+ if (gn->type & OP_PHONY) {
+ oodate = TRUE;
+ } else if (OP_NOP(gn->type) && Lst_IsEmpty(gn->children)) {
+ oodate = FALSE;
+ } else if ((!Lst_IsEmpty(gn->children) && gn->cmgn == NULL) ||
+ (gn->mtime > now) ||
+ (gn->cmgn != NULL && gn->mtime < gn->cmgn->mtime)) {
+ oodate = TRUE;
+ } else {
+#ifdef RANLIBMAG
+ struct ar_hdr *arhPtr; /* Header for __.SYMDEF */
+ int modTimeTOC; /* The table-of-contents's mod time */
+
+ arhPtr = ArchStatMember(gn->path, UNCONST(RANLIBMAG), FALSE);
+
+ if (arhPtr != NULL) {
+ modTimeTOC = (int)strtol(arhPtr->ar_date, NULL, 10);
+
+ if (DEBUG(ARCH) || DEBUG(MAKE)) {
+ fprintf(debug_file, "%s modified %s...", RANLIBMAG, Targ_FmtTime(modTimeTOC));
+ }
+ oodate = (gn->cmgn == NULL || gn->cmgn->mtime > modTimeTOC);
+ } else {
+ /*
+ * A library w/o a table of contents is out-of-date
+ */
+ if (DEBUG(ARCH) || DEBUG(MAKE)) {
+ fprintf(debug_file, "No t.o.c....");
+ }
+ oodate = TRUE;
+ }
+#else
+ oodate = FALSE;
+#endif
+ }
+ return (oodate);
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * Arch_Init --
+ * Initialize things for this module.
+ *
+ * Results:
+ * None.
+ *
+ * Side Effects:
+ * The 'archives' list is initialized.
+ *
+ *-----------------------------------------------------------------------
+ */
+void
+Arch_Init(void)
+{
+ archives = Lst_Init(FALSE);
+}
+
+
+
+/*-
+ *-----------------------------------------------------------------------
+ * Arch_End --
+ * Cleanup things for this module.
+ *
+ * Results:
+ * None.
+ *
+ * Side Effects:
+ * The 'archives' list is freed
+ *
+ *-----------------------------------------------------------------------
+ */
+void
+Arch_End(void)
+{
+#ifdef CLEANUP
+ Lst_Destroy(archives, ArchFree);
+#endif
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * Arch_IsLib --
+ * Check if the node is a library
+ *
+ * Results:
+ * True or False.
+ *
+ * Side Effects:
+ * None.
+ *
+ *-----------------------------------------------------------------------
+ */
+int
+Arch_IsLib(GNode *gn)
+{
+ static const char armag[] = "!<arch>\n";
+ char buf[sizeof(armag)-1];
+ int fd;
+
+ if ((fd = open(gn->path, O_RDONLY)) == -1)
+ return FALSE;
+
+ if (read(fd, buf, sizeof(buf)) != sizeof(buf)) {
+ (void)close(fd);
+ return FALSE;
+ }
+
+ (void)close(fd);
+
+ return memcmp(buf, armag, sizeof(buf)) == 0;
+}
diff --git a/usr.bin/make/buf.c b/usr.bin/make/buf.c
new file mode 100644
index 0000000..ac95c16
--- /dev/null
+++ b/usr.bin/make/buf.c
@@ -0,0 +1,291 @@
+/* $NetBSD: buf.c,v 1.25 2012/04/24 20:26:58 sjg Exp $ */
+
+/*
+ * Copyright (c) 1988, 1989, 1990 The Regents of the University of California.
+ * All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Adam de Boor.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+/*
+ * Copyright (c) 1988, 1989 by Adam de Boor
+ * Copyright (c) 1989 by Berkeley Softworks
+ * All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Adam de Boor.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. All advertising materials mentioning features or use of this software
+ * must display the following acknowledgement:
+ * This product includes software developed by the University of
+ * California, Berkeley and its contributors.
+ * 4. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#ifndef MAKE_NATIVE
+static char rcsid[] = "$NetBSD: buf.c,v 1.25 2012/04/24 20:26:58 sjg Exp $";
+#else
+#include <sys/cdefs.h>
+#ifndef lint
+#if 0
+static char sccsid[] = "@(#)buf.c 8.1 (Berkeley) 6/6/93";
+#else
+__RCSID("$NetBSD: buf.c,v 1.25 2012/04/24 20:26:58 sjg Exp $");
+#endif
+#endif /* not lint */
+#endif
+
+/*-
+ * buf.c --
+ * Functions for automatically-expanded buffers.
+ */
+
+#include "make.h"
+#include "buf.h"
+
+#ifndef max
+#define max(a,b) ((a) > (b) ? (a) : (b))
+#endif
+
+#define BUF_DEF_SIZE 256 /* Default buffer size */
+
+/*-
+ *-----------------------------------------------------------------------
+ * Buf_Expand_1 --
+ * Extend buffer for single byte add.
+ *
+ *-----------------------------------------------------------------------
+ */
+void
+Buf_Expand_1(Buffer *bp)
+{
+ bp->size += max(bp->size, 16);
+ bp->buffer = bmake_realloc(bp->buffer, bp->size);
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * Buf_AddBytes --
+ * Add a number of bytes to the buffer.
+ *
+ * Results:
+ * None.
+ *
+ * Side Effects:
+ * Guess what?
+ *
+ *-----------------------------------------------------------------------
+ */
+void
+Buf_AddBytes(Buffer *bp, int numBytes, const Byte *bytesPtr)
+{
+ int count = bp->count;
+ Byte *ptr;
+
+ if (__predict_false(count + numBytes >= bp->size)) {
+ bp->size += max(bp->size, numBytes + 16);
+ bp->buffer = bmake_realloc(bp->buffer, bp->size);
+ }
+
+ ptr = bp->buffer + count;
+ bp->count = count + numBytes;
+ ptr[numBytes] = 0;
+ memcpy(ptr, bytesPtr, numBytes);
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * Buf_GetAll --
+ * Get all the available data at once.
+ *
+ * Results:
+ * A pointer to the data and the number of bytes available.
+ *
+ * Side Effects:
+ * None.
+ *
+ *-----------------------------------------------------------------------
+ */
+Byte *
+Buf_GetAll(Buffer *bp, int *numBytesPtr)
+{
+
+ if (numBytesPtr != NULL)
+ *numBytesPtr = bp->count;
+
+ return (bp->buffer);
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * Buf_Empty --
+ * Throw away bytes in a buffer.
+ *
+ * Results:
+ * None.
+ *
+ * Side Effects:
+ * The bytes are discarded.
+ *
+ *-----------------------------------------------------------------------
+ */
+void
+Buf_Empty(Buffer *bp)
+{
+
+ bp->count = 0;
+ *bp->buffer = 0;
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * Buf_Init --
+ * Initialize a buffer. If no initial size is given, a reasonable
+ * default is used.
+ *
+ * Input:
+ * size Initial size for the buffer
+ *
+ * Results:
+ * A buffer to be given to other functions in this library.
+ *
+ * Side Effects:
+ * The buffer is created, the space allocated and pointers
+ * initialized.
+ *
+ *-----------------------------------------------------------------------
+ */
+void
+Buf_Init(Buffer *bp, int size)
+{
+ if (size <= 0) {
+ size = BUF_DEF_SIZE;
+ }
+ bp->size = size;
+ bp->count = 0;
+ bp->buffer = bmake_malloc(size);
+ *bp->buffer = 0;
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * Buf_Destroy --
+ * Nuke a buffer and all its resources.
+ *
+ * Input:
+ * buf Buffer to destroy
+ * freeData TRUE if the data should be destroyed
+ *
+ * Results:
+ * Data buffer, NULL if freed
+ *
+ * Side Effects:
+ * The buffer is freed.
+ *
+ *-----------------------------------------------------------------------
+ */
+Byte *
+Buf_Destroy(Buffer *buf, Boolean freeData)
+{
+ Byte *data;
+
+ data = buf->buffer;
+ if (freeData) {
+ free(data);
+ data = NULL;
+ }
+
+ buf->size = 0;
+ buf->count = 0;
+ buf->buffer = NULL;
+
+ return data;
+}
+
+
+/*-
+ *-----------------------------------------------------------------------
+ * Buf_DestroyCompact --
+ * Nuke a buffer and return its data.
+ *
+ * Input:
+ * buf Buffer to destroy
+ *
+ * Results:
+ * Data buffer
+ *
+ * Side Effects:
+ * If the buffer size is much greater than its content,
+ * a new buffer will be allocated and the old one freed.
+ *
+ *-----------------------------------------------------------------------
+ */
+#ifndef BUF_COMPACT_LIMIT
+# define BUF_COMPACT_LIMIT 128 /* worthwhile saving */
+#endif
+
+Byte *
+Buf_DestroyCompact(Buffer *buf)
+{
+#if BUF_COMPACT_LIMIT > 0
+ Byte *data;
+
+ if (buf->size - buf->count >= BUF_COMPACT_LIMIT) {
+ /* We trust realloc to be smart */
+ data = bmake_realloc(buf->buffer, buf->count + 1);
+ if (data) {
+ data[buf->count] = 0;
+ Buf_Destroy(buf, FALSE);
+ return data;
+ }
+ }
+#endif
+ return Buf_Destroy(buf, FALSE);
+}
diff --git a/usr.bin/make/buf.h b/usr.bin/make/buf.h
new file mode 100644
index 0000000..7bd2d2b
--- /dev/null
+++ b/usr.bin/make/buf.h
@@ -0,0 +1,119 @@
+/* $NetBSD: buf.h,v 1.19 2017/05/31 22:02:06 maya Exp $ */
+
+/*
+ * Copyright (c) 1988, 1989, 1990 The Regents of the University of California.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Adam de Boor.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * from: @(#)buf.h 8.1 (Berkeley) 6/6/93
+ */
+
+/*
+ * Copyright (c) 1988, 1989 by Adam de Boor
+ * Copyright (c) 1989 by Berkeley Softworks
+ * All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Adam de Boor.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. All advertising materials mentioning features or use of this software
+ * must display the following acknowledgement:
+ * This product includes software developed by the University of
+ * California, Berkeley and its contributors.
+ * 4. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * from: @(#)buf.h 8.1 (Berkeley) 6/6/93
+ */
+
+/*-
+ * buf.h --
+ * Header for users of the buf library.
+ */
+
+#ifndef MAKE_BUF_H
+#define MAKE_BUF_H
+
+typedef char Byte;
+
+typedef struct Buffer {
+ int size; /* Current size of the buffer */
+ int count; /* Number of bytes in buffer */
+ Byte *buffer; /* The buffer itself (zero terminated) */
+} Buffer;
+
+/* If we aren't on netbsd, __predict_false() might not be defined. */
+#ifndef __predict_false
+#define __predict_false(x) (x)
+#endif
+
+/* Buf_AddByte adds a single byte to a buffer. */
+#define Buf_AddByte(bp, byte) do { \
+ int _count = ++(bp)->count; \
+ char *_ptr; \
+ if (__predict_false(_count >= (bp)->size)) \
+ Buf_Expand_1(bp); \
+ _ptr = (bp)->buffer + _count; \
+ _ptr[-1] = (byte); \
+ _ptr[0] = 0; \
+ } while (0)
+
+#define BUF_ERROR 256
+
+#define Buf_Size(bp) ((bp)->count)
+
+void Buf_Expand_1(Buffer *);
+void Buf_AddBytes(Buffer *, int, const Byte *);
+Byte *Buf_GetAll(Buffer *, int *);
+void Buf_Empty(Buffer *);
+void Buf_Init(Buffer *, int);
+Byte *Buf_Destroy(Buffer *, Boolean);
+Byte *Buf_DestroyCompact(Buffer *);
+
+#endif /* MAKE_BUF_H */
diff --git a/usr.bin/make/compat.c b/usr.bin/make/compat.c
new file mode 100644
index 0000000..538c456
--- /dev/null
+++ b/usr.bin/make/compat.c
@@ -0,0 +1,778 @@
+/* $NetBSD: compat.c,v 1.107 2017/07/20 19:29:54 sjg Exp $ */
+
+/*
+ * Copyright (c) 1988, 1989, 1990 The Regents of the University of California.
+ * All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Adam de Boor.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+/*
+ * Copyright (c) 1988, 1989 by Adam de Boor
+ * Copyright (c) 1989 by Berkeley Softworks
+ * All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Adam de Boor.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. All advertising materials mentioning features or use of this software
+ * must display the following acknowledgement:
+ * This product includes software developed by the University of
+ * California, Berkeley and its contributors.
+ * 4. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#ifndef MAKE_NATIVE
+static char rcsid[] = "$NetBSD: compat.c,v 1.107 2017/07/20 19:29:54 sjg Exp $";
+#else
+#include <sys/cdefs.h>
+#ifndef lint
+#if 0
+static char sccsid[] = "@(#)compat.c 8.2 (Berkeley) 3/19/94";
+#else
+__RCSID("$NetBSD: compat.c,v 1.107 2017/07/20 19:29:54 sjg Exp $");
+#endif
+#endif /* not lint */
+#endif
+
+/*-
+ * compat.c --
+ * The routines in this file implement the full-compatibility
+ * mode of PMake. Most of the special functionality of PMake
+ * is available in this mode. Things not supported:
+ * - different shells.
+ * - friendly variable substitution.
+ *
+ * Interface:
+ * Compat_Run Initialize things for this module and recreate
+ * thems as need creatin'
+ */
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/wait.h>
+
+#include <ctype.h>
+#include <errno.h>
+#include <signal.h>
+#include <stdio.h>
+
+#include "make.h"
+#include "hash.h"
+#include "dir.h"
+#include "job.h"
+#include "metachar.h"
+#include "pathnames.h"
+
+
+static GNode *curTarg = NULL;
+static GNode *ENDNode;
+static void CompatInterrupt(int);
+static pid_t compatChild;
+static int compatSigno;
+
+/*
+ * CompatDeleteTarget -- delete a failed, interrupted, or otherwise
+ * duffed target if not inhibited by .PRECIOUS.
+ */
+static void
+CompatDeleteTarget(GNode *gn)
+{
+ if ((gn != NULL) && !Targ_Precious (gn)) {
+ char *p1;
+ char *file = Var_Value(TARGET, gn, &p1);
+
+ if (!noExecute && eunlink(file) != -1) {
+ Error("*** %s removed", file);
+ }
+
+ free(p1);
+ }
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * CompatInterrupt --
+ * Interrupt the creation of the current target and remove it if
+ * it ain't precious.
+ *
+ * Results:
+ * None.
+ *
+ * Side Effects:
+ * The target is removed and the process exits. If .INTERRUPT exists,
+ * its commands are run first WITH INTERRUPTS IGNORED..
+ *
+ * XXX: is .PRECIOUS supposed to inhibit .INTERRUPT? I doubt it, but I've
+ * left the logic alone for now. - dholland 20160826
+ *
+ *-----------------------------------------------------------------------
+ */
+static void
+CompatInterrupt(int signo)
+{
+ GNode *gn;
+
+ CompatDeleteTarget(curTarg);
+
+ if ((curTarg != NULL) && !Targ_Precious (curTarg)) {
+ /*
+ * Run .INTERRUPT only if hit with interrupt signal
+ */
+ if (signo == SIGINT) {
+ gn = Targ_FindNode(".INTERRUPT", TARG_NOCREATE);
+ if (gn != NULL) {
+ Compat_Make(gn, gn);
+ }
+ }
+ }
+ if (signo == SIGQUIT)
+ _exit(signo);
+ /*
+ * If there is a child running, pass the signal on
+ * we will exist after it has exited.
+ */
+ compatSigno = signo;
+ if (compatChild > 0) {
+ KILLPG(compatChild, signo);
+ } else {
+ bmake_signal(signo, SIG_DFL);
+ kill(myPid, signo);
+ }
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * CompatRunCommand --
+ * Execute the next command for a target. If the command returns an
+ * error, the node's made field is set to ERROR and creation stops.
+ *
+ * Input:
+ * cmdp Command to execute
+ * gnp Node from which the command came
+ *
+ * Results:
+ * 0 if the command succeeded, 1 if an error occurred.
+ *
+ * Side Effects:
+ * The node's 'made' field may be set to ERROR.
+ *
+ *-----------------------------------------------------------------------
+ */
+int
+CompatRunCommand(void *cmdp, void *gnp)
+{
+ char *cmdStart; /* Start of expanded command */
+ char *cp, *bp;
+ Boolean silent, /* Don't print command */
+ doIt; /* Execute even if -n */
+ volatile Boolean errCheck; /* Check errors */
+ int reason; /* Reason for child's death */
+ int status; /* Description of child's death */
+ pid_t cpid; /* Child actually found */
+ pid_t retstat; /* Result of wait */
+ LstNode cmdNode; /* Node where current command is located */
+ const char ** volatile av; /* Argument vector for thing to exec */
+ char ** volatile mav;/* Copy of the argument vector for freeing */
+ int argc; /* Number of arguments in av or 0 if not
+ * dynamically allocated */
+ Boolean local; /* TRUE if command should be executed
+ * locally */
+ Boolean useShell; /* TRUE if command should be executed
+ * using a shell */
+ char * volatile cmd = (char *)cmdp;
+ GNode *gn = (GNode *)gnp;
+
+ silent = gn->type & OP_SILENT;
+ errCheck = !(gn->type & OP_IGNORE);
+ doIt = FALSE;
+
+ cmdNode = Lst_Member(gn->commands, cmd);
+ cmdStart = Var_Subst(NULL, cmd, gn, VARF_WANTRES);
+
+ /*
+ * brk_string will return an argv with a NULL in av[0], thus causing
+ * execvp to choke and die horribly. Besides, how can we execute a null
+ * command? In any case, we warn the user that the command expanded to
+ * nothing (is this the right thing to do?).
+ */
+
+ if (*cmdStart == '\0') {
+ free(cmdStart);
+ return(0);
+ }
+ cmd = cmdStart;
+ Lst_Replace(cmdNode, cmdStart);
+
+ if ((gn->type & OP_SAVE_CMDS) && (gn != ENDNode)) {
+ (void)Lst_AtEnd(ENDNode->commands, cmdStart);
+ return(0);
+ }
+ if (strcmp(cmdStart, "...") == 0) {
+ gn->type |= OP_SAVE_CMDS;
+ return(0);
+ }
+
+ while ((*cmd == '@') || (*cmd == '-') || (*cmd == '+')) {
+ switch (*cmd) {
+ case '@':
+ silent = DEBUG(LOUD) ? FALSE : TRUE;
+ break;
+ case '-':
+ errCheck = FALSE;
+ break;
+ case '+':
+ doIt = TRUE;
+ if (!shellName) /* we came here from jobs */
+ Shell_Init();
+ break;
+ }
+ cmd++;
+ }
+
+ while (isspace((unsigned char)*cmd))
+ cmd++;
+
+ /*
+ * If we did not end up with a command, just skip it.
+ */
+ if (!*cmd)
+ return (0);
+
+#if !defined(MAKE_NATIVE)
+ /*
+ * In a non-native build, the host environment might be weird enough
+ * that it's necessary to go through a shell to get the correct
+ * behaviour. Or perhaps the shell has been replaced with something
+ * that does extra logging, and that should not be bypassed.
+ */
+ useShell = TRUE;
+#else
+ /*
+ * Search for meta characters in the command. If there are no meta
+ * characters, there's no need to execute a shell to execute the
+ * command.
+ *
+ * Additionally variable assignments and empty commands
+ * go to the shell. Therefore treat '=' and ':' like shell
+ * meta characters as documented in make(1).
+ */
+
+ useShell = needshell(cmd, FALSE);
+#endif
+
+ /*
+ * Print the command before echoing if we're not supposed to be quiet for
+ * this one. We also print the command if -n given.
+ */
+ if (!silent || NoExecute(gn)) {
+ printf("%s\n", cmd);
+ fflush(stdout);
+ }
+
+ /*
+ * If we're not supposed to execute any commands, this is as far as
+ * we go...
+ */
+ if (!doIt && NoExecute(gn)) {
+ return (0);
+ }
+ if (DEBUG(JOB))
+ fprintf(debug_file, "Execute: '%s'\n", cmd);
+
+again:
+ if (useShell) {
+ /*
+ * We need to pass the command off to the shell, typically
+ * because the command contains a "meta" character.
+ */
+ static const char *shargv[5];
+ int shargc;
+
+ shargc = 0;
+ shargv[shargc++] = shellPath;
+ /*
+ * The following work for any of the builtin shell specs.
+ */
+ if (errCheck && shellErrFlag) {
+ shargv[shargc++] = shellErrFlag;
+ }
+ if (DEBUG(SHELL))
+ shargv[shargc++] = "-xc";
+ else
+ shargv[shargc++] = "-c";
+ shargv[shargc++] = cmd;
+ shargv[shargc++] = NULL;
+ av = shargv;
+ argc = 0;
+ bp = NULL;
+ mav = NULL;
+ } else {
+ /*
+ * No meta-characters, so no need to exec a shell. Break the command
+ * into words to form an argument vector we can execute.
+ */
+ mav = brk_string(cmd, &argc, TRUE, &bp);
+ if (mav == NULL) {
+ useShell = 1;
+ goto again;
+ }
+ av = (void *)mav;
+ }
+
+ local = TRUE;
+
+#ifdef USE_META
+ if (useMeta) {
+ meta_compat_start();
+ }
+#endif
+
+ /*
+ * Fork and execute the single command. If the fork fails, we abort.
+ */
+ compatChild = cpid = vFork();
+ if (cpid < 0) {
+ Fatal("Could not fork");
+ }
+ if (cpid == 0) {
+ Var_ExportVars();
+#ifdef USE_META
+ if (useMeta) {
+ meta_compat_child();
+ }
+#endif
+ if (local)
+ (void)execvp(av[0], (char *const *)UNCONST(av));
+ else
+ (void)execv(av[0], (char *const *)UNCONST(av));
+ execError("exec", av[0]);
+ _exit(1);
+ }
+
+ free(mav);
+ free(bp);
+
+ Lst_Replace(cmdNode, NULL);
+
+#ifdef USE_META
+ if (useMeta) {
+ meta_compat_parent();
+ }
+#endif
+
+ /*
+ * The child is off and running. Now all we can do is wait...
+ */
+ while (1) {
+
+ while ((retstat = wait(&reason)) != cpid) {
+ if (retstat > 0)
+ JobReapChild(retstat, reason, FALSE); /* not ours? */
+ if (retstat == -1 && errno != EINTR) {
+ break;
+ }
+ }
+
+ if (retstat > -1) {
+ if (WIFSTOPPED(reason)) {
+ status = WSTOPSIG(reason); /* stopped */
+ } else if (WIFEXITED(reason)) {
+ status = WEXITSTATUS(reason); /* exited */
+#if defined(USE_META) && defined(USE_FILEMON_ONCE)
+ if (useMeta) {
+ meta_cmd_finish(NULL);
+ }
+#endif
+ if (status != 0) {
+ if (DEBUG(ERROR)) {
+ fprintf(debug_file, "\n*** Failed target: %s\n*** Failed command: ",
+ gn->name);
+ for (cp = cmd; *cp; ) {
+ if (isspace((unsigned char)*cp)) {
+ fprintf(debug_file, " ");
+ while (isspace((unsigned char)*cp))
+ cp++;
+ } else {
+ fprintf(debug_file, "%c", *cp);
+ cp++;
+ }
+ }
+ fprintf(debug_file, "\n");
+ }
+ printf("*** Error code %d", status);
+ }
+ } else {
+ status = WTERMSIG(reason); /* signaled */
+ printf("*** Signal %d", status);
+ }
+
+
+ if (!WIFEXITED(reason) || (status != 0)) {
+ if (errCheck) {
+#ifdef USE_META
+ if (useMeta) {
+ meta_job_error(NULL, gn, 0, status);
+ }
+#endif
+ gn->made = ERROR;
+ if (keepgoing) {
+ /*
+ * Abort the current target, but let others
+ * continue.
+ */
+ printf(" (continuing)\n");
+ } else {
+ printf("\n");
+ }
+ if (deleteOnError) {
+ CompatDeleteTarget(gn);
+ }
+ } else {
+ /*
+ * Continue executing commands for this target.
+ * If we return 0, this will happen...
+ */
+ printf(" (ignored)\n");
+ status = 0;
+ }
+ }
+ break;
+ } else {
+ Fatal("error in wait: %d: %s", retstat, strerror(errno));
+ /*NOTREACHED*/
+ }
+ }
+ free(cmdStart);
+ compatChild = 0;
+ if (compatSigno) {
+ bmake_signal(compatSigno, SIG_DFL);
+ kill(myPid, compatSigno);
+ }
+
+ return (status);
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * Compat_Make --
+ * Make a target.
+ *
+ * Input:
+ * gnp The node to make
+ * pgnp Parent to abort if necessary
+ *
+ * Results:
+ * 0
+ *
+ * Side Effects:
+ * If an error is detected and not being ignored, the process exits.
+ *
+ *-----------------------------------------------------------------------
+ */
+int
+Compat_Make(void *gnp, void *pgnp)
+{
+ GNode *gn = (GNode *)gnp;
+ GNode *pgn = (GNode *)pgnp;
+
+ if (!shellName) /* we came here from jobs */
+ Shell_Init();
+ if (gn->made == UNMADE && (gn == pgn || (pgn->type & OP_MADE) == 0)) {
+ /*
+ * First mark ourselves to be made, then apply whatever transformations
+ * the suffix module thinks are necessary. Once that's done, we can
+ * descend and make all our children. If any of them has an error
+ * but the -k flag was given, our 'make' field will be set FALSE again.
+ * This is our signal to not attempt to do anything but abort our
+ * parent as well.
+ */
+ gn->flags |= REMAKE;
+ gn->made = BEINGMADE;
+ if ((gn->type & OP_MADE) == 0)
+ Suff_FindDeps(gn);
+ Lst_ForEach(gn->children, Compat_Make, gn);
+ if ((gn->flags & REMAKE) == 0) {
+ gn->made = ABORTED;
+ pgn->flags &= ~REMAKE;
+ goto cohorts;
+ }
+
+ if (Lst_Member(gn->iParents, pgn) != NULL) {
+ char *p1;
+ Var_Set(IMPSRC, Var_Value(TARGET, gn, &p1), pgn, 0);
+ free(p1);
+ }
+
+ /*
+ * All the children were made ok. Now cmgn->mtime contains the
+ * modification time of the newest child, we need to find out if we
+ * exist and when we were modified last. The criteria for datedness
+ * are defined by the Make_OODate function.
+ */
+ if (DEBUG(MAKE)) {
+ fprintf(debug_file, "Examining %s...", gn->name);
+ }
+ if (! Make_OODate(gn)) {
+ gn->made = UPTODATE;
+ if (DEBUG(MAKE)) {
+ fprintf(debug_file, "up-to-date.\n");
+ }
+ goto cohorts;
+ } else if (DEBUG(MAKE)) {
+ fprintf(debug_file, "out-of-date.\n");
+ }
+
+ /*
+ * If the user is just seeing if something is out-of-date, exit now
+ * to tell him/her "yes".
+ */
+ if (queryFlag) {
+ exit(1);
+ }
+
+ /*
+ * We need to be re-made. We also have to make sure we've got a $?
+ * variable. To be nice, we also define the $> variable using
+ * Make_DoAllVar().
+ */
+ Make_DoAllVar(gn);
+
+ /*
+ * Alter our type to tell if errors should be ignored or things
+ * should not be printed so CompatRunCommand knows what to do.
+ */
+ if (Targ_Ignore(gn)) {
+ gn->type |= OP_IGNORE;
+ }
+ if (Targ_Silent(gn)) {
+ gn->type |= OP_SILENT;
+ }
+
+ if (Job_CheckCommands(gn, Fatal)) {
+ /*
+ * Our commands are ok, but we still have to worry about the -t
+ * flag...
+ */
+ if (!touchFlag || (gn->type & OP_MAKE)) {
+ curTarg = gn;
+#ifdef USE_META
+ if (useMeta && !NoExecute(gn)) {
+ meta_job_start(NULL, gn);
+ }
+#endif
+ Lst_ForEach(gn->commands, CompatRunCommand, gn);
+ curTarg = NULL;
+ } else {
+ Job_Touch(gn, gn->type & OP_SILENT);
+ }
+ } else {
+ gn->made = ERROR;
+ }
+#ifdef USE_META
+ if (useMeta && !NoExecute(gn)) {
+ if (meta_job_finish(NULL) != 0)
+ gn->made = ERROR;
+ }
+#endif
+
+ if (gn->made != ERROR) {
+ /*
+ * If the node was made successfully, mark it so, update
+ * its modification time and timestamp all its parents. Note
+ * that for .ZEROTIME targets, the timestamping isn't done.
+ * This is to keep its state from affecting that of its parent.
+ */
+ gn->made = MADE;
+ pgn->flags |= Make_Recheck(gn) == 0 ? FORCE : 0;
+ if (!(gn->type & OP_EXEC)) {
+ pgn->flags |= CHILDMADE;
+ Make_TimeStamp(pgn, gn);
+ }
+ } else if (keepgoing) {
+ pgn->flags &= ~REMAKE;
+ } else {
+ PrintOnError(gn, "\nStop.");
+ exit(1);
+ }
+ } else if (gn->made == ERROR) {
+ /*
+ * Already had an error when making this beastie. Tell the parent
+ * to abort.
+ */
+ pgn->flags &= ~REMAKE;
+ } else {
+ if (Lst_Member(gn->iParents, pgn) != NULL) {
+ char *p1;
+ Var_Set(IMPSRC, Var_Value(TARGET, gn, &p1), pgn, 0);
+ free(p1);
+ }
+ switch(gn->made) {
+ case BEINGMADE:
+ Error("Graph cycles through %s", gn->name);
+ gn->made = ERROR;
+ pgn->flags &= ~REMAKE;
+ break;
+ case MADE:
+ if ((gn->type & OP_EXEC) == 0) {
+ pgn->flags |= CHILDMADE;
+ Make_TimeStamp(pgn, gn);
+ }
+ break;
+ case UPTODATE:
+ if ((gn->type & OP_EXEC) == 0) {
+ Make_TimeStamp(pgn, gn);
+ }
+ break;
+ default:
+ break;
+ }
+ }
+
+cohorts:
+ Lst_ForEach(gn->cohorts, Compat_Make, pgnp);
+ return (0);
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * Compat_Run --
+ * Initialize this mode and start making.
+ *
+ * Input:
+ * targs List of target nodes to re-create
+ *
+ * Results:
+ * None.
+ *
+ * Side Effects:
+ * Guess what?
+ *
+ *-----------------------------------------------------------------------
+ */
+void
+Compat_Run(Lst targs)
+{
+ GNode *gn = NULL;/* Current root target */
+ int errors; /* Number of targets not remade due to errors */
+
+ if (!shellName)
+ Shell_Init();
+
+ if (bmake_signal(SIGINT, SIG_IGN) != SIG_IGN) {
+ bmake_signal(SIGINT, CompatInterrupt);
+ }
+ if (bmake_signal(SIGTERM, SIG_IGN) != SIG_IGN) {
+ bmake_signal(SIGTERM, CompatInterrupt);
+ }
+ if (bmake_signal(SIGHUP, SIG_IGN) != SIG_IGN) {
+ bmake_signal(SIGHUP, CompatInterrupt);
+ }
+ if (bmake_signal(SIGQUIT, SIG_IGN) != SIG_IGN) {
+ bmake_signal(SIGQUIT, CompatInterrupt);
+ }
+
+ ENDNode = Targ_FindNode(".END", TARG_CREATE);
+ ENDNode->type = OP_SPECIAL;
+ /*
+ * If the user has defined a .BEGIN target, execute the commands attached
+ * to it.
+ */
+ if (!queryFlag) {
+ gn = Targ_FindNode(".BEGIN", TARG_NOCREATE);
+ if (gn != NULL) {
+ Compat_Make(gn, gn);
+ if (gn->made == ERROR) {
+ PrintOnError(gn, "\nStop.");
+ exit(1);
+ }
+ }
+ }
+
+ /*
+ * Expand .USE nodes right now, because they can modify the structure
+ * of the tree.
+ */
+ Make_ExpandUse(targs);
+
+ /*
+ * For each entry in the list of targets to create, call Compat_Make on
+ * it to create the thing. Compat_Make will leave the 'made' field of gn
+ * in one of several states:
+ * UPTODATE gn was already up-to-date
+ * MADE gn was recreated successfully
+ * ERROR An error occurred while gn was being created
+ * ABORTED gn was not remade because one of its inferiors
+ * could not be made due to errors.
+ */
+ errors = 0;
+ while (!Lst_IsEmpty (targs)) {
+ gn = (GNode *)Lst_DeQueue(targs);
+ Compat_Make(gn, gn);
+
+ if (gn->made == UPTODATE) {
+ printf("`%s' is up to date.\n", gn->name);
+ } else if (gn->made == ABORTED) {
+ printf("`%s' not remade because of errors.\n", gn->name);
+ errors += 1;
+ }
+ }
+
+ /*
+ * If the user has defined a .END target, run its commands.
+ */
+ if (errors == 0) {
+ Compat_Make(ENDNode, ENDNode);
+ if (gn->made == ERROR) {
+ PrintOnError(gn, "\nStop.");
+ exit(1);
+ }
+ }
+}
diff --git a/usr.bin/make/cond.c b/usr.bin/make/cond.c
new file mode 100644
index 0000000..7c9c96a
--- /dev/null
+++ b/usr.bin/make/cond.c
@@ -0,0 +1,1436 @@
+/* $NetBSD: cond.c,v 1.75 2017/04/16 20:59:04 riastradh Exp $ */
+
+/*
+ * Copyright (c) 1988, 1989, 1990 The Regents of the University of California.
+ * All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Adam de Boor.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+/*
+ * Copyright (c) 1988, 1989 by Adam de Boor
+ * Copyright (c) 1989 by Berkeley Softworks
+ * All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Adam de Boor.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. All advertising materials mentioning features or use of this software
+ * must display the following acknowledgement:
+ * This product includes software developed by the University of
+ * California, Berkeley and its contributors.
+ * 4. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#ifndef MAKE_NATIVE
+static char rcsid[] = "$NetBSD: cond.c,v 1.75 2017/04/16 20:59:04 riastradh Exp $";
+#else
+#include <sys/cdefs.h>
+#ifndef lint
+#if 0
+static char sccsid[] = "@(#)cond.c 8.2 (Berkeley) 1/2/94";
+#else
+__RCSID("$NetBSD: cond.c,v 1.75 2017/04/16 20:59:04 riastradh Exp $");
+#endif
+#endif /* not lint */
+#endif
+
+/*-
+ * cond.c --
+ * Functions to handle conditionals in a makefile.
+ *
+ * Interface:
+ * Cond_Eval Evaluate the conditional in the passed line.
+ *
+ */
+
+#include <assert.h>
+#include <ctype.h>
+#include <errno.h> /* For strtoul() error checking */
+
+#include "make.h"
+#include "hash.h"
+#include "dir.h"
+#include "buf.h"
+
+/*
+ * The parsing of conditional expressions is based on this grammar:
+ * E -> F || E
+ * E -> F
+ * F -> T && F
+ * F -> T
+ * T -> defined(variable)
+ * T -> make(target)
+ * T -> exists(file)
+ * T -> empty(varspec)
+ * T -> target(name)
+ * T -> commands(name)
+ * T -> symbol
+ * T -> $(varspec) op value
+ * T -> $(varspec) == "string"
+ * T -> $(varspec) != "string"
+ * T -> "string"
+ * T -> ( E )
+ * T -> ! T
+ * op -> == | != | > | < | >= | <=
+ *
+ * 'symbol' is some other symbol to which the default function (condDefProc)
+ * is applied.
+ *
+ * Tokens are scanned from the 'condExpr' string. The scanner (CondToken)
+ * will return TOK_AND for '&' and '&&', TOK_OR for '|' and '||',
+ * TOK_NOT for '!', TOK_LPAREN for '(', TOK_RPAREN for ')' and will evaluate
+ * the other terminal symbols, using either the default function or the
+ * function given in the terminal, and return the result as either TOK_TRUE
+ * or TOK_FALSE.
+ *
+ * TOK_FALSE is 0 and TOK_TRUE 1 so we can directly assign C comparisons.
+ *
+ * All Non-Terminal functions (CondE, CondF and CondT) return TOK_ERROR on
+ * error.
+ */
+typedef enum {
+ TOK_FALSE = 0, TOK_TRUE = 1, TOK_AND, TOK_OR, TOK_NOT,
+ TOK_LPAREN, TOK_RPAREN, TOK_EOF, TOK_NONE, TOK_ERROR
+} Token;
+
+/*-
+ * Structures to handle elegantly the different forms of #if's. The
+ * last two fields are stored in condInvert and condDefProc, respectively.
+ */
+static void CondPushBack(Token);
+static int CondGetArg(char **, char **, const char *);
+static Boolean CondDoDefined(int, const char *);
+static int CondStrMatch(const void *, const void *);
+static Boolean CondDoMake(int, const char *);
+static Boolean CondDoExists(int, const char *);
+static Boolean CondDoTarget(int, const char *);
+static Boolean CondDoCommands(int, const char *);
+static Boolean CondCvtArg(char *, double *);
+static Token CondToken(Boolean);
+static Token CondT(Boolean);
+static Token CondF(Boolean);
+static Token CondE(Boolean);
+static int do_Cond_EvalExpression(Boolean *);
+
+static const struct If {
+ const char *form; /* Form of if */
+ int formlen; /* Length of form */
+ Boolean doNot; /* TRUE if default function should be negated */
+ Boolean (*defProc)(int, const char *); /* Default function to apply */
+} ifs[] = {
+ { "def", 3, FALSE, CondDoDefined },
+ { "ndef", 4, TRUE, CondDoDefined },
+ { "make", 4, FALSE, CondDoMake },
+ { "nmake", 5, TRUE, CondDoMake },
+ { "", 0, FALSE, CondDoDefined },
+ { NULL, 0, FALSE, NULL }
+};
+
+static const struct If *if_info; /* Info for current statement */
+static char *condExpr; /* The expression to parse */
+static Token condPushBack=TOK_NONE; /* Single push-back token used in
+ * parsing */
+
+static unsigned int cond_depth = 0; /* current .if nesting level */
+static unsigned int cond_min_depth = 0; /* depth at makefile open */
+
+/*
+ * Indicate when we should be strict about lhs of comparisons.
+ * TRUE when Cond_EvalExpression is called from Cond_Eval (.if etc)
+ * FALSE when Cond_EvalExpression is called from var.c:ApplyModifiers
+ * since lhs is already expanded and we cannot tell if
+ * it was a variable reference or not.
+ */
+static Boolean lhsStrict;
+
+static int
+istoken(const char *str, const char *tok, size_t len)
+{
+ return strncmp(str, tok, len) == 0 && !isalpha((unsigned char)str[len]);
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * CondPushBack --
+ * Push back the most recent token read. We only need one level of
+ * this, so the thing is just stored in 'condPushback'.
+ *
+ * Input:
+ * t Token to push back into the "stream"
+ *
+ * Results:
+ * None.
+ *
+ * Side Effects:
+ * condPushback is overwritten.
+ *
+ *-----------------------------------------------------------------------
+ */
+static void
+CondPushBack(Token t)
+{
+ condPushBack = t;
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * CondGetArg --
+ * Find the argument of a built-in function.
+ *
+ * Input:
+ * parens TRUE if arg should be bounded by parens
+ *
+ * Results:
+ * The length of the argument and the address of the argument.
+ *
+ * Side Effects:
+ * The pointer is set to point to the closing parenthesis of the
+ * function call.
+ *
+ *-----------------------------------------------------------------------
+ */
+static int
+CondGetArg(char **linePtr, char **argPtr, const char *func)
+{
+ char *cp;
+ int argLen;
+ Buffer buf;
+ int paren_depth;
+ char ch;
+
+ cp = *linePtr;
+ if (func != NULL)
+ /* Skip opening '(' - verfied by caller */
+ cp++;
+
+ if (*cp == '\0') {
+ /*
+ * No arguments whatsoever. Because 'make' and 'defined' aren't really
+ * "reserved words", we don't print a message. I think this is better
+ * than hitting the user with a warning message every time s/he uses
+ * the word 'make' or 'defined' at the beginning of a symbol...
+ */
+ *argPtr = NULL;
+ return (0);
+ }
+
+ while (*cp == ' ' || *cp == '\t') {
+ cp++;
+ }
+
+ /*
+ * Create a buffer for the argument and start it out at 16 characters
+ * long. Why 16? Why not?
+ */
+ Buf_Init(&buf, 16);
+
+ paren_depth = 0;
+ for (;;) {
+ ch = *cp;
+ if (ch == 0 || ch == ' ' || ch == '\t')
+ break;
+ if ((ch == '&' || ch == '|') && paren_depth == 0)
+ break;
+ if (*cp == '$') {
+ /*
+ * Parse the variable spec and install it as part of the argument
+ * if it's valid. We tell Var_Parse to complain on an undefined
+ * variable, so we don't do it too. Nor do we return an error,
+ * though perhaps we should...
+ */
+ char *cp2;
+ int len;
+ void *freeIt;
+
+ cp2 = Var_Parse(cp, VAR_CMD, VARF_UNDEFERR|VARF_WANTRES,
+ &len, &freeIt);
+ Buf_AddBytes(&buf, strlen(cp2), cp2);
+ free(freeIt);
+ cp += len;
+ continue;
+ }
+ if (ch == '(')
+ paren_depth++;
+ else
+ if (ch == ')' && --paren_depth < 0)
+ break;
+ Buf_AddByte(&buf, *cp);
+ cp++;
+ }
+
+ *argPtr = Buf_GetAll(&buf, &argLen);
+ Buf_Destroy(&buf, FALSE);
+
+ while (*cp == ' ' || *cp == '\t') {
+ cp++;
+ }
+
+ if (func != NULL && *cp++ != ')') {
+ Parse_Error(PARSE_WARNING, "Missing closing parenthesis for %s()",
+ func);
+ return (0);
+ }
+
+ *linePtr = cp;
+ return (argLen);
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * CondDoDefined --
+ * Handle the 'defined' function for conditionals.
+ *
+ * Results:
+ * TRUE if the given variable is defined.
+ *
+ * Side Effects:
+ * None.
+ *
+ *-----------------------------------------------------------------------
+ */
+static Boolean
+CondDoDefined(int argLen MAKE_ATTR_UNUSED, const char *arg)
+{
+ char *p1;
+ Boolean result;
+
+ if (Var_Value(arg, VAR_CMD, &p1) != NULL) {
+ result = TRUE;
+ } else {
+ result = FALSE;
+ }
+
+ free(p1);
+ return (result);
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * CondStrMatch --
+ * Front-end for Str_Match so it returns 0 on match and non-zero
+ * on mismatch. Callback function for CondDoMake via Lst_Find
+ *
+ * Results:
+ * 0 if string matches pattern
+ *
+ * Side Effects:
+ * None
+ *
+ *-----------------------------------------------------------------------
+ */
+static int
+CondStrMatch(const void *string, const void *pattern)
+{
+ return(!Str_Match(string, pattern));
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * CondDoMake --
+ * Handle the 'make' function for conditionals.
+ *
+ * Results:
+ * TRUE if the given target is being made.
+ *
+ * Side Effects:
+ * None.
+ *
+ *-----------------------------------------------------------------------
+ */
+static Boolean
+CondDoMake(int argLen MAKE_ATTR_UNUSED, const char *arg)
+{
+ return Lst_Find(create, arg, CondStrMatch) != NULL;
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * CondDoExists --
+ * See if the given file exists.
+ *
+ * Results:
+ * TRUE if the file exists and FALSE if it does not.
+ *
+ * Side Effects:
+ * None.
+ *
+ *-----------------------------------------------------------------------
+ */
+static Boolean
+CondDoExists(int argLen MAKE_ATTR_UNUSED, const char *arg)
+{
+ Boolean result;
+ char *path;
+
+ path = Dir_FindFile(arg, dirSearchPath);
+ if (DEBUG(COND)) {
+ fprintf(debug_file, "exists(%s) result is \"%s\"\n",
+ arg, path ? path : "");
+ }
+ if (path != NULL) {
+ result = TRUE;
+ free(path);
+ } else {
+ result = FALSE;
+ }
+ return (result);
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * CondDoTarget --
+ * See if the given node exists and is an actual target.
+ *
+ * Results:
+ * TRUE if the node exists as a target and FALSE if it does not.
+ *
+ * Side Effects:
+ * None.
+ *
+ *-----------------------------------------------------------------------
+ */
+static Boolean
+CondDoTarget(int argLen MAKE_ATTR_UNUSED, const char *arg)
+{
+ GNode *gn;
+
+ gn = Targ_FindNode(arg, TARG_NOCREATE);
+ return (gn != NULL) && !OP_NOP(gn->type);
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * CondDoCommands --
+ * See if the given node exists and is an actual target with commands
+ * associated with it.
+ *
+ * Results:
+ * TRUE if the node exists as a target and has commands associated with
+ * it and FALSE if it does not.
+ *
+ * Side Effects:
+ * None.
+ *
+ *-----------------------------------------------------------------------
+ */
+static Boolean
+CondDoCommands(int argLen MAKE_ATTR_UNUSED, const char *arg)
+{
+ GNode *gn;
+
+ gn = Targ_FindNode(arg, TARG_NOCREATE);
+ return (gn != NULL) && !OP_NOP(gn->type) && !Lst_IsEmpty(gn->commands);
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * CondCvtArg --
+ * Convert the given number into a double.
+ * We try a base 10 or 16 integer conversion first, if that fails
+ * then we try a floating point conversion instead.
+ *
+ * Results:
+ * Sets 'value' to double value of string.
+ * Returns 'true' if the convertion suceeded
+ *
+ *-----------------------------------------------------------------------
+ */
+static Boolean
+CondCvtArg(char *str, double *value)
+{
+ char *eptr, ech;
+ unsigned long l_val;
+ double d_val;
+
+ errno = 0;
+ if (!*str) {
+ *value = (double)0;
+ return TRUE;
+ }
+ l_val = strtoul(str, &eptr, str[1] == 'x' ? 16 : 10);
+ ech = *eptr;
+ if (ech == 0 && errno != ERANGE) {
+ d_val = str[0] == '-' ? -(double)-l_val : (double)l_val;
+ } else {
+ if (ech != 0 && ech != '.' && ech != 'e' && ech != 'E')
+ return FALSE;
+ d_val = strtod(str, &eptr);
+ if (*eptr)
+ return FALSE;
+ }
+
+ *value = d_val;
+ return TRUE;
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * CondGetString --
+ * Get a string from a variable reference or an optionally quoted
+ * string. This is called for the lhs and rhs of string compares.
+ *
+ * Results:
+ * Sets freeIt if needed,
+ * Sets quoted if string was quoted,
+ * Returns NULL on error,
+ * else returns string - absent any quotes.
+ *
+ * Side Effects:
+ * Moves condExpr to end of this token.
+ *
+ *
+ *-----------------------------------------------------------------------
+ */
+/* coverity:[+alloc : arg-*2] */
+static char *
+CondGetString(Boolean doEval, Boolean *quoted, void **freeIt, Boolean strictLHS)
+{
+ Buffer buf;
+ char *cp;
+ char *str;
+ int len;
+ int qt;
+ char *start;
+
+ Buf_Init(&buf, 0);
+ str = NULL;
+ *freeIt = NULL;
+ *quoted = qt = *condExpr == '"' ? 1 : 0;
+ if (qt)
+ condExpr++;
+ for (start = condExpr; *condExpr && str == NULL; condExpr++) {
+ switch (*condExpr) {
+ case '\\':
+ if (condExpr[1] != '\0') {
+ condExpr++;
+ Buf_AddByte(&buf, *condExpr);
+ }
+ break;
+ case '"':
+ if (qt) {
+ condExpr++; /* we don't want the quotes */
+ goto got_str;
+ } else
+ Buf_AddByte(&buf, *condExpr); /* likely? */
+ break;
+ case ')':
+ case '!':
+ case '=':
+ case '>':
+ case '<':
+ case ' ':
+ case '\t':
+ if (!qt)
+ goto got_str;
+ else
+ Buf_AddByte(&buf, *condExpr);
+ break;
+ case '$':
+ /* if we are in quotes, then an undefined variable is ok */
+ str = Var_Parse(condExpr, VAR_CMD,
+ ((!qt && doEval) ? VARF_UNDEFERR : 0) |
+ VARF_WANTRES, &len, freeIt);
+ if (str == var_Error) {
+ if (*freeIt) {
+ free(*freeIt);
+ *freeIt = NULL;
+ }
+ /*
+ * Even if !doEval, we still report syntax errors, which
+ * is what getting var_Error back with !doEval means.
+ */
+ str = NULL;
+ goto cleanup;
+ }
+ condExpr += len;
+ /*
+ * If the '$' was first char (no quotes), and we are
+ * followed by space, the operator or end of expression,
+ * we are done.
+ */
+ if ((condExpr == start + len) &&
+ (*condExpr == '\0' ||
+ isspace((unsigned char) *condExpr) ||
+ strchr("!=><)", *condExpr))) {
+ goto cleanup;
+ }
+ /*
+ * Nope, we better copy str to buf
+ */
+ for (cp = str; *cp; cp++) {
+ Buf_AddByte(&buf, *cp);
+ }
+ if (*freeIt) {
+ free(*freeIt);
+ *freeIt = NULL;
+ }
+ str = NULL; /* not finished yet */
+ condExpr--; /* don't skip over next char */
+ break;
+ default:
+ if (strictLHS && !qt && *start != '$' &&
+ !isdigit((unsigned char) *start)) {
+ /* lhs must be quoted, a variable reference or number */
+ if (*freeIt) {
+ free(*freeIt);
+ *freeIt = NULL;
+ }
+ str = NULL;
+ goto cleanup;
+ }
+ Buf_AddByte(&buf, *condExpr);
+ break;
+ }
+ }
+ got_str:
+ str = Buf_GetAll(&buf, NULL);
+ *freeIt = str;
+ cleanup:
+ Buf_Destroy(&buf, FALSE);
+ return str;
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * CondToken --
+ * Return the next token from the input.
+ *
+ * Results:
+ * A Token for the next lexical token in the stream.
+ *
+ * Side Effects:
+ * condPushback will be set back to TOK_NONE if it is used.
+ *
+ *-----------------------------------------------------------------------
+ */
+static Token
+compare_expression(Boolean doEval)
+{
+ Token t;
+ char *lhs;
+ char *rhs;
+ char *op;
+ void *lhsFree;
+ void *rhsFree;
+ Boolean lhsQuoted;
+ Boolean rhsQuoted;
+ double left, right;
+
+ t = TOK_ERROR;
+ rhs = NULL;
+ lhsFree = rhsFree = FALSE;
+ lhsQuoted = rhsQuoted = FALSE;
+
+ /*
+ * Parse the variable spec and skip over it, saving its
+ * value in lhs.
+ */
+ lhs = CondGetString(doEval, &lhsQuoted, &lhsFree, lhsStrict);
+ if (!lhs)
+ goto done;
+
+ /*
+ * Skip whitespace to get to the operator
+ */
+ while (isspace((unsigned char) *condExpr))
+ condExpr++;
+
+ /*
+ * Make sure the operator is a valid one. If it isn't a
+ * known relational operator, pretend we got a
+ * != 0 comparison.
+ */
+ op = condExpr;
+ switch (*condExpr) {
+ case '!':
+ case '=':
+ case '<':
+ case '>':
+ if (condExpr[1] == '=') {
+ condExpr += 2;
+ } else {
+ condExpr += 1;
+ }
+ break;
+ default:
+ if (!doEval) {
+ t = TOK_FALSE;
+ goto done;
+ }
+ /* For .ifxxx "..." check for non-empty string. */
+ if (lhsQuoted) {
+ t = lhs[0] != 0;
+ goto done;
+ }
+ /* For .ifxxx <number> compare against zero */
+ if (CondCvtArg(lhs, &left)) {
+ t = left != 0.0;
+ goto done;
+ }
+ /* For .if ${...} check for non-empty string (defProc is ifdef). */
+ if (if_info->form[0] == 0) {
+ t = lhs[0] != 0;
+ goto done;
+ }
+ /* Otherwise action default test ... */
+ t = if_info->defProc(strlen(lhs), lhs) != if_info->doNot;
+ goto done;
+ }
+
+ while (isspace((unsigned char)*condExpr))
+ condExpr++;
+
+ if (*condExpr == '\0') {
+ Parse_Error(PARSE_WARNING,
+ "Missing right-hand-side of operator");
+ goto done;
+ }
+
+ rhs = CondGetString(doEval, &rhsQuoted, &rhsFree, FALSE);
+ if (!rhs)
+ goto done;
+
+ if (rhsQuoted || lhsQuoted) {
+do_string_compare:
+ if (((*op != '!') && (*op != '=')) || (op[1] != '=')) {
+ Parse_Error(PARSE_WARNING,
+ "String comparison operator should be either == or !=");
+ goto done;
+ }
+
+ if (DEBUG(COND)) {
+ fprintf(debug_file, "lhs = \"%s\", rhs = \"%s\", op = %.2s\n",
+ lhs, rhs, op);
+ }
+ /*
+ * Null-terminate rhs and perform the comparison.
+ * t is set to the result.
+ */
+ if (*op == '=') {
+ t = strcmp(lhs, rhs) == 0;
+ } else {
+ t = strcmp(lhs, rhs) != 0;
+ }
+ } else {
+ /*
+ * rhs is either a float or an integer. Convert both the
+ * lhs and the rhs to a double and compare the two.
+ */
+
+ if (!CondCvtArg(lhs, &left) || !CondCvtArg(rhs, &right))
+ goto do_string_compare;
+
+ if (DEBUG(COND)) {
+ fprintf(debug_file, "left = %f, right = %f, op = %.2s\n", left,
+ right, op);
+ }
+ switch(op[0]) {
+ case '!':
+ if (op[1] != '=') {
+ Parse_Error(PARSE_WARNING,
+ "Unknown operator");
+ goto done;
+ }
+ t = (left != right);
+ break;
+ case '=':
+ if (op[1] != '=') {
+ Parse_Error(PARSE_WARNING,
+ "Unknown operator");
+ goto done;
+ }
+ t = (left == right);
+ break;
+ case '<':
+ if (op[1] == '=') {
+ t = (left <= right);
+ } else {
+ t = (left < right);
+ }
+ break;
+ case '>':
+ if (op[1] == '=') {
+ t = (left >= right);
+ } else {
+ t = (left > right);
+ }
+ break;
+ }
+ }
+
+done:
+ free(lhsFree);
+ free(rhsFree);
+ return t;
+}
+
+static int
+get_mpt_arg(char **linePtr, char **argPtr, const char *func MAKE_ATTR_UNUSED)
+{
+ /*
+ * Use Var_Parse to parse the spec in parens and return
+ * TOK_TRUE if the resulting string is empty.
+ */
+ int length;
+ void *freeIt;
+ char *val;
+ char *cp = *linePtr;
+
+ /* We do all the work here and return the result as the length */
+ *argPtr = NULL;
+
+ val = Var_Parse(cp - 1, VAR_CMD, VARF_WANTRES, &length, &freeIt);
+ /*
+ * Advance *linePtr to beyond the closing ). Note that
+ * we subtract one because 'length' is calculated from 'cp - 1'.
+ */
+ *linePtr = cp - 1 + length;
+
+ if (val == var_Error) {
+ free(freeIt);
+ return -1;
+ }
+
+ /* A variable is empty when it just contains spaces... 4/15/92, christos */
+ while (isspace(*(unsigned char *)val))
+ val++;
+
+ /*
+ * For consistency with the other functions we can't generate the
+ * true/false here.
+ */
+ length = *val ? 2 : 1;
+ free(freeIt);
+ return length;
+}
+
+static Boolean
+CondDoEmpty(int arglen, const char *arg MAKE_ATTR_UNUSED)
+{
+ return arglen == 1;
+}
+
+static Token
+compare_function(Boolean doEval)
+{
+ static const struct fn_def {
+ const char *fn_name;
+ int fn_name_len;
+ int (*fn_getarg)(char **, char **, const char *);
+ Boolean (*fn_proc)(int, const char *);
+ } fn_defs[] = {
+ { "defined", 7, CondGetArg, CondDoDefined },
+ { "make", 4, CondGetArg, CondDoMake },
+ { "exists", 6, CondGetArg, CondDoExists },
+ { "empty", 5, get_mpt_arg, CondDoEmpty },
+ { "target", 6, CondGetArg, CondDoTarget },
+ { "commands", 8, CondGetArg, CondDoCommands },
+ { NULL, 0, NULL, NULL },
+ };
+ const struct fn_def *fn_def;
+ Token t;
+ char *arg = NULL;
+ int arglen;
+ char *cp = condExpr;
+ char *cp1;
+
+ for (fn_def = fn_defs; fn_def->fn_name != NULL; fn_def++) {
+ if (!istoken(cp, fn_def->fn_name, fn_def->fn_name_len))
+ continue;
+ cp += fn_def->fn_name_len;
+ /* There can only be whitespace before the '(' */
+ while (isspace(*(unsigned char *)cp))
+ cp++;
+ if (*cp != '(')
+ break;
+
+ arglen = fn_def->fn_getarg(&cp, &arg, fn_def->fn_name);
+ if (arglen <= 0) {
+ condExpr = cp;
+ return arglen < 0 ? TOK_ERROR : TOK_FALSE;
+ }
+ /* Evaluate the argument using the required function. */
+ t = !doEval || fn_def->fn_proc(arglen, arg);
+ free(arg);
+ condExpr = cp;
+ return t;
+ }
+
+ /* Push anything numeric through the compare expression */
+ cp = condExpr;
+ if (isdigit((unsigned char)cp[0]) || strchr("+-", cp[0]))
+ return compare_expression(doEval);
+
+ /*
+ * Most likely we have a naked token to apply the default function to.
+ * However ".if a == b" gets here when the "a" is unquoted and doesn't
+ * start with a '$'. This surprises people.
+ * If what follows the function argument is a '=' or '!' then the syntax
+ * would be invalid if we did "defined(a)" - so instead treat as an
+ * expression.
+ */
+ arglen = CondGetArg(&cp, &arg, NULL);
+ for (cp1 = cp; isspace(*(unsigned char *)cp1); cp1++)
+ continue;
+ if (*cp1 == '=' || *cp1 == '!')
+ return compare_expression(doEval);
+ condExpr = cp;
+
+ /*
+ * Evaluate the argument using the default function.
+ * This path always treats .if as .ifdef. To get here the character
+ * after .if must have been taken literally, so the argument cannot
+ * be empty - even if it contained a variable expansion.
+ */
+ t = !doEval || if_info->defProc(arglen, arg) != if_info->doNot;
+ free(arg);
+ return t;
+}
+
+static Token
+CondToken(Boolean doEval)
+{
+ Token t;
+
+ t = condPushBack;
+ if (t != TOK_NONE) {
+ condPushBack = TOK_NONE;
+ return t;
+ }
+
+ while (*condExpr == ' ' || *condExpr == '\t') {
+ condExpr++;
+ }
+
+ switch (*condExpr) {
+
+ case '(':
+ condExpr++;
+ return TOK_LPAREN;
+
+ case ')':
+ condExpr++;
+ return TOK_RPAREN;
+
+ case '|':
+ if (condExpr[1] == '|') {
+ condExpr++;
+ }
+ condExpr++;
+ return TOK_OR;
+
+ case '&':
+ if (condExpr[1] == '&') {
+ condExpr++;
+ }
+ condExpr++;
+ return TOK_AND;
+
+ case '!':
+ condExpr++;
+ return TOK_NOT;
+
+ case '#':
+ case '\n':
+ case '\0':
+ return TOK_EOF;
+
+ case '"':
+ case '$':
+ return compare_expression(doEval);
+
+ default:
+ return compare_function(doEval);
+ }
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * CondT --
+ * Parse a single term in the expression. This consists of a terminal
+ * symbol or TOK_NOT and a terminal symbol (not including the binary
+ * operators):
+ * T -> defined(variable) | make(target) | exists(file) | symbol
+ * T -> ! T | ( E )
+ *
+ * Results:
+ * TOK_TRUE, TOK_FALSE or TOK_ERROR.
+ *
+ * Side Effects:
+ * Tokens are consumed.
+ *
+ *-----------------------------------------------------------------------
+ */
+static Token
+CondT(Boolean doEval)
+{
+ Token t;
+
+ t = CondToken(doEval);
+
+ if (t == TOK_EOF) {
+ /*
+ * If we reached the end of the expression, the expression
+ * is malformed...
+ */
+ t = TOK_ERROR;
+ } else if (t == TOK_LPAREN) {
+ /*
+ * T -> ( E )
+ */
+ t = CondE(doEval);
+ if (t != TOK_ERROR) {
+ if (CondToken(doEval) != TOK_RPAREN) {
+ t = TOK_ERROR;
+ }
+ }
+ } else if (t == TOK_NOT) {
+ t = CondT(doEval);
+ if (t == TOK_TRUE) {
+ t = TOK_FALSE;
+ } else if (t == TOK_FALSE) {
+ t = TOK_TRUE;
+ }
+ }
+ return (t);
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * CondF --
+ * Parse a conjunctive factor (nice name, wot?)
+ * F -> T && F | T
+ *
+ * Results:
+ * TOK_TRUE, TOK_FALSE or TOK_ERROR
+ *
+ * Side Effects:
+ * Tokens are consumed.
+ *
+ *-----------------------------------------------------------------------
+ */
+static Token
+CondF(Boolean doEval)
+{
+ Token l, o;
+
+ l = CondT(doEval);
+ if (l != TOK_ERROR) {
+ o = CondToken(doEval);
+
+ if (o == TOK_AND) {
+ /*
+ * F -> T && F
+ *
+ * If T is TOK_FALSE, the whole thing will be TOK_FALSE, but we have to
+ * parse the r.h.s. anyway (to throw it away).
+ * If T is TOK_TRUE, the result is the r.h.s., be it an TOK_ERROR or no.
+ */
+ if (l == TOK_TRUE) {
+ l = CondF(doEval);
+ } else {
+ (void)CondF(FALSE);
+ }
+ } else {
+ /*
+ * F -> T
+ */
+ CondPushBack(o);
+ }
+ }
+ return (l);
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * CondE --
+ * Main expression production.
+ * E -> F || E | F
+ *
+ * Results:
+ * TOK_TRUE, TOK_FALSE or TOK_ERROR.
+ *
+ * Side Effects:
+ * Tokens are, of course, consumed.
+ *
+ *-----------------------------------------------------------------------
+ */
+static Token
+CondE(Boolean doEval)
+{
+ Token l, o;
+
+ l = CondF(doEval);
+ if (l != TOK_ERROR) {
+ o = CondToken(doEval);
+
+ if (o == TOK_OR) {
+ /*
+ * E -> F || E
+ *
+ * A similar thing occurs for ||, except that here we make sure
+ * the l.h.s. is TOK_FALSE before we bother to evaluate the r.h.s.
+ * Once again, if l is TOK_FALSE, the result is the r.h.s. and once
+ * again if l is TOK_TRUE, we parse the r.h.s. to throw it away.
+ */
+ if (l == TOK_FALSE) {
+ l = CondE(doEval);
+ } else {
+ (void)CondE(FALSE);
+ }
+ } else {
+ /*
+ * E -> F
+ */
+ CondPushBack(o);
+ }
+ }
+ return (l);
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * Cond_EvalExpression --
+ * Evaluate an expression in the passed line. The expression
+ * consists of &&, ||, !, make(target), defined(variable)
+ * and parenthetical groupings thereof.
+ *
+ * Results:
+ * COND_PARSE if the condition was valid grammatically
+ * COND_INVALID if not a valid conditional.
+ *
+ * (*value) is set to the boolean value of the condition
+ *
+ * Side Effects:
+ * None.
+ *
+ *-----------------------------------------------------------------------
+ */
+int
+Cond_EvalExpression(const struct If *info, char *line, Boolean *value, int eprint, Boolean strictLHS)
+{
+ static const struct If *dflt_info;
+ const struct If *sv_if_info = if_info;
+ char *sv_condExpr = condExpr;
+ Token sv_condPushBack = condPushBack;
+ int rval;
+
+ lhsStrict = strictLHS;
+
+ while (*line == ' ' || *line == '\t')
+ line++;
+
+ if (info == NULL && (info = dflt_info) == NULL) {
+ /* Scan for the entry for .if - it can't be first */
+ for (info = ifs; ; info++)
+ if (info->form[0] == 0)
+ break;
+ dflt_info = info;
+ }
+ assert(info != NULL);
+
+ if_info = info;
+ condExpr = line;
+ condPushBack = TOK_NONE;
+
+ rval = do_Cond_EvalExpression(value);
+
+ if (rval == COND_INVALID && eprint)
+ Parse_Error(PARSE_FATAL, "Malformed conditional (%s)", line);
+
+ if_info = sv_if_info;
+ condExpr = sv_condExpr;
+ condPushBack = sv_condPushBack;
+
+ return rval;
+}
+
+static int
+do_Cond_EvalExpression(Boolean *value)
+{
+
+ switch (CondE(TRUE)) {
+ case TOK_TRUE:
+ if (CondToken(TRUE) == TOK_EOF) {
+ *value = TRUE;
+ return COND_PARSE;
+ }
+ break;
+ case TOK_FALSE:
+ if (CondToken(TRUE) == TOK_EOF) {
+ *value = FALSE;
+ return COND_PARSE;
+ }
+ break;
+ default:
+ case TOK_ERROR:
+ break;
+ }
+
+ return COND_INVALID;
+}
+
+
+/*-
+ *-----------------------------------------------------------------------
+ * Cond_Eval --
+ * Evaluate the conditional in the passed line. The line
+ * looks like this:
+ * .<cond-type> <expr>
+ * where <cond-type> is any of if, ifmake, ifnmake, ifdef,
+ * ifndef, elif, elifmake, elifnmake, elifdef, elifndef
+ * and <expr> consists of &&, ||, !, make(target), defined(variable)
+ * and parenthetical groupings thereof.
+ *
+ * Input:
+ * line Line to parse
+ *
+ * Results:
+ * COND_PARSE if should parse lines after the conditional
+ * COND_SKIP if should skip lines after the conditional
+ * COND_INVALID if not a valid conditional.
+ *
+ * Side Effects:
+ * None.
+ *
+ * Note that the states IF_ACTIVE and ELSE_ACTIVE are only different in order
+ * to detect splurious .else lines (as are SKIP_TO_ELSE and SKIP_TO_ENDIF)
+ * otherwise .else could be treated as '.elif 1'.
+ *
+ *-----------------------------------------------------------------------
+ */
+int
+Cond_Eval(char *line)
+{
+#define MAXIF 128 /* maximum depth of .if'ing */
+#define MAXIF_BUMP 32 /* how much to grow by */
+ enum if_states {
+ IF_ACTIVE, /* .if or .elif part active */
+ ELSE_ACTIVE, /* .else part active */
+ SEARCH_FOR_ELIF, /* searching for .elif/else to execute */
+ SKIP_TO_ELSE, /* has been true, but not seen '.else' */
+ SKIP_TO_ENDIF /* nothing else to execute */
+ };
+ static enum if_states *cond_state = NULL;
+ static unsigned int max_if_depth = MAXIF;
+
+ const struct If *ifp;
+ Boolean isElif;
+ Boolean value;
+ int level; /* Level at which to report errors. */
+ enum if_states state;
+
+ level = PARSE_FATAL;
+ if (!cond_state) {
+ cond_state = bmake_malloc(max_if_depth * sizeof(*cond_state));
+ cond_state[0] = IF_ACTIVE;
+ }
+ /* skip leading character (the '.') and any whitespace */
+ for (line++; *line == ' ' || *line == '\t'; line++)
+ continue;
+
+ /* Find what type of if we're dealing with. */
+ if (line[0] == 'e') {
+ if (line[1] != 'l') {
+ if (!istoken(line + 1, "ndif", 4))
+ return COND_INVALID;
+ /* End of conditional section */
+ if (cond_depth == cond_min_depth) {
+ Parse_Error(level, "if-less endif");
+ return COND_PARSE;
+ }
+ /* Return state for previous conditional */
+ cond_depth--;
+ return cond_state[cond_depth] <= ELSE_ACTIVE ? COND_PARSE : COND_SKIP;
+ }
+
+ /* Quite likely this is 'else' or 'elif' */
+ line += 2;
+ if (istoken(line, "se", 2)) {
+ /* It is else... */
+ if (cond_depth == cond_min_depth) {
+ Parse_Error(level, "if-less else");
+ return COND_PARSE;
+ }
+
+ state = cond_state[cond_depth];
+ switch (state) {
+ case SEARCH_FOR_ELIF:
+ state = ELSE_ACTIVE;
+ break;
+ case ELSE_ACTIVE:
+ case SKIP_TO_ENDIF:
+ Parse_Error(PARSE_WARNING, "extra else");
+ /* FALLTHROUGH */
+ default:
+ case IF_ACTIVE:
+ case SKIP_TO_ELSE:
+ state = SKIP_TO_ENDIF;
+ break;
+ }
+ cond_state[cond_depth] = state;
+ return state <= ELSE_ACTIVE ? COND_PARSE : COND_SKIP;
+ }
+ /* Assume for now it is an elif */
+ isElif = TRUE;
+ } else
+ isElif = FALSE;
+
+ if (line[0] != 'i' || line[1] != 'f')
+ /* Not an ifxxx or elifxxx line */
+ return COND_INVALID;
+
+ /*
+ * Figure out what sort of conditional it is -- what its default
+ * function is, etc. -- by looking in the table of valid "ifs"
+ */
+ line += 2;
+ for (ifp = ifs; ; ifp++) {
+ if (ifp->form == NULL)
+ return COND_INVALID;
+ if (istoken(ifp->form, line, ifp->formlen)) {
+ line += ifp->formlen;
+ break;
+ }
+ }
+
+ /* Now we know what sort of 'if' it is... */
+
+ if (isElif) {
+ if (cond_depth == cond_min_depth) {
+ Parse_Error(level, "if-less elif");
+ return COND_PARSE;
+ }
+ state = cond_state[cond_depth];
+ if (state == SKIP_TO_ENDIF || state == ELSE_ACTIVE) {
+ Parse_Error(PARSE_WARNING, "extra elif");
+ cond_state[cond_depth] = SKIP_TO_ENDIF;
+ return COND_SKIP;
+ }
+ if (state != SEARCH_FOR_ELIF) {
+ /* Either just finished the 'true' block, or already SKIP_TO_ELSE */
+ cond_state[cond_depth] = SKIP_TO_ELSE;
+ return COND_SKIP;
+ }
+ } else {
+ /* Normal .if */
+ if (cond_depth + 1 >= max_if_depth) {
+ /*
+ * This is rare, but not impossible.
+ * In meta mode, dirdeps.mk (only runs at level 0)
+ * can need more than the default.
+ */
+ max_if_depth += MAXIF_BUMP;
+ cond_state = bmake_realloc(cond_state, max_if_depth *
+ sizeof(*cond_state));
+ }
+ state = cond_state[cond_depth];
+ cond_depth++;
+ if (state > ELSE_ACTIVE) {
+ /* If we aren't parsing the data, treat as always false */
+ cond_state[cond_depth] = SKIP_TO_ELSE;
+ return COND_SKIP;
+ }
+ }
+
+ /* And evaluate the conditional expresssion */
+ if (Cond_EvalExpression(ifp, line, &value, 1, TRUE) == COND_INVALID) {
+ /* Syntax error in conditional, error message already output. */
+ /* Skip everything to matching .endif */
+ cond_state[cond_depth] = SKIP_TO_ELSE;
+ return COND_SKIP;
+ }
+
+ if (!value) {
+ cond_state[cond_depth] = SEARCH_FOR_ELIF;
+ return COND_SKIP;
+ }
+ cond_state[cond_depth] = IF_ACTIVE;
+ return COND_PARSE;
+}
+
+
+
+/*-
+ *-----------------------------------------------------------------------
+ * Cond_End --
+ * Make sure everything's clean at the end of a makefile.
+ *
+ * Results:
+ * None.
+ *
+ * Side Effects:
+ * Parse_Error will be called if open conditionals are around.
+ *
+ *-----------------------------------------------------------------------
+ */
+void
+Cond_restore_depth(unsigned int saved_depth)
+{
+ int open_conds = cond_depth - cond_min_depth;
+
+ if (open_conds != 0 || saved_depth > cond_depth) {
+ Parse_Error(PARSE_FATAL, "%d open conditional%s", open_conds,
+ open_conds == 1 ? "" : "s");
+ cond_depth = cond_min_depth;
+ }
+
+ cond_min_depth = saved_depth;
+}
+
+unsigned int
+Cond_save_depth(void)
+{
+ int depth = cond_min_depth;
+
+ cond_min_depth = cond_depth;
+ return depth;
+}
diff --git a/usr.bin/make/config.h b/usr.bin/make/config.h
new file mode 100644
index 0000000..06af09c
--- /dev/null
+++ b/usr.bin/make/config.h
@@ -0,0 +1,160 @@
+/* $NetBSD: config.h,v 1.21 2012/03/31 00:12:24 christos Exp $ */
+
+/*
+ * Copyright (c) 1988, 1989, 1990 The Regents of the University of California.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Adam de Boor.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * from: @(#)config.h 8.1 (Berkeley) 6/6/93
+ */
+
+/*
+ * Copyright (c) 1988, 1989 by Adam de Boor
+ * Copyright (c) 1989 by Berkeley Softworks
+ * All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Adam de Boor.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. All advertising materials mentioning features or use of this software
+ * must display the following acknowledgement:
+ * This product includes software developed by the University of
+ * California, Berkeley and its contributors.
+ * 4. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * from: @(#)config.h 8.1 (Berkeley) 6/6/93
+ */
+
+/*
+ * DEFMAXJOBS
+ * DEFMAXLOCAL
+ * These control the default concurrency. On no occasion will more
+ * than DEFMAXJOBS targets be created at once (locally or remotely)
+ * DEFMAXLOCAL is the highest number of targets which will be
+ * created on the local machine at once. Note that if you set this
+ * to 0, nothing will ever happen...
+ */
+#define DEFMAXJOBS 4
+#define DEFMAXLOCAL 1
+
+/*
+ * INCLUDES
+ * LIBRARIES
+ * These control the handling of the .INCLUDES and .LIBS variables.
+ * If INCLUDES is defined, the .INCLUDES variable will be filled
+ * from the search paths of those suffixes which are marked by
+ * .INCLUDES dependency lines. Similarly for LIBRARIES and .LIBS
+ * See suff.c for more details.
+ */
+#define INCLUDES
+#define LIBRARIES
+
+/*
+ * LIBSUFF
+ * Is the suffix used to denote libraries and is used by the Suff module
+ * to find the search path on which to seek any -l<xx> targets.
+ *
+ * RECHECK
+ * If defined, Make_Update will check a target for its current
+ * modification time after it has been re-made, setting it to the
+ * starting time of the make only if the target still doesn't exist.
+ * Unfortunately, under NFS the modification time often doesn't
+ * get updated in time, so a target will appear to not have been
+ * re-made, causing later targets to appear up-to-date. On systems
+ * that don't have this problem, you should defined this. Under
+ * NFS you probably should not, unless you aren't exporting jobs.
+ */
+#define LIBSUFF ".a"
+#define RECHECK
+
+/*
+ * POSIX
+ * Adhere to the POSIX 1003.2 draft for the make(1) program.
+ * - Use MAKEFLAGS instead of MAKE to pick arguments from the
+ * environment.
+ * - Allow empty command lines if starting with tab.
+ */
+#define POSIX
+
+/*
+ * SYSVINCLUDE
+ * Recognize system V like include directives [include "filename"]
+ * SYSVVARSUB
+ * Recognize system V like ${VAR:x=y} variable substitutions
+ */
+#define SYSVINCLUDE
+#define SYSVVARSUB
+
+/*
+ * GMAKEEXPORT
+ * Recognize gmake like variable export directives [export <VAR>=<VALUE>]
+ */
+#define GMAKEEXPORT
+
+/*
+ * SUNSHCMD
+ * Recognize SunOS and Solaris:
+ * VAR :sh= CMD # Assign VAR to the command substitution of CMD
+ * ${VAR:sh} # Return the command substitution of the value
+ * # of ${VAR}
+ */
+#define SUNSHCMD
+
+/*
+ * USE_IOVEC
+ * We have writev(2)
+ */
+#define USE_IOVEC
+
+#if defined(MAKE_NATIVE) && !defined(__ELF__)
+# ifndef RANLIBMAG
+# define RANLIBMAG "__.SYMDEF"
+# endif
+#endif
diff --git a/usr.bin/make/dir.c b/usr.bin/make/dir.c
new file mode 100644
index 0000000..0062ac0
--- /dev/null
+++ b/usr.bin/make/dir.c
@@ -0,0 +1,1849 @@
+/* $NetBSD: dir.c,v 1.73 2018/07/12 18:03:31 christos Exp $ */
+
+/*
+ * Copyright (c) 1988, 1989, 1990 The Regents of the University of California.
+ * All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Adam de Boor.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+/*
+ * Copyright (c) 1988, 1989 by Adam de Boor
+ * Copyright (c) 1989 by Berkeley Softworks
+ * All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Adam de Boor.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. All advertising materials mentioning features or use of this software
+ * must display the following acknowledgement:
+ * This product includes software developed by the University of
+ * California, Berkeley and its contributors.
+ * 4. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#ifndef MAKE_NATIVE
+static char rcsid[] = "$NetBSD: dir.c,v 1.73 2018/07/12 18:03:31 christos Exp $";
+#else
+#include <sys/cdefs.h>
+#ifndef lint
+#if 0
+static char sccsid[] = "@(#)dir.c 8.2 (Berkeley) 1/2/94";
+#else
+__RCSID("$NetBSD: dir.c,v 1.73 2018/07/12 18:03:31 christos Exp $");
+#endif
+#endif /* not lint */
+#endif
+
+/*-
+ * dir.c --
+ * Directory searching using wildcards and/or normal names...
+ * Used both for source wildcarding in the Makefile and for finding
+ * implicit sources.
+ *
+ * The interface for this module is:
+ * Dir_Init Initialize the module.
+ *
+ * Dir_InitCur Set the cur Path.
+ *
+ * Dir_InitDot Set the dot Path.
+ *
+ * Dir_End Cleanup the module.
+ *
+ * Dir_SetPATH Set ${.PATH} to reflect state of dirSearchPath.
+ *
+ * Dir_HasWildcards Returns TRUE if the name given it needs to
+ * be wildcard-expanded.
+ *
+ * Dir_Expand Given a pattern and a path, return a Lst of names
+ * which match the pattern on the search path.
+ *
+ * Dir_FindFile Searches for a file on a given search path.
+ * If it exists, the entire path is returned.
+ * Otherwise NULL is returned.
+ *
+ * Dir_FindHereOrAbove Search for a path in the current directory and
+ * then all the directories above it in turn until
+ * the path is found or we reach the root ("/").
+ *
+ * Dir_MTime Return the modification time of a node. The file
+ * is searched for along the default search path.
+ * The path and mtime fields of the node are filled
+ * in.
+ *
+ * Dir_AddDir Add a directory to a search path.
+ *
+ * Dir_MakeFlags Given a search path and a command flag, create
+ * a string with each of the directories in the path
+ * preceded by the command flag and all of them
+ * separated by a space.
+ *
+ * Dir_Destroy Destroy an element of a search path. Frees up all
+ * things that can be freed for the element as long
+ * as the element is no longer referenced by any other
+ * search path.
+ * Dir_ClearPath Resets a search path to the empty list.
+ *
+ * For debugging:
+ * Dir_PrintDirectories Print stats about the directory cache.
+ */
+
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#include <dirent.h>
+#include <errno.h>
+#include <stdio.h>
+
+#include "make.h"
+#include "hash.h"
+#include "dir.h"
+#include "job.h"
+
+/*
+ * A search path consists of a Lst of Path structures. A Path structure
+ * has in it the name of the directory and a hash table of all the files
+ * in the directory. This is used to cut down on the number of system
+ * calls necessary to find implicit dependents and their like. Since
+ * these searches are made before any actions are taken, we need not
+ * worry about the directory changing due to creation commands. If this
+ * hampers the style of some makefiles, they must be changed.
+ *
+ * A list of all previously-read directories is kept in the
+ * openDirectories Lst. This list is checked first before a directory
+ * is opened.
+ *
+ * The need for the caching of whole directories is brought about by
+ * the multi-level transformation code in suff.c, which tends to search
+ * for far more files than regular make does. In the initial
+ * implementation, the amount of time spent performing "stat" calls was
+ * truly astronomical. The problem with hashing at the start is,
+ * of course, that pmake doesn't then detect changes to these directories
+ * during the course of the make. Three possibilities suggest themselves:
+ *
+ * 1) just use stat to test for a file's existence. As mentioned
+ * above, this is very inefficient due to the number of checks
+ * engendered by the multi-level transformation code.
+ * 2) use readdir() and company to search the directories, keeping
+ * them open between checks. I have tried this and while it
+ * didn't slow down the process too much, it could severely
+ * affect the amount of parallelism available as each directory
+ * open would take another file descriptor out of play for
+ * handling I/O for another job. Given that it is only recently
+ * that UNIX OS's have taken to allowing more than 20 or 32
+ * file descriptors for a process, this doesn't seem acceptable
+ * to me.
+ * 3) record the mtime of the directory in the Path structure and
+ * verify the directory hasn't changed since the contents were
+ * hashed. This will catch the creation or deletion of files,
+ * but not the updating of files. However, since it is the
+ * creation and deletion that is the problem, this could be
+ * a good thing to do. Unfortunately, if the directory (say ".")
+ * were fairly large and changed fairly frequently, the constant
+ * rehashing could seriously degrade performance. It might be
+ * good in such cases to keep track of the number of rehashes
+ * and if the number goes over a (small) limit, resort to using
+ * stat in its place.
+ *
+ * An additional thing to consider is that pmake is used primarily
+ * to create C programs and until recently pcc-based compilers refused
+ * to allow you to specify where the resulting object file should be
+ * placed. This forced all objects to be created in the current
+ * directory. This isn't meant as a full excuse, just an explanation of
+ * some of the reasons for the caching used here.
+ *
+ * One more note: the location of a target's file is only performed
+ * on the downward traversal of the graph and then only for terminal
+ * nodes in the graph. This could be construed as wrong in some cases,
+ * but prevents inadvertent modification of files when the "installed"
+ * directory for a file is provided in the search path.
+ *
+ * Another data structure maintained by this module is an mtime
+ * cache used when the searching of cached directories fails to find
+ * a file. In the past, Dir_FindFile would simply perform an access()
+ * call in such a case to determine if the file could be found using
+ * just the name given. When this hit, however, all that was gained
+ * was the knowledge that the file existed. Given that an access() is
+ * essentially a stat() without the copyout() call, and that the same
+ * filesystem overhead would have to be incurred in Dir_MTime, it made
+ * sense to replace the access() with a stat() and record the mtime
+ * in a cache for when Dir_MTime was actually called.
+ */
+
+Lst dirSearchPath; /* main search path */
+
+static Lst openDirectories; /* the list of all open directories */
+
+/*
+ * Variables for gathering statistics on the efficiency of the hashing
+ * mechanism.
+ */
+static int hits, /* Found in directory cache */
+ misses, /* Sad, but not evil misses */
+ nearmisses, /* Found under search path */
+ bigmisses; /* Sought by itself */
+
+static Path *dot; /* contents of current directory */
+static Path *cur; /* contents of current directory, if not dot */
+static Path *dotLast; /* a fake path entry indicating we need to
+ * look for . last */
+static Hash_Table mtimes; /* Results of doing a last-resort stat in
+ * Dir_FindFile -- if we have to go to the
+ * system to find the file, we might as well
+ * have its mtime on record. XXX: If this is done
+ * way early, there's a chance other rules will
+ * have already updated the file, in which case
+ * we'll update it again. Generally, there won't
+ * be two rules to update a single file, so this
+ * should be ok, but... */
+
+static Hash_Table lmtimes; /* same as mtimes but for lstat */
+
+static int DirFindName(const void *, const void *);
+static int DirMatchFiles(const char *, Path *, Lst);
+static void DirExpandCurly(const char *, const char *, Lst, Lst);
+static void DirExpandInt(const char *, Lst, Lst);
+static int DirPrintWord(void *, void *);
+static int DirPrintDir(void *, void *);
+static char *DirLookup(Path *, const char *, const char *, Boolean);
+static char *DirLookupSubdir(Path *, const char *);
+static char *DirFindDot(Boolean, const char *, const char *);
+static char *DirLookupAbs(Path *, const char *, const char *);
+
+
+/*
+ * We use stat(2) a lot, cache the results
+ * mtime and mode are all we care about.
+ */
+struct cache_st {
+ time_t mtime;
+ mode_t mode;
+};
+
+/* minimize changes below */
+#define CST_LSTAT 1
+#define CST_UPDATE 2
+
+static int
+cached_stats(Hash_Table *htp, const char *pathname, struct stat *st, int flags)
+{
+ Hash_Entry *entry;
+ struct cache_st *cst;
+ int rc;
+
+ if (!pathname || !pathname[0])
+ return -1;
+
+ entry = Hash_FindEntry(htp, pathname);
+
+ if (entry && (flags & CST_UPDATE) == 0) {
+ cst = entry->clientPtr;
+
+ memset(st, 0, sizeof(*st));
+ st->st_mtime = cst->mtime;
+ st->st_mode = cst->mode;
+ if (DEBUG(DIR)) {
+ fprintf(debug_file, "Using cached time %s for %s\n",
+ Targ_FmtTime(st->st_mtime), pathname);
+ }
+ return 0;
+ }
+
+ rc = (flags & CST_LSTAT) ? lstat(pathname, st) : stat(pathname, st);
+ if (rc == -1)
+ return -1;
+
+ if (st->st_mtime == 0)
+ st->st_mtime = 1; /* avoid confusion with missing file */
+
+ if (!entry)
+ entry = Hash_CreateEntry(htp, pathname, NULL);
+ if (!entry->clientPtr)
+ entry->clientPtr = bmake_malloc(sizeof(*cst));
+ cst = entry->clientPtr;
+ cst->mtime = st->st_mtime;
+ cst->mode = st->st_mode;
+ if (DEBUG(DIR)) {
+ fprintf(debug_file, " Caching %s for %s\n",
+ Targ_FmtTime(st->st_mtime), pathname);
+ }
+
+ return 0;
+}
+
+int
+cached_stat(const char *pathname, void *st)
+{
+ return cached_stats(&mtimes, pathname, st, 0);
+}
+
+int
+cached_lstat(const char *pathname, void *st)
+{
+ return cached_stats(&lmtimes, pathname, st, CST_LSTAT);
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * Dir_Init --
+ * initialize things for this module
+ *
+ * Results:
+ * none
+ *
+ * Side Effects:
+ * some directories may be opened.
+ *-----------------------------------------------------------------------
+ */
+void
+Dir_Init(const char *cdname)
+{
+ if (!cdname) {
+ dirSearchPath = Lst_Init(FALSE);
+ openDirectories = Lst_Init(FALSE);
+ Hash_InitTable(&mtimes, 0);
+ Hash_InitTable(&lmtimes, 0);
+ return;
+ }
+ Dir_InitCur(cdname);
+
+ dotLast = bmake_malloc(sizeof(Path));
+ dotLast->refCount = 1;
+ dotLast->hits = 0;
+ dotLast->name = bmake_strdup(".DOTLAST");
+ Hash_InitTable(&dotLast->files, -1);
+}
+
+/*
+ * Called by Dir_Init() and whenever .CURDIR is assigned to.
+ */
+void
+Dir_InitCur(const char *cdname)
+{
+ Path *p;
+
+ if (cdname != NULL) {
+ /*
+ * Our build directory is not the same as our source directory.
+ * Keep this one around too.
+ */
+ if ((p = Dir_AddDir(NULL, cdname))) {
+ p->refCount += 1;
+ if (cur && cur != p) {
+ /*
+ * We've been here before, cleanup.
+ */
+ cur->refCount -= 1;
+ Dir_Destroy(cur);
+ }
+ cur = p;
+ }
+ }
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * Dir_InitDot --
+ * (re)initialize "dot" (current/object directory) path hash
+ *
+ * Results:
+ * none
+ *
+ * Side Effects:
+ * some directories may be opened.
+ *-----------------------------------------------------------------------
+ */
+void
+Dir_InitDot(void)
+{
+ if (dot != NULL) {
+ LstNode ln;
+
+ /* Remove old entry from openDirectories, but do not destroy. */
+ ln = Lst_Member(openDirectories, dot);
+ (void)Lst_Remove(openDirectories, ln);
+ }
+
+ dot = Dir_AddDir(NULL, ".");
+
+ if (dot == NULL) {
+ Error("Cannot open `.' (%s)", strerror(errno));
+ exit(1);
+ }
+
+ /*
+ * We always need to have dot around, so we increment its reference count
+ * to make sure it's not destroyed.
+ */
+ dot->refCount += 1;
+ Dir_SetPATH(); /* initialize */
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * Dir_End --
+ * cleanup things for this module
+ *
+ * Results:
+ * none
+ *
+ * Side Effects:
+ * none
+ *-----------------------------------------------------------------------
+ */
+void
+Dir_End(void)
+{
+#ifdef CLEANUP
+ if (cur) {
+ cur->refCount -= 1;
+ Dir_Destroy(cur);
+ }
+ dot->refCount -= 1;
+ dotLast->refCount -= 1;
+ Dir_Destroy(dotLast);
+ Dir_Destroy(dot);
+ Dir_ClearPath(dirSearchPath);
+ Lst_Destroy(dirSearchPath, NULL);
+ Dir_ClearPath(openDirectories);
+ Lst_Destroy(openDirectories, NULL);
+ Hash_DeleteTable(&mtimes);
+#endif
+}
+
+/*
+ * We want ${.PATH} to indicate the order in which we will actually
+ * search, so we rebuild it after any .PATH: target.
+ * This is the simplest way to deal with the effect of .DOTLAST.
+ */
+void
+Dir_SetPATH(void)
+{
+ LstNode ln; /* a list element */
+ Path *p;
+ Boolean hasLastDot = FALSE; /* true we should search dot last */
+
+ Var_Delete(".PATH", VAR_GLOBAL);
+
+ if (Lst_Open(dirSearchPath) == SUCCESS) {
+ if ((ln = Lst_First(dirSearchPath)) != NULL) {
+ p = (Path *)Lst_Datum(ln);
+ if (p == dotLast) {
+ hasLastDot = TRUE;
+ Var_Append(".PATH", dotLast->name, VAR_GLOBAL);
+ }
+ }
+
+ if (!hasLastDot) {
+ if (dot)
+ Var_Append(".PATH", dot->name, VAR_GLOBAL);
+ if (cur)
+ Var_Append(".PATH", cur->name, VAR_GLOBAL);
+ }
+
+ while ((ln = Lst_Next(dirSearchPath)) != NULL) {
+ p = (Path *)Lst_Datum(ln);
+ if (p == dotLast)
+ continue;
+ if (p == dot && hasLastDot)
+ continue;
+ Var_Append(".PATH", p->name, VAR_GLOBAL);
+ }
+
+ if (hasLastDot) {
+ if (dot)
+ Var_Append(".PATH", dot->name, VAR_GLOBAL);
+ if (cur)
+ Var_Append(".PATH", cur->name, VAR_GLOBAL);
+ }
+ Lst_Close(dirSearchPath);
+ }
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * DirFindName --
+ * See if the Path structure describes the same directory as the
+ * given one by comparing their names. Called from Dir_AddDir via
+ * Lst_Find when searching the list of open directories.
+ *
+ * Input:
+ * p Current name
+ * dname Desired name
+ *
+ * Results:
+ * 0 if it is the same. Non-zero otherwise
+ *
+ * Side Effects:
+ * None
+ *-----------------------------------------------------------------------
+ */
+static int
+DirFindName(const void *p, const void *dname)
+{
+ return (strcmp(((const Path *)p)->name, dname));
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * Dir_HasWildcards --
+ * see if the given name has any wildcard characters in it
+ * be careful not to expand unmatching brackets or braces.
+ * XXX: This code is not 100% correct. ([^]] fails etc.)
+ * I really don't think that make(1) should be expanding
+ * patterns, because then you have to set a mechanism for
+ * escaping the expansion!
+ *
+ * Input:
+ * name name to check
+ *
+ * Results:
+ * returns TRUE if the word should be expanded, FALSE otherwise
+ *
+ * Side Effects:
+ * none
+ *-----------------------------------------------------------------------
+ */
+Boolean
+Dir_HasWildcards(char *name)
+{
+ char *cp;
+ int wild = 0, brace = 0, bracket = 0;
+
+ for (cp = name; *cp; cp++) {
+ switch(*cp) {
+ case '{':
+ brace++;
+ wild = 1;
+ break;
+ case '}':
+ brace--;
+ break;
+ case '[':
+ bracket++;
+ wild = 1;
+ break;
+ case ']':
+ bracket--;
+ break;
+ case '?':
+ case '*':
+ wild = 1;
+ break;
+ default:
+ break;
+ }
+ }
+ return wild && bracket == 0 && brace == 0;
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * DirMatchFiles --
+ * Given a pattern and a Path structure, see if any files
+ * match the pattern and add their names to the 'expansions' list if
+ * any do. This is incomplete -- it doesn't take care of patterns like
+ * src / *src / *.c properly (just *.c on any of the directories), but it
+ * will do for now.
+ *
+ * Input:
+ * pattern Pattern to look for
+ * p Directory to search
+ * expansion Place to store the results
+ *
+ * Results:
+ * Always returns 0
+ *
+ * Side Effects:
+ * File names are added to the expansions lst. The directory will be
+ * fully hashed when this is done.
+ *-----------------------------------------------------------------------
+ */
+static int
+DirMatchFiles(const char *pattern, Path *p, Lst expansions)
+{
+ Hash_Search search; /* Index into the directory's table */
+ Hash_Entry *entry; /* Current entry in the table */
+ Boolean isDot; /* TRUE if the directory being searched is . */
+
+ isDot = (*p->name == '.' && p->name[1] == '\0');
+
+ for (entry = Hash_EnumFirst(&p->files, &search);
+ entry != NULL;
+ entry = Hash_EnumNext(&search))
+ {
+ /*
+ * See if the file matches the given pattern. Note we follow the UNIX
+ * convention that dot files will only be found if the pattern
+ * begins with a dot (note also that as a side effect of the hashing
+ * scheme, .* won't match . or .. since they aren't hashed).
+ */
+ if (Str_Match(entry->name, pattern) &&
+ ((entry->name[0] != '.') ||
+ (pattern[0] == '.')))
+ {
+ (void)Lst_AtEnd(expansions,
+ (isDot ? bmake_strdup(entry->name) :
+ str_concat(p->name, entry->name,
+ STR_ADDSLASH)));
+ }
+ }
+ return (0);
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * DirExpandCurly --
+ * Expand curly braces like the C shell. Does this recursively.
+ * Note the special case: if after the piece of the curly brace is
+ * done there are no wildcard characters in the result, the result is
+ * placed on the list WITHOUT CHECKING FOR ITS EXISTENCE.
+ *
+ * Input:
+ * word Entire word to expand
+ * brace First curly brace in it
+ * path Search path to use
+ * expansions Place to store the expansions
+ *
+ * Results:
+ * None.
+ *
+ * Side Effects:
+ * The given list is filled with the expansions...
+ *
+ *-----------------------------------------------------------------------
+ */
+static void
+DirExpandCurly(const char *word, const char *brace, Lst path, Lst expansions)
+{
+ const char *end; /* Character after the closing brace */
+ const char *cp; /* Current position in brace clause */
+ const char *start; /* Start of current piece of brace clause */
+ int bracelevel; /* Number of braces we've seen. If we see a
+ * right brace when this is 0, we've hit the
+ * end of the clause. */
+ char *file; /* Current expansion */
+ int otherLen; /* The length of the other pieces of the
+ * expansion (chars before and after the
+ * clause in 'word') */
+ char *cp2; /* Pointer for checking for wildcards in
+ * expansion before calling Dir_Expand */
+
+ start = brace+1;
+
+ /*
+ * Find the end of the brace clause first, being wary of nested brace
+ * clauses.
+ */
+ for (end = start, bracelevel = 0; *end != '\0'; end++) {
+ if (*end == '{') {
+ bracelevel++;
+ } else if ((*end == '}') && (bracelevel-- == 0)) {
+ break;
+ }
+ }
+ if (*end == '\0') {
+ Error("Unterminated {} clause \"%s\"", start);
+ return;
+ } else {
+ end++;
+ }
+ otherLen = brace - word + strlen(end);
+
+ for (cp = start; cp < end; cp++) {
+ /*
+ * Find the end of this piece of the clause.
+ */
+ bracelevel = 0;
+ while (*cp != ',') {
+ if (*cp == '{') {
+ bracelevel++;
+ } else if ((*cp == '}') && (bracelevel-- <= 0)) {
+ break;
+ }
+ cp++;
+ }
+ /*
+ * Allocate room for the combination and install the three pieces.
+ */
+ file = bmake_malloc(otherLen + cp - start + 1);
+ if (brace != word) {
+ strncpy(file, word, brace-word);
+ }
+ if (cp != start) {
+ strncpy(&file[brace-word], start, cp-start);
+ }
+ strcpy(&file[(brace-word)+(cp-start)], end);
+
+ /*
+ * See if the result has any wildcards in it. If we find one, call
+ * Dir_Expand right away, telling it to place the result on our list
+ * of expansions.
+ */
+ for (cp2 = file; *cp2 != '\0'; cp2++) {
+ switch(*cp2) {
+ case '*':
+ case '?':
+ case '{':
+ case '[':
+ Dir_Expand(file, path, expansions);
+ goto next;
+ }
+ }
+ if (*cp2 == '\0') {
+ /*
+ * Hit the end w/o finding any wildcards, so stick the expansion
+ * on the end of the list.
+ */
+ (void)Lst_AtEnd(expansions, file);
+ } else {
+ next:
+ free(file);
+ }
+ start = cp+1;
+ }
+}
+
+
+/*-
+ *-----------------------------------------------------------------------
+ * DirExpandInt --
+ * Internal expand routine. Passes through the directories in the
+ * path one by one, calling DirMatchFiles for each. NOTE: This still
+ * doesn't handle patterns in directories...
+ *
+ * Input:
+ * word Word to expand
+ * path Path on which to look
+ * expansions Place to store the result
+ *
+ * Results:
+ * None.
+ *
+ * Side Effects:
+ * Things are added to the expansions list.
+ *
+ *-----------------------------------------------------------------------
+ */
+static void
+DirExpandInt(const char *word, Lst path, Lst expansions)
+{
+ LstNode ln; /* Current node */
+ Path *p; /* Directory in the node */
+
+ if (Lst_Open(path) == SUCCESS) {
+ while ((ln = Lst_Next(path)) != NULL) {
+ p = (Path *)Lst_Datum(ln);
+ DirMatchFiles(word, p, expansions);
+ }
+ Lst_Close(path);
+ }
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * DirPrintWord --
+ * Print a word in the list of expansions. Callback for Dir_Expand
+ * when DEBUG(DIR), via Lst_ForEach.
+ *
+ * Results:
+ * === 0
+ *
+ * Side Effects:
+ * The passed word is printed, followed by a space.
+ *
+ *-----------------------------------------------------------------------
+ */
+static int
+DirPrintWord(void *word, void *dummy MAKE_ATTR_UNUSED)
+{
+ fprintf(debug_file, "%s ", (char *)word);
+
+ return 0;
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * Dir_Expand --
+ * Expand the given word into a list of words by globbing it looking
+ * in the directories on the given search path.
+ *
+ * Input:
+ * word the word to expand
+ * path the list of directories in which to find the
+ * resulting files
+ * expansions the list on which to place the results
+ *
+ * Results:
+ * A list of words consisting of the files which exist along the search
+ * path matching the given pattern.
+ *
+ * Side Effects:
+ * Directories may be opened. Who knows?
+ *-----------------------------------------------------------------------
+ */
+void
+Dir_Expand(const char *word, Lst path, Lst expansions)
+{
+ const char *cp;
+
+ if (DEBUG(DIR)) {
+ fprintf(debug_file, "Expanding \"%s\"... ", word);
+ }
+
+ cp = strchr(word, '{');
+ if (cp) {
+ DirExpandCurly(word, cp, path, expansions);
+ } else {
+ cp = strchr(word, '/');
+ if (cp) {
+ /*
+ * The thing has a directory component -- find the first wildcard
+ * in the string.
+ */
+ for (cp = word; *cp; cp++) {
+ if (*cp == '?' || *cp == '[' || *cp == '*' || *cp == '{') {
+ break;
+ }
+ }
+ if (*cp == '{') {
+ /*
+ * This one will be fun.
+ */
+ DirExpandCurly(word, cp, path, expansions);
+ return;
+ } else if (*cp != '\0') {
+ /*
+ * Back up to the start of the component
+ */
+ char *dirpath;
+
+ while (cp > word && *cp != '/') {
+ cp--;
+ }
+ if (cp != word) {
+ char sc;
+ /*
+ * If the glob isn't in the first component, try and find
+ * all the components up to the one with a wildcard.
+ */
+ sc = cp[1];
+ ((char *)UNCONST(cp))[1] = '\0';
+ dirpath = Dir_FindFile(word, path);
+ ((char *)UNCONST(cp))[1] = sc;
+ /*
+ * dirpath is null if can't find the leading component
+ * XXX: Dir_FindFile won't find internal components.
+ * i.e. if the path contains ../Etc/Object and we're
+ * looking for Etc, it won't be found. Ah well.
+ * Probably not important.
+ */
+ if (dirpath != NULL) {
+ char *dp = &dirpath[strlen(dirpath) - 1];
+ if (*dp == '/')
+ *dp = '\0';
+ path = Lst_Init(FALSE);
+ (void)Dir_AddDir(path, dirpath);
+ DirExpandInt(cp+1, path, expansions);
+ Lst_Destroy(path, NULL);
+ }
+ } else {
+ /*
+ * Start the search from the local directory
+ */
+ DirExpandInt(word, path, expansions);
+ }
+ } else {
+ /*
+ * Return the file -- this should never happen.
+ */
+ DirExpandInt(word, path, expansions);
+ }
+ } else {
+ /*
+ * First the files in dot
+ */
+ DirMatchFiles(word, dot, expansions);
+
+ /*
+ * Then the files in every other directory on the path.
+ */
+ DirExpandInt(word, path, expansions);
+ }
+ }
+ if (DEBUG(DIR)) {
+ Lst_ForEach(expansions, DirPrintWord, NULL);
+ fprintf(debug_file, "\n");
+ }
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * DirLookup --
+ * Find if the file with the given name exists in the given path.
+ *
+ * Results:
+ * The path to the file or NULL. This path is guaranteed to be in a
+ * different part of memory than name and so may be safely free'd.
+ *
+ * Side Effects:
+ * None.
+ *-----------------------------------------------------------------------
+ */
+static char *
+DirLookup(Path *p, const char *name MAKE_ATTR_UNUSED, const char *cp,
+ Boolean hasSlash MAKE_ATTR_UNUSED)
+{
+ char *file; /* the current filename to check */
+
+ if (DEBUG(DIR)) {
+ fprintf(debug_file, " %s ...\n", p->name);
+ }
+
+ if (Hash_FindEntry(&p->files, cp) == NULL)
+ return NULL;
+
+ file = str_concat(p->name, cp, STR_ADDSLASH);
+ if (DEBUG(DIR)) {
+ fprintf(debug_file, " returning %s\n", file);
+ }
+ p->hits += 1;
+ hits += 1;
+ return file;
+}
+
+
+/*-
+ *-----------------------------------------------------------------------
+ * DirLookupSubdir --
+ * Find if the file with the given name exists in the given path.
+ *
+ * Results:
+ * The path to the file or NULL. This path is guaranteed to be in a
+ * different part of memory than name and so may be safely free'd.
+ *
+ * Side Effects:
+ * If the file is found, it is added in the modification times hash
+ * table.
+ *-----------------------------------------------------------------------
+ */
+static char *
+DirLookupSubdir(Path *p, const char *name)
+{
+ struct stat stb; /* Buffer for stat, if necessary */
+ char *file; /* the current filename to check */
+
+ if (p != dot) {
+ file = str_concat(p->name, name, STR_ADDSLASH);
+ } else {
+ /*
+ * Checking in dot -- DON'T put a leading ./ on the thing.
+ */
+ file = bmake_strdup(name);
+ }
+
+ if (DEBUG(DIR)) {
+ fprintf(debug_file, "checking %s ...\n", file);
+ }
+
+ if (cached_stat(file, &stb) == 0) {
+ nearmisses += 1;
+ return (file);
+ }
+ free(file);
+ return NULL;
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * DirLookupAbs --
+ * Find if the file with the given name exists in the given path.
+ *
+ * Results:
+ * The path to the file, the empty string or NULL. If the file is
+ * the empty string, the search should be terminated.
+ * This path is guaranteed to be in a different part of memory
+ * than name and so may be safely free'd.
+ *
+ * Side Effects:
+ * None.
+ *-----------------------------------------------------------------------
+ */
+static char *
+DirLookupAbs(Path *p, const char *name, const char *cp)
+{
+ char *p1; /* pointer into p->name */
+ const char *p2; /* pointer into name */
+
+ if (DEBUG(DIR)) {
+ fprintf(debug_file, " %s ...\n", p->name);
+ }
+
+ /*
+ * If the file has a leading path component and that component
+ * exactly matches the entire name of the current search
+ * directory, we can attempt another cache lookup. And if we don't
+ * have a hit, we can safely assume the file does not exist at all.
+ */
+ for (p1 = p->name, p2 = name; *p1 && *p1 == *p2; p1++, p2++) {
+ continue;
+ }
+ if (*p1 != '\0' || p2 != cp - 1) {
+ return NULL;
+ }
+
+ if (Hash_FindEntry(&p->files, cp) == NULL) {
+ if (DEBUG(DIR)) {
+ fprintf(debug_file, " must be here but isn't -- returning\n");
+ }
+ /* Return empty string: terminates search */
+ return bmake_strdup("");
+ }
+
+ p->hits += 1;
+ hits += 1;
+ if (DEBUG(DIR)) {
+ fprintf(debug_file, " returning %s\n", name);
+ }
+ return (bmake_strdup(name));
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * DirFindDot --
+ * Find the file given on "." or curdir
+ *
+ * Results:
+ * The path to the file or NULL. This path is guaranteed to be in a
+ * different part of memory than name and so may be safely free'd.
+ *
+ * Side Effects:
+ * Hit counts change
+ *-----------------------------------------------------------------------
+ */
+static char *
+DirFindDot(Boolean hasSlash MAKE_ATTR_UNUSED, const char *name, const char *cp)
+{
+
+ if (Hash_FindEntry(&dot->files, cp) != NULL) {
+ if (DEBUG(DIR)) {
+ fprintf(debug_file, " in '.'\n");
+ }
+ hits += 1;
+ dot->hits += 1;
+ return (bmake_strdup(name));
+ }
+ if (cur &&
+ Hash_FindEntry(&cur->files, cp) != NULL) {
+ if (DEBUG(DIR)) {
+ fprintf(debug_file, " in ${.CURDIR} = %s\n", cur->name);
+ }
+ hits += 1;
+ cur->hits += 1;
+ return str_concat(cur->name, cp, STR_ADDSLASH);
+ }
+
+ return NULL;
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * Dir_FindFile --
+ * Find the file with the given name along the given search path.
+ *
+ * Input:
+ * name the file to find
+ * path the Lst of directories to search
+ *
+ * Results:
+ * The path to the file or NULL. This path is guaranteed to be in a
+ * different part of memory than name and so may be safely free'd.
+ *
+ * Side Effects:
+ * If the file is found in a directory which is not on the path
+ * already (either 'name' is absolute or it is a relative path
+ * [ dir1/.../dirn/file ] which exists below one of the directories
+ * already on the search path), its directory is added to the end
+ * of the path on the assumption that there will be more files in
+ * that directory later on. Sometimes this is true. Sometimes not.
+ *-----------------------------------------------------------------------
+ */
+char *
+Dir_FindFile(const char *name, Lst path)
+{
+ LstNode ln; /* a list element */
+ char *file; /* the current filename to check */
+ Path *p; /* current path member */
+ const char *cp; /* Terminal name of file */
+ Boolean hasLastDot = FALSE; /* true we should search dot last */
+ Boolean hasSlash; /* true if 'name' contains a / */
+ struct stat stb; /* Buffer for stat, if necessary */
+ const char *trailing_dot = ".";
+
+ /*
+ * Find the final component of the name and note whether it has a
+ * slash in it (the name, I mean)
+ */
+ cp = strrchr(name, '/');
+ if (cp) {
+ hasSlash = TRUE;
+ cp += 1;
+ } else {
+ hasSlash = FALSE;
+ cp = name;
+ }
+
+ if (DEBUG(DIR)) {
+ fprintf(debug_file, "Searching for %s ...", name);
+ }
+
+ if (Lst_Open(path) == FAILURE) {
+ if (DEBUG(DIR)) {
+ fprintf(debug_file, "couldn't open path, file not found\n");
+ }
+ misses += 1;
+ return NULL;
+ }
+
+ if ((ln = Lst_First(path)) != NULL) {
+ p = (Path *)Lst_Datum(ln);
+ if (p == dotLast) {
+ hasLastDot = TRUE;
+ if (DEBUG(DIR))
+ fprintf(debug_file, "[dot last]...");
+ }
+ }
+ if (DEBUG(DIR)) {
+ fprintf(debug_file, "\n");
+ }
+
+ /*
+ * If there's no leading directory components or if the leading
+ * directory component is exactly `./', consult the cached contents
+ * of each of the directories on the search path.
+ */
+ if (!hasSlash || (cp - name == 2 && *name == '.')) {
+ /*
+ * We look through all the directories on the path seeking one which
+ * contains the final component of the given name. If such a beast
+ * is found, we concatenate the directory name and the final
+ * component and return the resulting string. If we don't find any
+ * such thing, we go on to phase two...
+ *
+ * No matter what, we always look for the file in the current
+ * directory before anywhere else (unless we found the magic
+ * DOTLAST path, in which case we search it last) and we *do not*
+ * add the ./ to it if it exists.
+ * This is so there are no conflicts between what the user
+ * specifies (fish.c) and what pmake finds (./fish.c).
+ */
+ if (!hasLastDot &&
+ (file = DirFindDot(hasSlash, name, cp)) != NULL) {
+ Lst_Close(path);
+ return file;
+ }
+
+ while ((ln = Lst_Next(path)) != NULL) {
+ p = (Path *)Lst_Datum(ln);
+ if (p == dotLast)
+ continue;
+ if ((file = DirLookup(p, name, cp, hasSlash)) != NULL) {
+ Lst_Close(path);
+ return file;
+ }
+ }
+
+ if (hasLastDot &&
+ (file = DirFindDot(hasSlash, name, cp)) != NULL) {
+ Lst_Close(path);
+ return file;
+ }
+ }
+ Lst_Close(path);
+
+ /*
+ * We didn't find the file on any directory in the search path.
+ * If the name doesn't contain a slash, that means it doesn't exist.
+ * If it *does* contain a slash, however, there is still hope: it
+ * could be in a subdirectory of one of the members of the search
+ * path. (eg. /usr/include and sys/types.h. The above search would
+ * fail to turn up types.h in /usr/include, but it *is* in
+ * /usr/include/sys/types.h).
+ * [ This no longer applies: If we find such a beast, we assume there
+ * will be more (what else can we assume?) and add all but the last
+ * component of the resulting name onto the search path (at the
+ * end).]
+ * This phase is only performed if the file is *not* absolute.
+ */
+ if (!hasSlash) {
+ if (DEBUG(DIR)) {
+ fprintf(debug_file, " failed.\n");
+ }
+ misses += 1;
+ return NULL;
+ }
+
+ if (*cp == '\0') {
+ /* we were given a trailing "/" */
+ cp = trailing_dot;
+ }
+
+ if (name[0] != '/') {
+ Boolean checkedDot = FALSE;
+
+ if (DEBUG(DIR)) {
+ fprintf(debug_file, " Trying subdirectories...\n");
+ }
+
+ if (!hasLastDot) {
+ if (dot) {
+ checkedDot = TRUE;
+ if ((file = DirLookupSubdir(dot, name)) != NULL)
+ return file;
+ }
+ if (cur && (file = DirLookupSubdir(cur, name)) != NULL)
+ return file;
+ }
+
+ (void)Lst_Open(path);
+ while ((ln = Lst_Next(path)) != NULL) {
+ p = (Path *)Lst_Datum(ln);
+ if (p == dotLast)
+ continue;
+ if (p == dot) {
+ if (checkedDot)
+ continue;
+ checkedDot = TRUE;
+ }
+ if ((file = DirLookupSubdir(p, name)) != NULL) {
+ Lst_Close(path);
+ return file;
+ }
+ }
+ Lst_Close(path);
+
+ if (hasLastDot) {
+ if (dot && !checkedDot) {
+ checkedDot = TRUE;
+ if ((file = DirLookupSubdir(dot, name)) != NULL)
+ return file;
+ }
+ if (cur && (file = DirLookupSubdir(cur, name)) != NULL)
+ return file;
+ }
+
+ if (checkedDot) {
+ /*
+ * Already checked by the given name, since . was in the path,
+ * so no point in proceeding...
+ */
+ if (DEBUG(DIR)) {
+ fprintf(debug_file, " Checked . already, returning NULL\n");
+ }
+ return NULL;
+ }
+
+ } else { /* name[0] == '/' */
+
+ /*
+ * For absolute names, compare directory path prefix against the
+ * the directory path of each member on the search path for an exact
+ * match. If we have an exact match on any member of the search path,
+ * use the cached contents of that member to lookup the final file
+ * component. If that lookup fails we can safely assume that the
+ * file does not exist at all. This is signified by DirLookupAbs()
+ * returning an empty string.
+ */
+ if (DEBUG(DIR)) {
+ fprintf(debug_file, " Trying exact path matches...\n");
+ }
+
+ if (!hasLastDot && cur && ((file = DirLookupAbs(cur, name, cp))
+ != NULL)) {
+ if (file[0] == '\0') {
+ free(file);
+ return NULL;
+ }
+ return file;
+ }
+
+ (void)Lst_Open(path);
+ while ((ln = Lst_Next(path)) != NULL) {
+ p = (Path *)Lst_Datum(ln);
+ if (p == dotLast)
+ continue;
+ if ((file = DirLookupAbs(p, name, cp)) != NULL) {
+ Lst_Close(path);
+ if (file[0] == '\0') {
+ free(file);
+ return NULL;
+ }
+ return file;
+ }
+ }
+ Lst_Close(path);
+
+ if (hasLastDot && cur && ((file = DirLookupAbs(cur, name, cp))
+ != NULL)) {
+ if (file[0] == '\0') {
+ free(file);
+ return NULL;
+ }
+ return file;
+ }
+ }
+
+ /*
+ * Didn't find it that way, either. Sigh. Phase 3. Add its directory
+ * onto the search path in any case, just in case, then look for the
+ * thing in the hash table. If we find it, grand. We return a new
+ * copy of the name. Otherwise we sadly return a NULL pointer. Sigh.
+ * Note that if the directory holding the file doesn't exist, this will
+ * do an extra search of the final directory on the path. Unless something
+ * weird happens, this search won't succeed and life will be groovy.
+ *
+ * Sigh. We cannot add the directory onto the search path because
+ * of this amusing case:
+ * $(INSTALLDIR)/$(FILE): $(FILE)
+ *
+ * $(FILE) exists in $(INSTALLDIR) but not in the current one.
+ * When searching for $(FILE), we will find it in $(INSTALLDIR)
+ * b/c we added it here. This is not good...
+ */
+#ifdef notdef
+ if (cp == traling_dot) {
+ cp = strrchr(name, '/');
+ cp += 1;
+ }
+ cp[-1] = '\0';
+ (void)Dir_AddDir(path, name);
+ cp[-1] = '/';
+
+ bigmisses += 1;
+ ln = Lst_Last(path);
+ if (ln == NULL) {
+ return NULL;
+ } else {
+ p = (Path *)Lst_Datum(ln);
+ }
+
+ if (Hash_FindEntry(&p->files, cp) != NULL) {
+ return (bmake_strdup(name));
+ } else {
+ return NULL;
+ }
+#else /* !notdef */
+ if (DEBUG(DIR)) {
+ fprintf(debug_file, " Looking for \"%s\" ...\n", name);
+ }
+
+ bigmisses += 1;
+ if (cached_stat(name, &stb) == 0) {
+ return (bmake_strdup(name));
+ }
+
+ if (DEBUG(DIR)) {
+ fprintf(debug_file, " failed. Returning NULL\n");
+ }
+ return NULL;
+#endif /* notdef */
+}
+
+
+/*-
+ *-----------------------------------------------------------------------
+ * Dir_FindHereOrAbove --
+ * search for a path starting at a given directory and then working
+ * our way up towards the root.
+ *
+ * Input:
+ * here starting directory
+ * search_path the path we are looking for
+ * result the result of a successful search is placed here
+ * rlen the length of the result buffer
+ * (typically MAXPATHLEN + 1)
+ *
+ * Results:
+ * 0 on failure, 1 on success [in which case the found path is put
+ * in the result buffer].
+ *
+ * Side Effects:
+ *-----------------------------------------------------------------------
+ */
+int
+Dir_FindHereOrAbove(char *here, char *search_path, char *result, int rlen) {
+
+ struct stat st;
+ char dirbase[MAXPATHLEN + 1], *db_end;
+ char try[MAXPATHLEN + 1], *try_end;
+
+ /* copy out our starting point */
+ snprintf(dirbase, sizeof(dirbase), "%s", here);
+ db_end = dirbase + strlen(dirbase);
+
+ /* loop until we determine a result */
+ while (1) {
+
+ /* try and stat(2) it ... */
+ snprintf(try, sizeof(try), "%s/%s", dirbase, search_path);
+ if (cached_stat(try, &st) != -1) {
+ /*
+ * success! if we found a file, chop off
+ * the filename so we return a directory.
+ */
+ if ((st.st_mode & S_IFMT) != S_IFDIR) {
+ try_end = try + strlen(try);
+ while (try_end > try && *try_end != '/')
+ try_end--;
+ if (try_end > try)
+ *try_end = 0; /* chop! */
+ }
+
+ /*
+ * done!
+ */
+ snprintf(result, rlen, "%s", try);
+ return(1);
+ }
+
+ /*
+ * nope, we didn't find it. if we used up dirbase we've
+ * reached the root and failed.
+ */
+ if (db_end == dirbase)
+ break; /* failed! */
+
+ /*
+ * truncate dirbase from the end to move up a dir
+ */
+ while (db_end > dirbase && *db_end != '/')
+ db_end--;
+ *db_end = 0; /* chop! */
+
+ } /* while (1) */
+
+ /*
+ * we failed...
+ */
+ return(0);
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * Dir_MTime --
+ * Find the modification time of the file described by gn along the
+ * search path dirSearchPath.
+ *
+ * Input:
+ * gn the file whose modification time is desired
+ *
+ * Results:
+ * The modification time or 0 if it doesn't exist
+ *
+ * Side Effects:
+ * The modification time is placed in the node's mtime slot.
+ * If the node didn't have a path entry before, and Dir_FindFile
+ * found one for it, the full name is placed in the path slot.
+ *-----------------------------------------------------------------------
+ */
+int
+Dir_MTime(GNode *gn, Boolean recheck)
+{
+ char *fullName; /* the full pathname of name */
+ struct stat stb; /* buffer for finding the mod time */
+
+ if (gn->type & OP_ARCHV) {
+ return Arch_MTime(gn);
+ } else if (gn->type & OP_PHONY) {
+ gn->mtime = 0;
+ return 0;
+ } else if (gn->path == NULL) {
+ if (gn->type & OP_NOPATH)
+ fullName = NULL;
+ else {
+ fullName = Dir_FindFile(gn->name, Suff_FindPath(gn));
+ if (fullName == NULL && gn->flags & FROM_DEPEND &&
+ !Lst_IsEmpty(gn->iParents)) {
+ char *cp;
+
+ cp = strrchr(gn->name, '/');
+ if (cp) {
+ /*
+ * This is an implied source, and it may have moved,
+ * see if we can find it via the current .PATH
+ */
+ cp++;
+
+ fullName = Dir_FindFile(cp, Suff_FindPath(gn));
+ if (fullName) {
+ /*
+ * Put the found file in gn->path
+ * so that we give that to the compiler.
+ */
+ gn->path = bmake_strdup(fullName);
+ if (!Job_RunTarget(".STALE", gn->fname))
+ fprintf(stdout,
+ "%s: %s, %d: ignoring stale %s for %s, "
+ "found %s\n", progname, gn->fname, gn->lineno,
+ makeDependfile, gn->name, fullName);
+ }
+ }
+ }
+ if (DEBUG(DIR))
+ fprintf(debug_file, "Found '%s' as '%s'\n",
+ gn->name, fullName ? fullName : "(not found)" );
+ }
+ } else {
+ fullName = gn->path;
+ }
+
+ if (fullName == NULL) {
+ fullName = bmake_strdup(gn->name);
+ }
+
+ if (cached_stats(&mtimes, fullName, &stb, recheck ? CST_UPDATE : 0) < 0) {
+ if (gn->type & OP_MEMBER) {
+ if (fullName != gn->path)
+ free(fullName);
+ return Arch_MemMTime(gn);
+ } else {
+ stb.st_mtime = 0;
+ }
+ }
+
+ if (fullName && gn->path == NULL) {
+ gn->path = fullName;
+ }
+
+ gn->mtime = stb.st_mtime;
+ return (gn->mtime);
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * Dir_AddDir --
+ * Add the given name to the end of the given path. The order of
+ * the arguments is backwards so ParseDoDependency can do a
+ * Lst_ForEach of its list of paths...
+ *
+ * Input:
+ * path the path to which the directory should be
+ * added
+ * name the name of the directory to add
+ *
+ * Results:
+ * none
+ *
+ * Side Effects:
+ * A structure is added to the list and the directory is
+ * read and hashed.
+ *-----------------------------------------------------------------------
+ */
+Path *
+Dir_AddDir(Lst path, const char *name)
+{
+ LstNode ln = NULL; /* node in case Path structure is found */
+ Path *p = NULL; /* pointer to new Path structure */
+ DIR *d; /* for reading directory */
+ struct dirent *dp; /* entry in directory */
+
+ if (strcmp(name, ".DOTLAST") == 0) {
+ ln = Lst_Find(path, name, DirFindName);
+ if (ln != NULL)
+ return (Path *)Lst_Datum(ln);
+ else {
+ dotLast->refCount += 1;
+ (void)Lst_AtFront(path, dotLast);
+ }
+ }
+
+ if (path)
+ ln = Lst_Find(openDirectories, name, DirFindName);
+ if (ln != NULL) {
+ p = (Path *)Lst_Datum(ln);
+ if (path && Lst_Member(path, p) == NULL) {
+ p->refCount += 1;
+ (void)Lst_AtEnd(path, p);
+ }
+ } else {
+ if (DEBUG(DIR)) {
+ fprintf(debug_file, "Caching %s ...", name);
+ }
+
+ if ((d = opendir(name)) != NULL) {
+ p = bmake_malloc(sizeof(Path));
+ p->name = bmake_strdup(name);
+ p->hits = 0;
+ p->refCount = 1;
+ Hash_InitTable(&p->files, -1);
+
+ while ((dp = readdir(d)) != NULL) {
+#if defined(sun) && defined(d_ino) /* d_ino is a sunos4 #define for d_fileno */
+ /*
+ * The sun directory library doesn't check for a 0 inode
+ * (0-inode slots just take up space), so we have to do
+ * it ourselves.
+ */
+ if (dp->d_fileno == 0) {
+ continue;
+ }
+#endif /* sun && d_ino */
+ (void)Hash_CreateEntry(&p->files, dp->d_name, NULL);
+ }
+ (void)closedir(d);
+ (void)Lst_AtEnd(openDirectories, p);
+ if (path != NULL)
+ (void)Lst_AtEnd(path, p);
+ }
+ if (DEBUG(DIR)) {
+ fprintf(debug_file, "done\n");
+ }
+ }
+ return p;
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * Dir_CopyDir --
+ * Callback function for duplicating a search path via Lst_Duplicate.
+ * Ups the reference count for the directory.
+ *
+ * Results:
+ * Returns the Path it was given.
+ *
+ * Side Effects:
+ * The refCount of the path is incremented.
+ *
+ *-----------------------------------------------------------------------
+ */
+void *
+Dir_CopyDir(void *p)
+{
+ ((Path *)p)->refCount += 1;
+
+ return (p);
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * Dir_MakeFlags --
+ * Make a string by taking all the directories in the given search
+ * path and preceding them by the given flag. Used by the suffix
+ * module to create variables for compilers based on suffix search
+ * paths.
+ *
+ * Input:
+ * flag flag which should precede each directory
+ * path list of directories
+ *
+ * Results:
+ * The string mentioned above. Note that there is no space between
+ * the given flag and each directory. The empty string is returned if
+ * Things don't go well.
+ *
+ * Side Effects:
+ * None
+ *-----------------------------------------------------------------------
+ */
+char *
+Dir_MakeFlags(const char *flag, Lst path)
+{
+ char *str; /* the string which will be returned */
+ char *s1, *s2;/* the current directory preceded by 'flag' */
+ LstNode ln; /* the node of the current directory */
+ Path *p; /* the structure describing the current directory */
+
+ str = bmake_strdup("");
+
+ if (Lst_Open(path) == SUCCESS) {
+ while ((ln = Lst_Next(path)) != NULL) {
+ p = (Path *)Lst_Datum(ln);
+ s2 = str_concat(flag, p->name, 0);
+ str = str_concat(s1 = str, s2, STR_ADDSPACE);
+ free(s1);
+ free(s2);
+ }
+ Lst_Close(path);
+ }
+
+ return (str);
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * Dir_Destroy --
+ * Nuke a directory descriptor, if possible. Callback procedure
+ * for the suffixes module when destroying a search path.
+ *
+ * Input:
+ * pp The directory descriptor to nuke
+ *
+ * Results:
+ * None.
+ *
+ * Side Effects:
+ * If no other path references this directory (refCount == 0),
+ * the Path and all its data are freed.
+ *
+ *-----------------------------------------------------------------------
+ */
+void
+Dir_Destroy(void *pp)
+{
+ Path *p = (Path *)pp;
+ p->refCount -= 1;
+
+ if (p->refCount == 0) {
+ LstNode ln;
+
+ ln = Lst_Member(openDirectories, p);
+ (void)Lst_Remove(openDirectories, ln);
+
+ Hash_DeleteTable(&p->files);
+ free(p->name);
+ free(p);
+ }
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * Dir_ClearPath --
+ * Clear out all elements of the given search path. This is different
+ * from destroying the list, notice.
+ *
+ * Input:
+ * path Path to clear
+ *
+ * Results:
+ * None.
+ *
+ * Side Effects:
+ * The path is set to the empty list.
+ *
+ *-----------------------------------------------------------------------
+ */
+void
+Dir_ClearPath(Lst path)
+{
+ Path *p;
+ while (!Lst_IsEmpty(path)) {
+ p = (Path *)Lst_DeQueue(path);
+ Dir_Destroy(p);
+ }
+}
+
+
+/*-
+ *-----------------------------------------------------------------------
+ * Dir_Concat --
+ * Concatenate two paths, adding the second to the end of the first.
+ * Makes sure to avoid duplicates.
+ *
+ * Input:
+ * path1 Dest
+ * path2 Source
+ *
+ * Results:
+ * None
+ *
+ * Side Effects:
+ * Reference counts for added dirs are upped.
+ *
+ *-----------------------------------------------------------------------
+ */
+void
+Dir_Concat(Lst path1, Lst path2)
+{
+ LstNode ln;
+ Path *p;
+
+ for (ln = Lst_First(path2); ln != NULL; ln = Lst_Succ(ln)) {
+ p = (Path *)Lst_Datum(ln);
+ if (Lst_Member(path1, p) == NULL) {
+ p->refCount += 1;
+ (void)Lst_AtEnd(path1, p);
+ }
+ }
+}
+
+/********** DEBUG INFO **********/
+void
+Dir_PrintDirectories(void)
+{
+ LstNode ln;
+ Path *p;
+
+ fprintf(debug_file, "#*** Directory Cache:\n");
+ fprintf(debug_file, "# Stats: %d hits %d misses %d near misses %d losers (%d%%)\n",
+ hits, misses, nearmisses, bigmisses,
+ (hits+bigmisses+nearmisses ?
+ hits * 100 / (hits + bigmisses + nearmisses) : 0));
+ fprintf(debug_file, "# %-20s referenced\thits\n", "directory");
+ if (Lst_Open(openDirectories) == SUCCESS) {
+ while ((ln = Lst_Next(openDirectories)) != NULL) {
+ p = (Path *)Lst_Datum(ln);
+ fprintf(debug_file, "# %-20s %10d\t%4d\n", p->name, p->refCount, p->hits);
+ }
+ Lst_Close(openDirectories);
+ }
+}
+
+static int
+DirPrintDir(void *p, void *dummy MAKE_ATTR_UNUSED)
+{
+ fprintf(debug_file, "%s ", ((Path *)p)->name);
+ return 0;
+}
+
+void
+Dir_PrintPath(Lst path)
+{
+ Lst_ForEach(path, DirPrintDir, NULL);
+}
diff --git a/usr.bin/make/dir.h b/usr.bin/make/dir.h
new file mode 100644
index 0000000..52ab35e
--- /dev/null
+++ b/usr.bin/make/dir.h
@@ -0,0 +1,108 @@
+/* $NetBSD: dir.h,v 1.18 2017/05/31 22:02:06 maya Exp $ */
+
+/*
+ * Copyright (c) 1988, 1989, 1990 The Regents of the University of California.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Adam de Boor.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * from: @(#)dir.h 8.1 (Berkeley) 6/6/93
+ */
+
+/*
+ * Copyright (c) 1988, 1989 by Adam de Boor
+ * Copyright (c) 1989 by Berkeley Softworks
+ * All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Adam de Boor.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. All advertising materials mentioning features or use of this software
+ * must display the following acknowledgement:
+ * This product includes software developed by the University of
+ * California, Berkeley and its contributors.
+ * 4. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * from: @(#)dir.h 8.1 (Berkeley) 6/6/93
+ */
+
+/* dir.h --
+ */
+
+#ifndef MAKE_DIR_H
+#define MAKE_DIR_H
+
+typedef struct Path {
+ char *name; /* Name of directory */
+ int refCount; /* Number of paths with this directory */
+ int hits; /* the number of times a file in this
+ * directory has been found */
+ Hash_Table files; /* Hash table of files in directory */
+} Path;
+
+void Dir_Init(const char *);
+void Dir_InitCur(const char *);
+void Dir_InitDot(void);
+void Dir_End(void);
+void Dir_SetPATH(void);
+Boolean Dir_HasWildcards(char *);
+void Dir_Expand(const char *, Lst, Lst);
+char *Dir_FindFile(const char *, Lst);
+int Dir_FindHereOrAbove(char *, char *, char *, int);
+int Dir_MTime(GNode *, Boolean);
+Path *Dir_AddDir(Lst, const char *);
+char *Dir_MakeFlags(const char *, Lst);
+void Dir_ClearPath(Lst);
+void Dir_Concat(Lst, Lst);
+void Dir_PrintDirectories(void);
+void Dir_PrintPath(Lst);
+void Dir_Destroy(void *);
+void * Dir_CopyDir(void *);
+
+#endif /* MAKE_DIR_H */
diff --git a/usr.bin/make/for.c b/usr.bin/make/for.c
new file mode 100644
index 0000000..fffedda
--- /dev/null
+++ b/usr.bin/make/for.c
@@ -0,0 +1,496 @@
+/* $NetBSD: for.c,v 1.53 2017/04/16 21:04:44 riastradh Exp $ */
+
+/*
+ * Copyright (c) 1992, The Regents of the University of California.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#ifndef MAKE_NATIVE
+static char rcsid[] = "$NetBSD: for.c,v 1.53 2017/04/16 21:04:44 riastradh Exp $";
+#else
+#include <sys/cdefs.h>
+#ifndef lint
+#if 0
+static char sccsid[] = "@(#)for.c 8.1 (Berkeley) 6/6/93";
+#else
+__RCSID("$NetBSD: for.c,v 1.53 2017/04/16 21:04:44 riastradh Exp $");
+#endif
+#endif /* not lint */
+#endif
+
+/*-
+ * for.c --
+ * Functions to handle loops in a makefile.
+ *
+ * Interface:
+ * For_Eval Evaluate the loop in the passed line.
+ * For_Run Run accumulated loop
+ *
+ */
+
+#include <assert.h>
+#include <ctype.h>
+
+#include "make.h"
+#include "hash.h"
+#include "dir.h"
+#include "buf.h"
+#include "strlist.h"
+
+#define FOR_SUB_ESCAPE_CHAR 1
+#define FOR_SUB_ESCAPE_BRACE 2
+#define FOR_SUB_ESCAPE_PAREN 4
+
+/*
+ * For statements are of the form:
+ *
+ * .for <variable> in <varlist>
+ * ...
+ * .endfor
+ *
+ * The trick is to look for the matching end inside for for loop
+ * To do that, we count the current nesting level of the for loops.
+ * and the .endfor statements, accumulating all the statements between
+ * the initial .for loop and the matching .endfor;
+ * then we evaluate the for loop for each variable in the varlist.
+ *
+ * Note that any nested fors are just passed through; they get handled
+ * recursively in For_Eval when we're expanding the enclosing for in
+ * For_Run.
+ */
+
+static int forLevel = 0; /* Nesting level */
+
+/*
+ * State of a for loop.
+ */
+typedef struct _For {
+ Buffer buf; /* Body of loop */
+ strlist_t vars; /* Iteration variables */
+ strlist_t items; /* Substitution items */
+ char *parse_buf;
+ int short_var;
+ int sub_next;
+} For;
+
+static For *accumFor; /* Loop being accumulated */
+
+
+
+static char *
+make_str(const char *ptr, int len)
+{
+ char *new_ptr;
+
+ new_ptr = bmake_malloc(len + 1);
+ memcpy(new_ptr, ptr, len);
+ new_ptr[len] = 0;
+ return new_ptr;
+}
+
+static void
+For_Free(For *arg)
+{
+ Buf_Destroy(&arg->buf, TRUE);
+ strlist_clean(&arg->vars);
+ strlist_clean(&arg->items);
+ free(arg->parse_buf);
+
+ free(arg);
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * For_Eval --
+ * Evaluate the for loop in the passed line. The line
+ * looks like this:
+ * .for <variable> in <varlist>
+ *
+ * Input:
+ * line Line to parse
+ *
+ * Results:
+ * 0: Not a .for statement, parse the line
+ * 1: We found a for loop
+ * -1: A .for statement with a bad syntax error, discard.
+ *
+ * Side Effects:
+ * None.
+ *
+ *-----------------------------------------------------------------------
+ */
+int
+For_Eval(char *line)
+{
+ For *new_for;
+ char *ptr = line, *sub;
+ int len;
+ int escapes;
+ unsigned char ch;
+ char **words, *word_buf;
+ int n, nwords;
+
+ /* Skip the '.' and any following whitespace */
+ for (ptr++; *ptr && isspace((unsigned char) *ptr); ptr++)
+ continue;
+
+ /*
+ * If we are not in a for loop quickly determine if the statement is
+ * a for.
+ */
+ if (ptr[0] != 'f' || ptr[1] != 'o' || ptr[2] != 'r' ||
+ !isspace((unsigned char) ptr[3])) {
+ if (ptr[0] == 'e' && strncmp(ptr+1, "ndfor", 5) == 0) {
+ Parse_Error(PARSE_FATAL, "for-less endfor");
+ return -1;
+ }
+ return 0;
+ }
+ ptr += 3;
+
+ /*
+ * we found a for loop, and now we are going to parse it.
+ */
+
+ new_for = bmake_malloc(sizeof *new_for);
+ memset(new_for, 0, sizeof *new_for);
+
+ /* Grab the variables. Terminate on "in". */
+ for (;; ptr += len) {
+ while (*ptr && isspace((unsigned char) *ptr))
+ ptr++;
+ if (*ptr == '\0') {
+ Parse_Error(PARSE_FATAL, "missing `in' in for");
+ For_Free(new_for);
+ return -1;
+ }
+ for (len = 1; ptr[len] && !isspace((unsigned char)ptr[len]); len++)
+ continue;
+ if (len == 2 && ptr[0] == 'i' && ptr[1] == 'n') {
+ ptr += 2;
+ break;
+ }
+ if (len == 1)
+ new_for->short_var = 1;
+ strlist_add_str(&new_for->vars, make_str(ptr, len), len);
+ }
+
+ if (strlist_num(&new_for->vars) == 0) {
+ Parse_Error(PARSE_FATAL, "no iteration variables in for");
+ For_Free(new_for);
+ return -1;
+ }
+
+ while (*ptr && isspace((unsigned char) *ptr))
+ ptr++;
+
+ /*
+ * Make a list with the remaining words
+ * The values are substituted as ${:U<value>...} so we must \ escape
+ * characters that break that syntax.
+ * Variables are fully expanded - so it is safe for escape $.
+ * We can't do the escapes here - because we don't know whether
+ * we are substuting into ${...} or $(...).
+ */
+ sub = Var_Subst(NULL, ptr, VAR_GLOBAL, VARF_WANTRES);
+
+ /*
+ * Split into words allowing for quoted strings.
+ */
+ words = brk_string(sub, &nwords, FALSE, &word_buf);
+
+ free(sub);
+
+ if (words != NULL) {
+ for (n = 0; n < nwords; n++) {
+ ptr = words[n];
+ if (!*ptr)
+ continue;
+ escapes = 0;
+ while ((ch = *ptr++)) {
+ switch(ch) {
+ case ':':
+ case '$':
+ case '\\':
+ escapes |= FOR_SUB_ESCAPE_CHAR;
+ break;
+ case ')':
+ escapes |= FOR_SUB_ESCAPE_PAREN;
+ break;
+ case /*{*/ '}':
+ escapes |= FOR_SUB_ESCAPE_BRACE;
+ break;
+ }
+ }
+ /*
+ * We have to dup words[n] to maintain the semantics of
+ * strlist.
+ */
+ strlist_add_str(&new_for->items, bmake_strdup(words[n]), escapes);
+ }
+
+ free(words);
+ free(word_buf);
+
+ if ((len = strlist_num(&new_for->items)) > 0 &&
+ len % (n = strlist_num(&new_for->vars))) {
+ Parse_Error(PARSE_FATAL,
+ "Wrong number of words (%d) in .for substitution list"
+ " with %d vars", len, n);
+ /*
+ * Return 'success' so that the body of the .for loop is
+ * accumulated.
+ * Remove all items so that the loop doesn't iterate.
+ */
+ strlist_clean(&new_for->items);
+ }
+ }
+
+ Buf_Init(&new_for->buf, 0);
+ accumFor = new_for;
+ forLevel = 1;
+ return 1;
+}
+
+/*
+ * Add another line to a .for loop.
+ * Returns 0 when the matching .endfor is reached.
+ */
+
+int
+For_Accum(char *line)
+{
+ char *ptr = line;
+
+ if (*ptr == '.') {
+
+ for (ptr++; *ptr && isspace((unsigned char) *ptr); ptr++)
+ continue;
+
+ if (strncmp(ptr, "endfor", 6) == 0 &&
+ (isspace((unsigned char) ptr[6]) || !ptr[6])) {
+ if (DEBUG(FOR))
+ (void)fprintf(debug_file, "For: end for %d\n", forLevel);
+ if (--forLevel <= 0)
+ return 0;
+ } else if (strncmp(ptr, "for", 3) == 0 &&
+ isspace((unsigned char) ptr[3])) {
+ forLevel++;
+ if (DEBUG(FOR))
+ (void)fprintf(debug_file, "For: new loop %d\n", forLevel);
+ }
+ }
+
+ Buf_AddBytes(&accumFor->buf, strlen(line), line);
+ Buf_AddByte(&accumFor->buf, '\n');
+ return 1;
+}
+
+
+/*-
+ *-----------------------------------------------------------------------
+ * For_Run --
+ * Run the for loop, imitating the actions of an include file
+ *
+ * Results:
+ * None.
+ *
+ * Side Effects:
+ * None.
+ *
+ *-----------------------------------------------------------------------
+ */
+
+static int
+for_var_len(const char *var)
+{
+ char ch, var_start, var_end;
+ int depth;
+ int len;
+
+ var_start = *var;
+ if (var_start == 0)
+ /* just escape the $ */
+ return 0;
+
+ if (var_start == '(')
+ var_end = ')';
+ else if (var_start == '{')
+ var_end = '}';
+ else
+ /* Single char variable */
+ return 1;
+
+ depth = 1;
+ for (len = 1; (ch = var[len++]) != 0;) {
+ if (ch == var_start)
+ depth++;
+ else if (ch == var_end && --depth == 0)
+ return len;
+ }
+
+ /* Variable end not found, escape the $ */
+ return 0;
+}
+
+static void
+for_substitute(Buffer *cmds, strlist_t *items, unsigned int item_no, char ech)
+{
+ const char *item = strlist_str(items, item_no);
+ int len;
+ char ch;
+
+ /* If there were no escapes, or the only escape is the other variable
+ * terminator, then just substitute the full string */
+ if (!(strlist_info(items, item_no) &
+ (ech == ')' ? ~FOR_SUB_ESCAPE_BRACE : ~FOR_SUB_ESCAPE_PAREN))) {
+ Buf_AddBytes(cmds, strlen(item), item);
+ return;
+ }
+
+ /* Escape ':', '$', '\\' and 'ech' - removed by :U processing */
+ while ((ch = *item++) != 0) {
+ if (ch == '$') {
+ len = for_var_len(item);
+ if (len != 0) {
+ Buf_AddBytes(cmds, len + 1, item - 1);
+ item += len;
+ continue;
+ }
+ Buf_AddByte(cmds, '\\');
+ } else if (ch == ':' || ch == '\\' || ch == ech)
+ Buf_AddByte(cmds, '\\');
+ Buf_AddByte(cmds, ch);
+ }
+}
+
+static char *
+For_Iterate(void *v_arg, size_t *ret_len)
+{
+ For *arg = v_arg;
+ int i, len;
+ char *var;
+ char *cp;
+ char *cmd_cp;
+ char *body_end;
+ char ch;
+ Buffer cmds;
+
+ if (arg->sub_next + strlist_num(&arg->vars) > strlist_num(&arg->items)) {
+ /* No more iterations */
+ For_Free(arg);
+ return NULL;
+ }
+
+ free(arg->parse_buf);
+ arg->parse_buf = NULL;
+
+ /*
+ * Scan the for loop body and replace references to the loop variables
+ * with variable references that expand to the required text.
+ * Using variable expansions ensures that the .for loop can't generate
+ * syntax, and that the later parsing will still see a variable.
+ * We assume that the null variable will never be defined.
+ *
+ * The detection of substitions of the loop control variable is naive.
+ * Many of the modifiers use \ to escape $ (not $) so it is possible
+ * to contrive a makefile where an unwanted substitution happens.
+ */
+
+ cmd_cp = Buf_GetAll(&arg->buf, &len);
+ body_end = cmd_cp + len;
+ Buf_Init(&cmds, len + 256);
+ for (cp = cmd_cp; (cp = strchr(cp, '$')) != NULL;) {
+ char ech;
+ ch = *++cp;
+ if ((ch == '(' && (ech = ')', 1)) || (ch == '{' && (ech = '}', 1))) {
+ cp++;
+ /* Check variable name against the .for loop variables */
+ STRLIST_FOREACH(var, &arg->vars, i) {
+ len = strlist_info(&arg->vars, i);
+ if (memcmp(cp, var, len) != 0)
+ continue;
+ if (cp[len] != ':' && cp[len] != ech && cp[len] != '\\')
+ continue;
+ /* Found a variable match. Replace with :U<value> */
+ Buf_AddBytes(&cmds, cp - cmd_cp, cmd_cp);
+ Buf_AddBytes(&cmds, 2, ":U");
+ cp += len;
+ cmd_cp = cp;
+ for_substitute(&cmds, &arg->items, arg->sub_next + i, ech);
+ break;
+ }
+ continue;
+ }
+ if (ch == 0)
+ break;
+ /* Probably a single character name, ignore $$ and stupid ones. {*/
+ if (!arg->short_var || strchr("}):$", ch) != NULL) {
+ cp++;
+ continue;
+ }
+ STRLIST_FOREACH(var, &arg->vars, i) {
+ if (var[0] != ch || var[1] != 0)
+ continue;
+ /* Found a variable match. Replace with ${:U<value>} */
+ Buf_AddBytes(&cmds, cp - cmd_cp, cmd_cp);
+ Buf_AddBytes(&cmds, 3, "{:U");
+ cmd_cp = ++cp;
+ for_substitute(&cmds, &arg->items, arg->sub_next + i, /*{*/ '}');
+ Buf_AddBytes(&cmds, 1, "}");
+ break;
+ }
+ }
+ Buf_AddBytes(&cmds, body_end - cmd_cp, cmd_cp);
+
+ cp = Buf_Destroy(&cmds, FALSE);
+ if (DEBUG(FOR))
+ (void)fprintf(debug_file, "For: loop body:\n%s", cp);
+
+ arg->sub_next += strlist_num(&arg->vars);
+
+ arg->parse_buf = cp;
+ *ret_len = strlen(cp);
+ return cp;
+}
+
+void
+For_Run(int lineno)
+{
+ For *arg;
+
+ arg = accumFor;
+ accumFor = NULL;
+
+ if (strlist_num(&arg->items) == 0) {
+ /* Nothing to expand - possibly due to an earlier syntax error. */
+ For_Free(arg);
+ return;
+ }
+
+ Parse_SetInput(NULL, lineno, -1, For_Iterate, arg);
+}
diff --git a/usr.bin/make/hash.c b/usr.bin/make/hash.c
new file mode 100644
index 0000000..ed23644
--- /dev/null
+++ b/usr.bin/make/hash.c
@@ -0,0 +1,466 @@
+/* $NetBSD: hash.c,v 1.20 2013/11/14 00:27:05 sjg Exp $ */
+
+/*
+ * Copyright (c) 1988, 1989, 1990 The Regents of the University of California.
+ * All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Adam de Boor.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+/*
+ * Copyright (c) 1988, 1989 by Adam de Boor
+ * Copyright (c) 1989 by Berkeley Softworks
+ * All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Adam de Boor.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. All advertising materials mentioning features or use of this software
+ * must display the following acknowledgement:
+ * This product includes software developed by the University of
+ * California, Berkeley and its contributors.
+ * 4. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#ifndef MAKE_NATIVE
+static char rcsid[] = "$NetBSD: hash.c,v 1.20 2013/11/14 00:27:05 sjg Exp $";
+#else
+#include <sys/cdefs.h>
+#ifndef lint
+#if 0
+static char sccsid[] = "@(#)hash.c 8.1 (Berkeley) 6/6/93";
+#else
+__RCSID("$NetBSD: hash.c,v 1.20 2013/11/14 00:27:05 sjg Exp $");
+#endif
+#endif /* not lint */
+#endif
+
+/* hash.c --
+ *
+ * This module contains routines to manipulate a hash table.
+ * See hash.h for a definition of the structure of the hash
+ * table. Hash tables grow automatically as the amount of
+ * information increases.
+ */
+#include "sprite.h"
+#include "make.h"
+#include "hash.h"
+
+/*
+ * Forward references to local procedures that are used before they're
+ * defined:
+ */
+
+static void RebuildTable(Hash_Table *);
+
+/*
+ * The following defines the ratio of # entries to # buckets
+ * at which we rebuild the table to make it larger.
+ */
+
+#define rebuildLimit 3
+
+/*
+ *---------------------------------------------------------
+ *
+ * Hash_InitTable --
+ *
+ * This routine just sets up the hash table.
+ *
+ * Input:
+ * t Structure to to hold table.
+ * numBuckets How many buckets to create for starters. This
+ * number is rounded up to a power of two. If
+ * <= 0, a reasonable default is chosen. The
+ * table will grow in size later as needed.
+ *
+ * Results:
+ * None.
+ *
+ * Side Effects:
+ * Memory is allocated for the initial bucket area.
+ *
+ *---------------------------------------------------------
+ */
+
+void
+Hash_InitTable(Hash_Table *t, int numBuckets)
+{
+ int i;
+ struct Hash_Entry **hp;
+
+ /*
+ * Round up the size to a power of two.
+ */
+ if (numBuckets <= 0)
+ i = 16;
+ else {
+ for (i = 2; i < numBuckets; i <<= 1)
+ continue;
+ }
+ t->numEntries = 0;
+ t->size = i;
+ t->mask = i - 1;
+ t->bucketPtr = hp = bmake_malloc(sizeof(*hp) * i);
+ while (--i >= 0)
+ *hp++ = NULL;
+}
+
+/*
+ *---------------------------------------------------------
+ *
+ * Hash_DeleteTable --
+ *
+ * This routine removes everything from a hash table
+ * and frees up the memory space it occupied (except for
+ * the space in the Hash_Table structure).
+ *
+ * Results:
+ * None.
+ *
+ * Side Effects:
+ * Lots of memory is freed up.
+ *
+ *---------------------------------------------------------
+ */
+
+void
+Hash_DeleteTable(Hash_Table *t)
+{
+ struct Hash_Entry **hp, *h, *nexth = NULL;
+ int i;
+
+ for (hp = t->bucketPtr, i = t->size; --i >= 0;) {
+ for (h = *hp++; h != NULL; h = nexth) {
+ nexth = h->next;
+ free(h);
+ }
+ }
+ free(t->bucketPtr);
+
+ /*
+ * Set up the hash table to cause memory faults on any future access
+ * attempts until re-initialization.
+ */
+ t->bucketPtr = NULL;
+}
+
+/*
+ *---------------------------------------------------------
+ *
+ * Hash_FindEntry --
+ *
+ * Searches a hash table for an entry corresponding to key.
+ *
+ * Input:
+ * t Hash table to search.
+ * key A hash key.
+ *
+ * Results:
+ * The return value is a pointer to the entry for key,
+ * if key was present in the table. If key was not
+ * present, NULL is returned.
+ *
+ * Side Effects:
+ * None.
+ *
+ *---------------------------------------------------------
+ */
+
+Hash_Entry *
+Hash_FindEntry(Hash_Table *t, const char *key)
+{
+ Hash_Entry *e;
+ unsigned h;
+ const char *p;
+
+ if (t == NULL || t->bucketPtr == NULL) {
+ return NULL;
+ }
+ for (h = 0, p = key; *p;)
+ h = (h << 5) - h + *p++;
+ p = key;
+ for (e = t->bucketPtr[h & t->mask]; e != NULL; e = e->next)
+ if (e->namehash == h && strcmp(e->name, p) == 0)
+ return (e);
+ return NULL;
+}
+
+/*
+ *---------------------------------------------------------
+ *
+ * Hash_CreateEntry --
+ *
+ * Searches a hash table for an entry corresponding to
+ * key. If no entry is found, then one is created.
+ *
+ * Input:
+ * t Hash table to search.
+ * key A hash key.
+ * newPtr Filled in with TRUE if new entry created,
+ * FALSE otherwise.
+ *
+ * Results:
+ * The return value is a pointer to the entry. If *newPtr
+ * isn't NULL, then *newPtr is filled in with TRUE if a
+ * new entry was created, and FALSE if an entry already existed
+ * with the given key.
+ *
+ * Side Effects:
+ * Memory may be allocated, and the hash buckets may be modified.
+ *---------------------------------------------------------
+ */
+
+Hash_Entry *
+Hash_CreateEntry(Hash_Table *t, const char *key, Boolean *newPtr)
+{
+ Hash_Entry *e;
+ unsigned h;
+ const char *p;
+ int keylen;
+ struct Hash_Entry **hp;
+
+ /*
+ * Hash the key. As a side effect, save the length (strlen) of the
+ * key in case we need to create the entry.
+ */
+ for (h = 0, p = key; *p;)
+ h = (h << 5) - h + *p++;
+ keylen = p - key;
+ p = key;
+ for (e = t->bucketPtr[h & t->mask]; e != NULL; e = e->next) {
+ if (e->namehash == h && strcmp(e->name, p) == 0) {
+ if (newPtr != NULL)
+ *newPtr = FALSE;
+ return (e);
+ }
+ }
+
+ /*
+ * The desired entry isn't there. Before allocating a new entry,
+ * expand the table if necessary (and this changes the resulting
+ * bucket chain).
+ */
+ if (t->numEntries >= rebuildLimit * t->size)
+ RebuildTable(t);
+ e = bmake_malloc(sizeof(*e) + keylen);
+ hp = &t->bucketPtr[h & t->mask];
+ e->next = *hp;
+ *hp = e;
+ Hash_SetValue(e, NULL);
+ e->namehash = h;
+ (void)strcpy(e->name, p);
+ t->numEntries++;
+
+ if (newPtr != NULL)
+ *newPtr = TRUE;
+ return (e);
+}
+
+/*
+ *---------------------------------------------------------
+ *
+ * Hash_DeleteEntry --
+ *
+ * Delete the given hash table entry and free memory associated with
+ * it.
+ *
+ * Results:
+ * None.
+ *
+ * Side Effects:
+ * Hash chain that entry lives in is modified and memory is freed.
+ *
+ *---------------------------------------------------------
+ */
+
+void
+Hash_DeleteEntry(Hash_Table *t, Hash_Entry *e)
+{
+ Hash_Entry **hp, *p;
+
+ if (e == NULL)
+ return;
+ for (hp = &t->bucketPtr[e->namehash & t->mask];
+ (p = *hp) != NULL; hp = &p->next) {
+ if (p == e) {
+ *hp = p->next;
+ free(p);
+ t->numEntries--;
+ return;
+ }
+ }
+ (void)write(2, "bad call to Hash_DeleteEntry\n", 29);
+ abort();
+}
+
+/*
+ *---------------------------------------------------------
+ *
+ * Hash_EnumFirst --
+ * This procedure sets things up for a complete search
+ * of all entries recorded in the hash table.
+ *
+ * Input:
+ * t Table to be searched.
+ * searchPtr Area in which to keep state about search.
+ *
+ * Results:
+ * The return value is the address of the first entry in
+ * the hash table, or NULL if the table is empty.
+ *
+ * Side Effects:
+ * The information in searchPtr is initialized so that successive
+ * calls to Hash_Next will return successive HashEntry's
+ * from the table.
+ *
+ *---------------------------------------------------------
+ */
+
+Hash_Entry *
+Hash_EnumFirst(Hash_Table *t, Hash_Search *searchPtr)
+{
+ searchPtr->tablePtr = t;
+ searchPtr->nextIndex = 0;
+ searchPtr->hashEntryPtr = NULL;
+ return Hash_EnumNext(searchPtr);
+}
+
+/*
+ *---------------------------------------------------------
+ *
+ * Hash_EnumNext --
+ * This procedure returns successive entries in the hash table.
+ *
+ * Input:
+ * searchPtr Area used to keep state about search.
+ *
+ * Results:
+ * The return value is a pointer to the next HashEntry
+ * in the table, or NULL when the end of the table is
+ * reached.
+ *
+ * Side Effects:
+ * The information in searchPtr is modified to advance to the
+ * next entry.
+ *
+ *---------------------------------------------------------
+ */
+
+Hash_Entry *
+Hash_EnumNext(Hash_Search *searchPtr)
+{
+ Hash_Entry *e;
+ Hash_Table *t = searchPtr->tablePtr;
+
+ /*
+ * The hashEntryPtr field points to the most recently returned
+ * entry, or is nil if we are starting up. If not nil, we have
+ * to start at the next one in the chain.
+ */
+ e = searchPtr->hashEntryPtr;
+ if (e != NULL)
+ e = e->next;
+ /*
+ * If the chain ran out, or if we are starting up, we need to
+ * find the next nonempty chain.
+ */
+ while (e == NULL) {
+ if (searchPtr->nextIndex >= t->size)
+ return NULL;
+ e = t->bucketPtr[searchPtr->nextIndex++];
+ }
+ searchPtr->hashEntryPtr = e;
+ return (e);
+}
+
+/*
+ *---------------------------------------------------------
+ *
+ * RebuildTable --
+ * This local routine makes a new hash table that
+ * is larger than the old one.
+ *
+ * Results:
+ * None.
+ *
+ * Side Effects:
+ * The entire hash table is moved, so any bucket numbers
+ * from the old table are invalid.
+ *
+ *---------------------------------------------------------
+ */
+
+static void
+RebuildTable(Hash_Table *t)
+{
+ Hash_Entry *e, *next = NULL, **hp, **xp;
+ int i, mask;
+ Hash_Entry **oldhp;
+ int oldsize;
+
+ oldhp = t->bucketPtr;
+ oldsize = i = t->size;
+ i <<= 1;
+ t->size = i;
+ t->mask = mask = i - 1;
+ t->bucketPtr = hp = bmake_malloc(sizeof(*hp) * i);
+ while (--i >= 0)
+ *hp++ = NULL;
+ for (hp = oldhp, i = oldsize; --i >= 0;) {
+ for (e = *hp++; e != NULL; e = next) {
+ next = e->next;
+ xp = &t->bucketPtr[e->namehash & mask];
+ e->next = *xp;
+ *xp = e;
+ }
+ }
+ free(oldhp);
+}
diff --git a/usr.bin/make/hash.h b/usr.bin/make/hash.h
new file mode 100644
index 0000000..8ab6ffd
--- /dev/null
+++ b/usr.bin/make/hash.h
@@ -0,0 +1,149 @@
+/* $NetBSD: hash.h,v 1.12 2017/05/31 21:07:03 maya Exp $ */
+
+/*
+ * Copyright (c) 1988, 1989, 1990 The Regents of the University of California.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Adam de Boor.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * from: @(#)hash.h 8.1 (Berkeley) 6/6/93
+ */
+
+/*
+ * Copyright (c) 1988, 1989 by Adam de Boor
+ * Copyright (c) 1989 by Berkeley Softworks
+ * All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Adam de Boor.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. All advertising materials mentioning features or use of this software
+ * must display the following acknowledgement:
+ * This product includes software developed by the University of
+ * California, Berkeley and its contributors.
+ * 4. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * from: @(#)hash.h 8.1 (Berkeley) 6/6/93
+ */
+
+/* hash.h --
+ *
+ * This file contains definitions used by the hash module,
+ * which maintains hash tables.
+ */
+
+#ifndef _HASH_H
+#define _HASH_H
+
+/*
+ * The following defines one entry in the hash table.
+ */
+
+typedef struct Hash_Entry {
+ struct Hash_Entry *next; /* Used to link together all the
+ * entries associated with the same
+ * bucket. */
+ void *clientPtr; /* Arbitrary pointer */
+ unsigned namehash; /* hash value of key */
+ char name[1]; /* key string */
+} Hash_Entry;
+
+typedef struct Hash_Table {
+ struct Hash_Entry **bucketPtr;/* Pointers to Hash_Entry, one
+ * for each bucket in the table. */
+ int size; /* Actual size of array. */
+ int numEntries; /* Number of entries in the table. */
+ int mask; /* Used to select bits for hashing. */
+} Hash_Table;
+
+/*
+ * The following structure is used by the searching routines
+ * to record where we are in the search.
+ */
+
+typedef struct Hash_Search {
+ Hash_Table *tablePtr; /* Table being searched. */
+ int nextIndex; /* Next bucket to check (after current). */
+ Hash_Entry *hashEntryPtr; /* Next entry to check in current bucket. */
+} Hash_Search;
+
+/*
+ * Macros.
+ */
+
+/*
+ * void * Hash_GetValue(h)
+ * Hash_Entry *h;
+ */
+
+#define Hash_GetValue(h) ((h)->clientPtr)
+
+/*
+ * Hash_SetValue(h, val);
+ * Hash_Entry *h;
+ * char *val;
+ */
+
+#define Hash_SetValue(h, val) ((h)->clientPtr = (val))
+
+/*
+ * Hash_Size(n) returns the number of words in an object of n bytes
+ */
+
+#define Hash_Size(n) (((n) + sizeof (int) - 1) / sizeof (int))
+
+void Hash_InitTable(Hash_Table *, int);
+void Hash_DeleteTable(Hash_Table *);
+Hash_Entry *Hash_FindEntry(Hash_Table *, const char *);
+Hash_Entry *Hash_CreateEntry(Hash_Table *, const char *, Boolean *);
+void Hash_DeleteEntry(Hash_Table *, Hash_Entry *);
+Hash_Entry *Hash_EnumFirst(Hash_Table *, Hash_Search *);
+Hash_Entry *Hash_EnumNext(Hash_Search *);
+
+#endif /* _HASH_H */
diff --git a/usr.bin/make/job.c b/usr.bin/make/job.c
new file mode 100644
index 0000000..48ec284
--- /dev/null
+++ b/usr.bin/make/job.c
@@ -0,0 +1,3070 @@
+/* $NetBSD: job.c,v 1.195 2018/05/13 22:13:28 sjg Exp $ */
+
+/*
+ * Copyright (c) 1988, 1989, 1990 The Regents of the University of California.
+ * All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Adam de Boor.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+/*
+ * Copyright (c) 1988, 1989 by Adam de Boor
+ * Copyright (c) 1989 by Berkeley Softworks
+ * All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Adam de Boor.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. All advertising materials mentioning features or use of this software
+ * must display the following acknowledgement:
+ * This product includes software developed by the University of
+ * California, Berkeley and its contributors.
+ * 4. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#ifndef MAKE_NATIVE
+static char rcsid[] = "$NetBSD: job.c,v 1.195 2018/05/13 22:13:28 sjg Exp $";
+#else
+#include <sys/cdefs.h>
+#ifndef lint
+#if 0
+static char sccsid[] = "@(#)job.c 8.2 (Berkeley) 3/19/94";
+#else
+__RCSID("$NetBSD: job.c,v 1.195 2018/05/13 22:13:28 sjg Exp $");
+#endif
+#endif /* not lint */
+#endif
+
+/*-
+ * job.c --
+ * handle the creation etc. of our child processes.
+ *
+ * Interface:
+ * Job_Make Start the creation of the given target.
+ *
+ * Job_CatchChildren Check for and handle the termination of any
+ * children. This must be called reasonably
+ * frequently to keep the whole make going at
+ * a decent clip, since job table entries aren't
+ * removed until their process is caught this way.
+ *
+ * Job_CatchOutput Print any output our children have produced.
+ * Should also be called fairly frequently to
+ * keep the user informed of what's going on.
+ * If no output is waiting, it will block for
+ * a time given by the SEL_* constants, below,
+ * or until output is ready.
+ *
+ * Job_Init Called to initialize this module. in addition,
+ * any commands attached to the .BEGIN target
+ * are executed before this function returns.
+ * Hence, the makefile must have been parsed
+ * before this function is called.
+ *
+ * Job_End Cleanup any memory used.
+ *
+ * Job_ParseShell Given the line following a .SHELL target, parse
+ * the line as a shell specification. Returns
+ * FAILURE if the spec was incorrect.
+ *
+ * Job_Finish Perform any final processing which needs doing.
+ * This includes the execution of any commands
+ * which have been/were attached to the .END
+ * target. It should only be called when the
+ * job table is empty.
+ *
+ * Job_AbortAll Abort all currently running jobs. It doesn't
+ * handle output or do anything for the jobs,
+ * just kills them. It should only be called in
+ * an emergency, as it were.
+ *
+ * Job_CheckCommands Verify that the commands for a target are
+ * ok. Provide them if necessary and possible.
+ *
+ * Job_Touch Update a target without really updating it.
+ *
+ * Job_Wait Wait for all currently-running jobs to finish.
+ */
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/file.h>
+#include <sys/time.h>
+#include <sys/wait.h>
+
+#include <assert.h>
+#include <errno.h>
+#ifndef USE_SELECT
+#include <poll.h>
+#endif
+#include <signal.h>
+#include <stdio.h>
+#include <string.h>
+#include <utime.h>
+
+#include "make.h"
+#include "hash.h"
+#include "dir.h"
+#include "job.h"
+#include "pathnames.h"
+#include "trace.h"
+# define STATIC static
+
+/*
+ * error handling variables
+ */
+static int errors = 0; /* number of errors reported */
+static int aborting = 0; /* why is the make aborting? */
+#define ABORT_ERROR 1 /* Because of an error */
+#define ABORT_INTERRUPT 2 /* Because it was interrupted */
+#define ABORT_WAIT 3 /* Waiting for jobs to finish */
+#define JOB_TOKENS "+EI+" /* Token to requeue for each abort state */
+
+/*
+ * this tracks the number of tokens currently "out" to build jobs.
+ */
+int jobTokensRunning = 0;
+int not_parallel = 0; /* set if .NOT_PARALLEL */
+
+/*
+ * XXX: Avoid SunOS bug... FILENO() is fp->_file, and file
+ * is a char! So when we go above 127 we turn negative!
+ */
+#define FILENO(a) ((unsigned) fileno(a))
+
+/*
+ * post-make command processing. The node postCommands is really just the
+ * .END target but we keep it around to avoid having to search for it
+ * all the time.
+ */
+static GNode *postCommands = NULL;
+ /* node containing commands to execute when
+ * everything else is done */
+static int numCommands; /* The number of commands actually printed
+ * for a target. Should this number be
+ * 0, no shell will be executed. */
+
+/*
+ * Return values from JobStart.
+ */
+#define JOB_RUNNING 0 /* Job is running */
+#define JOB_ERROR 1 /* Error in starting the job */
+#define JOB_FINISHED 2 /* The job is already finished */
+
+/*
+ * Descriptions for various shells.
+ *
+ * The build environment may set DEFSHELL_INDEX to one of
+ * DEFSHELL_INDEX_SH, DEFSHELL_INDEX_KSH, or DEFSHELL_INDEX_CSH, to
+ * select one of the prefedined shells as the default shell.
+ *
+ * Alternatively, the build environment may set DEFSHELL_CUSTOM to the
+ * name or the full path of a sh-compatible shell, which will be used as
+ * the default shell.
+ *
+ * ".SHELL" lines in Makefiles can choose the default shell from the
+ # set defined here, or add additional shells.
+ */
+
+#ifdef DEFSHELL_CUSTOM
+#define DEFSHELL_INDEX_CUSTOM 0
+#define DEFSHELL_INDEX_SH 1
+#define DEFSHELL_INDEX_KSH 2
+#define DEFSHELL_INDEX_CSH 3
+#else /* !DEFSHELL_CUSTOM */
+#define DEFSHELL_INDEX_SH 0
+#define DEFSHELL_INDEX_KSH 1
+#define DEFSHELL_INDEX_CSH 2
+#endif /* !DEFSHELL_CUSTOM */
+
+#ifndef DEFSHELL_INDEX
+#define DEFSHELL_INDEX 0 /* DEFSHELL_INDEX_CUSTOM or DEFSHELL_INDEX_SH */
+#endif /* !DEFSHELL_INDEX */
+
+static Shell shells[] = {
+#ifdef DEFSHELL_CUSTOM
+ /*
+ * An sh-compatible shell with a non-standard name.
+ *
+ * Keep this in sync with the "sh" description below, but avoid
+ * non-portable features that might not be supplied by all
+ * sh-compatible shells.
+ */
+{
+ DEFSHELL_CUSTOM,
+ FALSE, "", "", "", 0,
+ FALSE, "echo \"%s\"\n", "%s\n", "{ %s \n} || exit $?\n", "'\n'", '#',
+ "",
+ "",
+},
+#endif /* DEFSHELL_CUSTOM */
+ /*
+ * SH description. Echo control is also possible and, under
+ * sun UNIX anyway, one can even control error checking.
+ */
+{
+ "sh",
+ FALSE, "", "", "", 0,
+ FALSE, "echo \"%s\"\n", "%s\n", "{ %s \n} || exit $?\n", "'\n'", '#',
+#if defined(MAKE_NATIVE) && defined(__NetBSD__)
+ "q",
+#else
+ "",
+#endif
+ "",
+},
+ /*
+ * KSH description.
+ */
+{
+ "ksh",
+ TRUE, "set +v", "set -v", "set +v", 6,
+ FALSE, "echo \"%s\"\n", "%s\n", "{ %s \n} || exit $?\n", "'\n'", '#',
+ "v",
+ "",
+},
+ /*
+ * CSH description. The csh can do echo control by playing
+ * with the setting of the 'echo' shell variable. Sadly,
+ * however, it is unable to do error control nicely.
+ */
+{
+ "csh",
+ TRUE, "unset verbose", "set verbose", "unset verbose", 10,
+ FALSE, "echo \"%s\"\n", "csh -c \"%s || exit 0\"\n", "", "'\\\n'", '#',
+ "v", "e",
+},
+ /*
+ * UNKNOWN.
+ */
+{
+ NULL,
+ FALSE, NULL, NULL, NULL, 0,
+ FALSE, NULL, NULL, NULL, NULL, 0,
+ NULL, NULL,
+}
+};
+static Shell *commandShell = &shells[DEFSHELL_INDEX]; /* this is the shell to
+ * which we pass all
+ * commands in the Makefile.
+ * It is set by the
+ * Job_ParseShell function */
+const char *shellPath = NULL, /* full pathname of
+ * executable image */
+ *shellName = NULL; /* last component of shell */
+char *shellErrFlag = NULL;
+static const char *shellArgv = NULL; /* Custom shell args */
+
+
+STATIC Job *job_table; /* The structures that describe them */
+STATIC Job *job_table_end; /* job_table + maxJobs */
+static int wantToken; /* we want a token */
+static int lurking_children = 0;
+static int make_suspended = 0; /* non-zero if we've seen a SIGTSTP (etc) */
+
+/*
+ * Set of descriptors of pipes connected to
+ * the output channels of children
+ */
+static struct pollfd *fds = NULL;
+static Job **jobfds = NULL;
+static int nfds = 0;
+static void watchfd(Job *);
+static void clearfd(Job *);
+static int readyfd(Job *);
+
+STATIC GNode *lastNode; /* The node for which output was most recently
+ * produced. */
+static char *targPrefix = NULL; /* What we print at the start of TARG_FMT */
+static Job tokenWaitJob; /* token wait pseudo-job */
+
+static Job childExitJob; /* child exit pseudo-job */
+#define CHILD_EXIT "."
+#define DO_JOB_RESUME "R"
+
+#define TARG_FMT "%s %s ---\n" /* Default format */
+#define MESSAGE(fp, gn) \
+ if (maxJobs != 1 && targPrefix && *targPrefix) \
+ (void)fprintf(fp, TARG_FMT, targPrefix, gn->name)
+
+static sigset_t caught_signals; /* Set of signals we handle */
+
+static void JobChildSig(int);
+static void JobContinueSig(int);
+static Job *JobFindPid(int, int, Boolean);
+static int JobPrintCommand(void *, void *);
+static int JobSaveCommand(void *, void *);
+static void JobClose(Job *);
+static void JobExec(Job *, char **);
+static void JobMakeArgv(Job *, char **);
+static int JobStart(GNode *, int);
+static char *JobOutput(Job *, char *, char *, int);
+static void JobDoOutput(Job *, Boolean);
+static Shell *JobMatchShell(const char *);
+static void JobInterrupt(int, int) MAKE_ATTR_DEAD;
+static void JobRestartJobs(void);
+static void JobTokenAdd(void);
+static void JobSigLock(sigset_t *);
+static void JobSigUnlock(sigset_t *);
+static void JobSigReset(void);
+
+const char *malloc_options="A";
+
+static void
+job_table_dump(const char *where)
+{
+ Job *job;
+
+ fprintf(debug_file, "job table @ %s\n", where);
+ for (job = job_table; job < job_table_end; job++) {
+ fprintf(debug_file, "job %d, status %d, flags %d, pid %d\n",
+ (int)(job - job_table), job->job_state, job->flags, job->pid);
+ }
+}
+
+/*
+ * Delete the target of a failed, interrupted, or otherwise
+ * unsuccessful job unless inhibited by .PRECIOUS.
+ */
+static void
+JobDeleteTarget(GNode *gn)
+{
+ if ((gn->type & (OP_JOIN|OP_PHONY)) == 0 && !Targ_Precious(gn)) {
+ char *file = (gn->path == NULL ? gn->name : gn->path);
+ if (!noExecute && eunlink(file) != -1) {
+ Error("*** %s removed", file);
+ }
+ }
+}
+
+/*
+ * JobSigLock/JobSigUnlock
+ *
+ * Signal lock routines to get exclusive access. Currently used to
+ * protect `jobs' and `stoppedJobs' list manipulations.
+ */
+static void JobSigLock(sigset_t *omaskp)
+{
+ if (sigprocmask(SIG_BLOCK, &caught_signals, omaskp) != 0) {
+ Punt("JobSigLock: sigprocmask: %s", strerror(errno));
+ sigemptyset(omaskp);
+ }
+}
+
+static void JobSigUnlock(sigset_t *omaskp)
+{
+ (void)sigprocmask(SIG_SETMASK, omaskp, NULL);
+}
+
+static void
+JobCreatePipe(Job *job, int minfd)
+{
+ int i, fd, flags;
+
+ if (pipe(job->jobPipe) == -1)
+ Punt("Cannot create pipe: %s", strerror(errno));
+
+ for (i = 0; i < 2; i++) {
+ /* Avoid using low numbered fds */
+ fd = fcntl(job->jobPipe[i], F_DUPFD, minfd);
+ if (fd != -1) {
+ close(job->jobPipe[i]);
+ job->jobPipe[i] = fd;
+ }
+ }
+
+ /* Set close-on-exec flag for both */
+ if (fcntl(job->jobPipe[0], F_SETFD, FD_CLOEXEC) == -1)
+ Punt("Cannot set close-on-exec: %s", strerror(errno));
+ if (fcntl(job->jobPipe[1], F_SETFD, FD_CLOEXEC) == -1)
+ Punt("Cannot set close-on-exec: %s", strerror(errno));
+
+ /*
+ * We mark the input side of the pipe non-blocking; we poll(2) the
+ * pipe when we're waiting for a job token, but we might lose the
+ * race for the token when a new one becomes available, so the read
+ * from the pipe should not block.
+ */
+ flags = fcntl(job->jobPipe[0], F_GETFL, 0);
+ if (flags == -1)
+ Punt("Cannot get flags: %s", strerror(errno));
+ flags |= O_NONBLOCK;
+ if (fcntl(job->jobPipe[0], F_SETFL, flags) == -1)
+ Punt("Cannot set flags: %s", strerror(errno));
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * JobCondPassSig --
+ * Pass a signal to a job
+ *
+ * Input:
+ * signop Signal to send it
+ *
+ * Side Effects:
+ * None, except the job may bite it.
+ *
+ *-----------------------------------------------------------------------
+ */
+static void
+JobCondPassSig(int signo)
+{
+ Job *job;
+
+ if (DEBUG(JOB)) {
+ (void)fprintf(debug_file, "JobCondPassSig(%d) called.\n", signo);
+ }
+
+ for (job = job_table; job < job_table_end; job++) {
+ if (job->job_state != JOB_ST_RUNNING)
+ continue;
+ if (DEBUG(JOB)) {
+ (void)fprintf(debug_file,
+ "JobCondPassSig passing signal %d to child %d.\n",
+ signo, job->pid);
+ }
+ KILLPG(job->pid, signo);
+ }
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * JobChldSig --
+ * SIGCHLD handler.
+ *
+ * Input:
+ * signo The signal number we've received
+ *
+ * Results:
+ * None.
+ *
+ * Side Effects:
+ * Sends a token on the child exit pipe to wake us up from
+ * select()/poll().
+ *
+ *-----------------------------------------------------------------------
+ */
+static void
+JobChildSig(int signo MAKE_ATTR_UNUSED)
+{
+ while (write(childExitJob.outPipe, CHILD_EXIT, 1) == -1 && errno == EAGAIN)
+ continue;
+}
+
+
+/*-
+ *-----------------------------------------------------------------------
+ * JobContinueSig --
+ * Resume all stopped jobs.
+ *
+ * Input:
+ * signo The signal number we've received
+ *
+ * Results:
+ * None.
+ *
+ * Side Effects:
+ * Jobs start running again.
+ *
+ *-----------------------------------------------------------------------
+ */
+static void
+JobContinueSig(int signo MAKE_ATTR_UNUSED)
+{
+ /*
+ * Defer sending to SIGCONT to our stopped children until we return
+ * from the signal handler.
+ */
+ while (write(childExitJob.outPipe, DO_JOB_RESUME, 1) == -1 &&
+ errno == EAGAIN)
+ continue;
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * JobPassSig --
+ * Pass a signal on to all jobs, then resend to ourselves.
+ *
+ * Input:
+ * signo The signal number we've received
+ *
+ * Results:
+ * None.
+ *
+ * Side Effects:
+ * We die by the same signal.
+ *
+ *-----------------------------------------------------------------------
+ */
+MAKE_ATTR_DEAD static void
+JobPassSig_int(int signo)
+{
+ /* Run .INTERRUPT target then exit */
+ JobInterrupt(TRUE, signo);
+}
+
+MAKE_ATTR_DEAD static void
+JobPassSig_term(int signo)
+{
+ /* Dont run .INTERRUPT target then exit */
+ JobInterrupt(FALSE, signo);
+}
+
+static void
+JobPassSig_suspend(int signo)
+{
+ sigset_t nmask, omask;
+ struct sigaction act;
+
+ /* Suppress job started/continued messages */
+ make_suspended = 1;
+
+ /* Pass the signal onto every job */
+ JobCondPassSig(signo);
+
+ /*
+ * Send ourselves the signal now we've given the message to everyone else.
+ * Note we block everything else possible while we're getting the signal.
+ * This ensures that all our jobs get continued when we wake up before
+ * we take any other signal.
+ */
+ sigfillset(&nmask);
+ sigdelset(&nmask, signo);
+ (void)sigprocmask(SIG_SETMASK, &nmask, &omask);
+
+ act.sa_handler = SIG_DFL;
+ sigemptyset(&act.sa_mask);
+ act.sa_flags = 0;
+ (void)sigaction(signo, &act, NULL);
+
+ if (DEBUG(JOB)) {
+ (void)fprintf(debug_file,
+ "JobPassSig passing signal %d to self.\n", signo);
+ }
+
+ (void)kill(getpid(), signo);
+
+ /*
+ * We've been continued.
+ *
+ * A whole host of signals continue to happen!
+ * SIGCHLD for any processes that actually suspended themselves.
+ * SIGCHLD for any processes that exited while we were alseep.
+ * The SIGCONT that actually caused us to wakeup.
+ *
+ * Since we defer passing the SIGCONT on to our children until
+ * the main processing loop, we can be sure that all the SIGCHLD
+ * events will have happened by then - and that the waitpid() will
+ * collect the child 'suspended' events.
+ * For correct sequencing we just need to ensure we process the
+ * waitpid() before passign on the SIGCONT.
+ *
+ * In any case nothing else is needed here.
+ */
+
+ /* Restore handler and signal mask */
+ act.sa_handler = JobPassSig_suspend;
+ (void)sigaction(signo, &act, NULL);
+ (void)sigprocmask(SIG_SETMASK, &omask, NULL);
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * JobFindPid --
+ * Compare the pid of the job with the given pid and return 0 if they
+ * are equal. This function is called from Job_CatchChildren
+ * to find the job descriptor of the finished job.
+ *
+ * Input:
+ * job job to examine
+ * pid process id desired
+ *
+ * Results:
+ * Job with matching pid
+ *
+ * Side Effects:
+ * None
+ *-----------------------------------------------------------------------
+ */
+static Job *
+JobFindPid(int pid, int status, Boolean isJobs)
+{
+ Job *job;
+
+ for (job = job_table; job < job_table_end; job++) {
+ if ((job->job_state == status) && job->pid == pid)
+ return job;
+ }
+ if (DEBUG(JOB) && isJobs)
+ job_table_dump("no pid");
+ return NULL;
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * JobPrintCommand --
+ * Put out another command for the given job. If the command starts
+ * with an @ or a - we process it specially. In the former case,
+ * so long as the -s and -n flags weren't given to make, we stick
+ * a shell-specific echoOff command in the script. In the latter,
+ * we ignore errors for the entire job, unless the shell has error
+ * control.
+ * If the command is just "..." we take all future commands for this
+ * job to be commands to be executed once the entire graph has been
+ * made and return non-zero to signal that the end of the commands
+ * was reached. These commands are later attached to the postCommands
+ * node and executed by Job_End when all things are done.
+ * This function is called from JobStart via Lst_ForEach.
+ *
+ * Input:
+ * cmdp command string to print
+ * jobp job for which to print it
+ *
+ * Results:
+ * Always 0, unless the command was "..."
+ *
+ * Side Effects:
+ * If the command begins with a '-' and the shell has no error control,
+ * the JOB_IGNERR flag is set in the job descriptor.
+ * If the command is "..." and we're not ignoring such things,
+ * tailCmds is set to the successor node of the cmd.
+ * numCommands is incremented if the command is actually printed.
+ *-----------------------------------------------------------------------
+ */
+static int
+JobPrintCommand(void *cmdp, void *jobp)
+{
+ Boolean noSpecials; /* true if we shouldn't worry about
+ * inserting special commands into
+ * the input stream. */
+ Boolean shutUp = FALSE; /* true if we put a no echo command
+ * into the command file */
+ Boolean errOff = FALSE; /* true if we turned error checking
+ * off before printing the command
+ * and need to turn it back on */
+ const char *cmdTemplate; /* Template to use when printing the
+ * command */
+ char *cmdStart; /* Start of expanded command */
+ char *escCmd = NULL; /* Command with quotes/backticks escaped */
+ char *cmd = (char *)cmdp;
+ Job *job = (Job *)jobp;
+ int i, j;
+
+ noSpecials = NoExecute(job->node);
+
+ if (strcmp(cmd, "...") == 0) {
+ job->node->type |= OP_SAVE_CMDS;
+ if ((job->flags & JOB_IGNDOTS) == 0) {
+ job->tailCmds = Lst_Succ(Lst_Member(job->node->commands,
+ cmd));
+ return 1;
+ }
+ return 0;
+ }
+
+#define DBPRINTF(fmt, arg) if (DEBUG(JOB)) { \
+ (void)fprintf(debug_file, fmt, arg); \
+ } \
+ (void)fprintf(job->cmdFILE, fmt, arg); \
+ (void)fflush(job->cmdFILE);
+
+ numCommands += 1;
+
+ cmdStart = cmd = Var_Subst(NULL, cmd, job->node, VARF_WANTRES);
+
+ cmdTemplate = "%s\n";
+
+ /*
+ * Check for leading @' and -'s to control echoing and error checking.
+ */
+ while (*cmd == '@' || *cmd == '-' || (*cmd == '+')) {
+ switch (*cmd) {
+ case '@':
+ shutUp = DEBUG(LOUD) ? FALSE : TRUE;
+ break;
+ case '-':
+ errOff = TRUE;
+ break;
+ case '+':
+ if (noSpecials) {
+ /*
+ * We're not actually executing anything...
+ * but this one needs to be - use compat mode just for it.
+ */
+ CompatRunCommand(cmdp, job->node);
+ free(cmdStart);
+ return 0;
+ }
+ break;
+ }
+ cmd++;
+ }
+
+ while (isspace((unsigned char) *cmd))
+ cmd++;
+
+ /*
+ * If the shell doesn't have error control the alternate echo'ing will
+ * be done (to avoid showing additional error checking code)
+ * and this will need the characters '$ ` \ "' escaped
+ */
+
+ if (!commandShell->hasErrCtl) {
+ /* Worst that could happen is every char needs escaping. */
+ escCmd = bmake_malloc((strlen(cmd) * 2) + 1);
+ for (i = 0, j= 0; cmd[i] != '\0'; i++, j++) {
+ if (cmd[i] == '$' || cmd[i] == '`' || cmd[i] == '\\' ||
+ cmd[i] == '"')
+ escCmd[j++] = '\\';
+ escCmd[j] = cmd[i];
+ }
+ escCmd[j] = 0;
+ }
+
+ if (shutUp) {
+ if (!(job->flags & JOB_SILENT) && !noSpecials &&
+ commandShell->hasEchoCtl) {
+ DBPRINTF("%s\n", commandShell->echoOff);
+ } else {
+ if (commandShell->hasErrCtl)
+ shutUp = FALSE;
+ }
+ }
+
+ if (errOff) {
+ if (!noSpecials) {
+ if (commandShell->hasErrCtl) {
+ /*
+ * we don't want the error-control commands showing
+ * up either, so we turn off echoing while executing
+ * them. We could put another field in the shell
+ * structure to tell JobDoOutput to look for this
+ * string too, but why make it any more complex than
+ * it already is?
+ */
+ if (!(job->flags & JOB_SILENT) && !shutUp &&
+ commandShell->hasEchoCtl) {
+ DBPRINTF("%s\n", commandShell->echoOff);
+ DBPRINTF("%s\n", commandShell->ignErr);
+ DBPRINTF("%s\n", commandShell->echoOn);
+ } else {
+ DBPRINTF("%s\n", commandShell->ignErr);
+ }
+ } else if (commandShell->ignErr &&
+ (*commandShell->ignErr != '\0'))
+ {
+ /*
+ * The shell has no error control, so we need to be
+ * weird to get it to ignore any errors from the command.
+ * If echoing is turned on, we turn it off and use the
+ * errCheck template to echo the command. Leave echoing
+ * off so the user doesn't see the weirdness we go through
+ * to ignore errors. Set cmdTemplate to use the weirdness
+ * instead of the simple "%s\n" template.
+ */
+ job->flags |= JOB_IGNERR;
+ if (!(job->flags & JOB_SILENT) && !shutUp) {
+ if (commandShell->hasEchoCtl) {
+ DBPRINTF("%s\n", commandShell->echoOff);
+ }
+ DBPRINTF(commandShell->errCheck, escCmd);
+ shutUp = TRUE;
+ } else {
+ if (!shutUp) {
+ DBPRINTF(commandShell->errCheck, escCmd);
+ }
+ }
+ cmdTemplate = commandShell->ignErr;
+ /*
+ * The error ignoration (hee hee) is already taken care
+ * of by the ignErr template, so pretend error checking
+ * is still on.
+ */
+ errOff = FALSE;
+ } else {
+ errOff = FALSE;
+ }
+ } else {
+ errOff = FALSE;
+ }
+ } else {
+
+ /*
+ * If errors are being checked and the shell doesn't have error control
+ * but does supply an errOut template, then setup commands to run
+ * through it.
+ */
+
+ if (!commandShell->hasErrCtl && commandShell->errOut &&
+ (*commandShell->errOut != '\0')) {
+ if (!(job->flags & JOB_SILENT) && !shutUp) {
+ if (commandShell->hasEchoCtl) {
+ DBPRINTF("%s\n", commandShell->echoOff);
+ }
+ DBPRINTF(commandShell->errCheck, escCmd);
+ shutUp = TRUE;
+ }
+ /* If it's a comment line or blank, treat as an ignored error */
+ if ((escCmd[0] == commandShell->commentChar) ||
+ (escCmd[0] == 0))
+ cmdTemplate = commandShell->ignErr;
+ else
+ cmdTemplate = commandShell->errOut;
+ errOff = FALSE;
+ }
+ }
+
+ if (DEBUG(SHELL) && strcmp(shellName, "sh") == 0 &&
+ (job->flags & JOB_TRACED) == 0) {
+ DBPRINTF("set -%s\n", "x");
+ job->flags |= JOB_TRACED;
+ }
+
+ DBPRINTF(cmdTemplate, cmd);
+ free(cmdStart);
+ free(escCmd);
+ if (errOff) {
+ /*
+ * If echoing is already off, there's no point in issuing the
+ * echoOff command. Otherwise we issue it and pretend it was on
+ * for the whole command...
+ */
+ if (!shutUp && !(job->flags & JOB_SILENT) && commandShell->hasEchoCtl){
+ DBPRINTF("%s\n", commandShell->echoOff);
+ shutUp = TRUE;
+ }
+ DBPRINTF("%s\n", commandShell->errCheck);
+ }
+ if (shutUp && commandShell->hasEchoCtl) {
+ DBPRINTF("%s\n", commandShell->echoOn);
+ }
+ return 0;
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * JobSaveCommand --
+ * Save a command to be executed when everything else is done.
+ * Callback function for JobFinish...
+ *
+ * Results:
+ * Always returns 0
+ *
+ * Side Effects:
+ * The command is tacked onto the end of postCommands's commands list.
+ *
+ *-----------------------------------------------------------------------
+ */
+static int
+JobSaveCommand(void *cmd, void *gn)
+{
+ cmd = Var_Subst(NULL, (char *)cmd, (GNode *)gn, VARF_WANTRES);
+ (void)Lst_AtEnd(postCommands->commands, cmd);
+ return(0);
+}
+
+
+/*-
+ *-----------------------------------------------------------------------
+ * JobClose --
+ * Called to close both input and output pipes when a job is finished.
+ *
+ * Results:
+ * Nada
+ *
+ * Side Effects:
+ * The file descriptors associated with the job are closed.
+ *
+ *-----------------------------------------------------------------------
+ */
+static void
+JobClose(Job *job)
+{
+ clearfd(job);
+ (void)close(job->outPipe);
+ job->outPipe = -1;
+
+ JobDoOutput(job, TRUE);
+ (void)close(job->inPipe);
+ job->inPipe = -1;
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * JobFinish --
+ * Do final processing for the given job including updating
+ * parents and starting new jobs as available/necessary. Note
+ * that we pay no attention to the JOB_IGNERR flag here.
+ * This is because when we're called because of a noexecute flag
+ * or something, jstat.w_status is 0 and when called from
+ * Job_CatchChildren, the status is zeroed if it s/b ignored.
+ *
+ * Input:
+ * job job to finish
+ * status sub-why job went away
+ *
+ * Results:
+ * None
+ *
+ * Side Effects:
+ * Final commands for the job are placed on postCommands.
+ *
+ * If we got an error and are aborting (aborting == ABORT_ERROR) and
+ * the job list is now empty, we are done for the day.
+ * If we recognized an error (errors !=0), we set the aborting flag
+ * to ABORT_ERROR so no more jobs will be started.
+ *-----------------------------------------------------------------------
+ */
+/*ARGSUSED*/
+static void
+JobFinish(Job *job, int status)
+{
+ Boolean done, return_job_token;
+
+ if (DEBUG(JOB)) {
+ fprintf(debug_file, "Jobfinish: %d [%s], status %d\n",
+ job->pid, job->node->name, status);
+ }
+
+ if ((WIFEXITED(status) &&
+ (((WEXITSTATUS(status) != 0) && !(job->flags & JOB_IGNERR)))) ||
+ WIFSIGNALED(status))
+ {
+ /*
+ * If it exited non-zero and either we're doing things our
+ * way or we're not ignoring errors, the job is finished.
+ * Similarly, if the shell died because of a signal
+ * the job is also finished. In these
+ * cases, finish out the job's output before printing the exit
+ * status...
+ */
+ JobClose(job);
+ if (job->cmdFILE != NULL && job->cmdFILE != stdout) {
+ (void)fclose(job->cmdFILE);
+ job->cmdFILE = NULL;
+ }
+ done = TRUE;
+ } else if (WIFEXITED(status)) {
+ /*
+ * Deal with ignored errors in -B mode. We need to print a message
+ * telling of the ignored error as well as setting status.w_status
+ * to 0 so the next command gets run. To do this, we set done to be
+ * TRUE if in -B mode and the job exited non-zero.
+ */
+ done = WEXITSTATUS(status) != 0;
+ /*
+ * Old comment said: "Note we don't
+ * want to close down any of the streams until we know we're at the
+ * end."
+ * But we do. Otherwise when are we going to print the rest of the
+ * stuff?
+ */
+ JobClose(job);
+ } else {
+ /*
+ * No need to close things down or anything.
+ */
+ done = FALSE;
+ }
+
+ if (done) {
+ if (WIFEXITED(status)) {
+ if (DEBUG(JOB)) {
+ (void)fprintf(debug_file, "Process %d [%s] exited.\n",
+ job->pid, job->node->name);
+ }
+ if (WEXITSTATUS(status) != 0) {
+ if (job->node != lastNode) {
+ MESSAGE(stdout, job->node);
+ lastNode = job->node;
+ }
+#ifdef USE_META
+ if (useMeta) {
+ meta_job_error(job, job->node, job->flags, WEXITSTATUS(status));
+ }
+#endif
+ (void)printf("*** [%s] Error code %d%s\n",
+ job->node->name,
+ WEXITSTATUS(status),
+ (job->flags & JOB_IGNERR) ? " (ignored)" : "");
+ if (job->flags & JOB_IGNERR) {
+ status = 0;
+ } else {
+ if (deleteOnError) {
+ JobDeleteTarget(job->node);
+ }
+ PrintOnError(job->node, NULL);
+ }
+ } else if (DEBUG(JOB)) {
+ if (job->node != lastNode) {
+ MESSAGE(stdout, job->node);
+ lastNode = job->node;
+ }
+ (void)printf("*** [%s] Completed successfully\n",
+ job->node->name);
+ }
+ } else {
+ if (job->node != lastNode) {
+ MESSAGE(stdout, job->node);
+ lastNode = job->node;
+ }
+ (void)printf("*** [%s] Signal %d\n",
+ job->node->name, WTERMSIG(status));
+ if (deleteOnError) {
+ JobDeleteTarget(job->node);
+ }
+ }
+ (void)fflush(stdout);
+ }
+
+#ifdef USE_META
+ if (useMeta) {
+ int x;
+
+ if ((x = meta_job_finish(job)) != 0 && status == 0) {
+ status = x;
+ }
+ }
+#endif
+
+ return_job_token = FALSE;
+
+ Trace_Log(JOBEND, job);
+ if (!(job->flags & JOB_SPECIAL)) {
+ if ((status != 0) ||
+ (aborting == ABORT_ERROR) ||
+ (aborting == ABORT_INTERRUPT))
+ return_job_token = TRUE;
+ }
+
+ if ((aborting != ABORT_ERROR) && (aborting != ABORT_INTERRUPT) && (status == 0)) {
+ /*
+ * As long as we aren't aborting and the job didn't return a non-zero
+ * status that we shouldn't ignore, we call Make_Update to update
+ * the parents. In addition, any saved commands for the node are placed
+ * on the .END target.
+ */
+ if (job->tailCmds != NULL) {
+ Lst_ForEachFrom(job->node->commands, job->tailCmds,
+ JobSaveCommand,
+ job->node);
+ }
+ job->node->made = MADE;
+ if (!(job->flags & JOB_SPECIAL))
+ return_job_token = TRUE;
+ Make_Update(job->node);
+ job->job_state = JOB_ST_FREE;
+ } else if (status != 0) {
+ errors += 1;
+ job->job_state = JOB_ST_FREE;
+ }
+
+ /*
+ * Set aborting if any error.
+ */
+ if (errors && !keepgoing && (aborting != ABORT_INTERRUPT)) {
+ /*
+ * If we found any errors in this batch of children and the -k flag
+ * wasn't given, we set the aborting flag so no more jobs get
+ * started.
+ */
+ aborting = ABORT_ERROR;
+ }
+
+ if (return_job_token)
+ Job_TokenReturn();
+
+ if (aborting == ABORT_ERROR && jobTokensRunning == 0) {
+ /*
+ * If we are aborting and the job table is now empty, we finish.
+ */
+ Finish(errors);
+ }
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * Job_Touch --
+ * Touch the given target. Called by JobStart when the -t flag was
+ * given
+ *
+ * Input:
+ * gn the node of the file to touch
+ * silent TRUE if should not print message
+ *
+ * Results:
+ * None
+ *
+ * Side Effects:
+ * The data modification of the file is changed. In addition, if the
+ * file did not exist, it is created.
+ *-----------------------------------------------------------------------
+ */
+void
+Job_Touch(GNode *gn, Boolean silent)
+{
+ int streamID; /* ID of stream opened to do the touch */
+ struct utimbuf times; /* Times for utime() call */
+
+ if (gn->type & (OP_JOIN|OP_USE|OP_USEBEFORE|OP_EXEC|OP_OPTIONAL|
+ OP_SPECIAL|OP_PHONY)) {
+ /*
+ * .JOIN, .USE, .ZEROTIME and .OPTIONAL targets are "virtual" targets
+ * and, as such, shouldn't really be created.
+ */
+ return;
+ }
+
+ if (!silent || NoExecute(gn)) {
+ (void)fprintf(stdout, "touch %s\n", gn->name);
+ (void)fflush(stdout);
+ }
+
+ if (NoExecute(gn)) {
+ return;
+ }
+
+ if (gn->type & OP_ARCHV) {
+ Arch_Touch(gn);
+ } else if (gn->type & OP_LIB) {
+ Arch_TouchLib(gn);
+ } else {
+ char *file = gn->path ? gn->path : gn->name;
+
+ times.actime = times.modtime = now;
+ if (utime(file, &times) < 0){
+ streamID = open(file, O_RDWR | O_CREAT, 0666);
+
+ if (streamID >= 0) {
+ char c;
+
+ /*
+ * Read and write a byte to the file to change the
+ * modification time, then close the file.
+ */
+ if (read(streamID, &c, 1) == 1) {
+ (void)lseek(streamID, (off_t)0, SEEK_SET);
+ while (write(streamID, &c, 1) == -1 && errno == EAGAIN)
+ continue;
+ }
+
+ (void)close(streamID);
+ } else {
+ (void)fprintf(stdout, "*** couldn't touch %s: %s",
+ file, strerror(errno));
+ (void)fflush(stdout);
+ }
+ }
+ }
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * Job_CheckCommands --
+ * Make sure the given node has all the commands it needs.
+ *
+ * Input:
+ * gn The target whose commands need verifying
+ * abortProc Function to abort with message
+ *
+ * Results:
+ * TRUE if the commands list is/was ok.
+ *
+ * Side Effects:
+ * The node will have commands from the .DEFAULT rule added to it
+ * if it needs them.
+ *-----------------------------------------------------------------------
+ */
+Boolean
+Job_CheckCommands(GNode *gn, void (*abortProc)(const char *, ...))
+{
+ if (OP_NOP(gn->type) && Lst_IsEmpty(gn->commands) &&
+ ((gn->type & OP_LIB) == 0 || Lst_IsEmpty(gn->children))) {
+ /*
+ * No commands. Look for .DEFAULT rule from which we might infer
+ * commands
+ */
+ if ((DEFAULT != NULL) && !Lst_IsEmpty(DEFAULT->commands) &&
+ (gn->type & OP_SPECIAL) == 0) {
+ char *p1;
+ /*
+ * Make only looks for a .DEFAULT if the node was never the
+ * target of an operator, so that's what we do too. If
+ * a .DEFAULT was given, we substitute its commands for gn's
+ * commands and set the IMPSRC variable to be the target's name
+ * The DEFAULT node acts like a transformation rule, in that
+ * gn also inherits any attributes or sources attached to
+ * .DEFAULT itself.
+ */
+ Make_HandleUse(DEFAULT, gn);
+ Var_Set(IMPSRC, Var_Value(TARGET, gn, &p1), gn, 0);
+ free(p1);
+ } else if (Dir_MTime(gn, 0) == 0 && (gn->type & OP_SPECIAL) == 0) {
+ /*
+ * The node wasn't the target of an operator we have no .DEFAULT
+ * rule to go on and the target doesn't already exist. There's
+ * nothing more we can do for this branch. If the -k flag wasn't
+ * given, we stop in our tracks, otherwise we just don't update
+ * this node's parents so they never get examined.
+ */
+ static const char msg[] = ": don't know how to make";
+
+ if (gn->flags & FROM_DEPEND) {
+ if (!Job_RunTarget(".STALE", gn->fname))
+ fprintf(stdout, "%s: %s, %d: ignoring stale %s for %s\n",
+ progname, gn->fname, gn->lineno, makeDependfile,
+ gn->name);
+ return TRUE;
+ }
+
+ if (gn->type & OP_OPTIONAL) {
+ (void)fprintf(stdout, "%s%s %s (ignored)\n", progname,
+ msg, gn->name);
+ (void)fflush(stdout);
+ } else if (keepgoing) {
+ (void)fprintf(stdout, "%s%s %s (continuing)\n", progname,
+ msg, gn->name);
+ (void)fflush(stdout);
+ return FALSE;
+ } else {
+ (*abortProc)("%s%s %s. Stop", progname, msg, gn->name);
+ return FALSE;
+ }
+ }
+ }
+ return TRUE;
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * JobExec --
+ * Execute the shell for the given job. Called from JobStart
+ *
+ * Input:
+ * job Job to execute
+ *
+ * Results:
+ * None.
+ *
+ * Side Effects:
+ * A shell is executed, outputs is altered and the Job structure added
+ * to the job table.
+ *
+ *-----------------------------------------------------------------------
+ */
+static void
+JobExec(Job *job, char **argv)
+{
+ int cpid; /* ID of new child */
+ sigset_t mask;
+
+ job->flags &= ~JOB_TRACED;
+
+ if (DEBUG(JOB)) {
+ int i;
+
+ (void)fprintf(debug_file, "Running %s %sly\n", job->node->name, "local");
+ (void)fprintf(debug_file, "\tCommand: ");
+ for (i = 0; argv[i] != NULL; i++) {
+ (void)fprintf(debug_file, "%s ", argv[i]);
+ }
+ (void)fprintf(debug_file, "\n");
+ }
+
+ /*
+ * Some jobs produce no output and it's disconcerting to have
+ * no feedback of their running (since they produce no output, the
+ * banner with their name in it never appears). This is an attempt to
+ * provide that feedback, even if nothing follows it.
+ */
+ if ((lastNode != job->node) && !(job->flags & JOB_SILENT)) {
+ MESSAGE(stdout, job->node);
+ lastNode = job->node;
+ }
+
+ /* No interruptions until this job is on the `jobs' list */
+ JobSigLock(&mask);
+
+ /* Pre-emptively mark job running, pid still zero though */
+ job->job_state = JOB_ST_RUNNING;
+
+ cpid = vFork();
+ if (cpid == -1)
+ Punt("Cannot vfork: %s", strerror(errno));
+
+ if (cpid == 0) {
+ /* Child */
+ sigset_t tmask;
+
+#ifdef USE_META
+ if (useMeta) {
+ meta_job_child(job);
+ }
+#endif
+ /*
+ * Reset all signal handlers; this is necessary because we also
+ * need to unblock signals before we exec(2).
+ */
+ JobSigReset();
+
+ /* Now unblock signals */
+ sigemptyset(&tmask);
+ JobSigUnlock(&tmask);
+
+ /*
+ * Must duplicate the input stream down to the child's input and
+ * reset it to the beginning (again). Since the stream was marked
+ * close-on-exec, we must clear that bit in the new input.
+ */
+ if (dup2(FILENO(job->cmdFILE), 0) == -1) {
+ execError("dup2", "job->cmdFILE");
+ _exit(1);
+ }
+ if (fcntl(0, F_SETFD, 0) == -1) {
+ execError("fcntl clear close-on-exec", "stdin");
+ _exit(1);
+ }
+ if (lseek(0, (off_t)0, SEEK_SET) == -1) {
+ execError("lseek to 0", "stdin");
+ _exit(1);
+ }
+
+ if (job->node->type & (OP_MAKE | OP_SUBMAKE)) {
+ /*
+ * Pass job token pipe to submakes.
+ */
+ if (fcntl(tokenWaitJob.inPipe, F_SETFD, 0) == -1) {
+ execError("clear close-on-exec", "tokenWaitJob.inPipe");
+ _exit(1);
+ }
+ if (fcntl(tokenWaitJob.outPipe, F_SETFD, 0) == -1) {
+ execError("clear close-on-exec", "tokenWaitJob.outPipe");
+ _exit(1);
+ }
+ }
+
+ /*
+ * Set up the child's output to be routed through the pipe
+ * we've created for it.
+ */
+ if (dup2(job->outPipe, 1) == -1) {
+ execError("dup2", "job->outPipe");
+ _exit(1);
+ }
+ /*
+ * The output channels are marked close on exec. This bit was
+ * duplicated by the dup2(on some systems), so we have to clear
+ * it before routing the shell's error output to the same place as
+ * its standard output.
+ */
+ if (fcntl(1, F_SETFD, 0) == -1) {
+ execError("clear close-on-exec", "stdout");
+ _exit(1);
+ }
+ if (dup2(1, 2) == -1) {
+ execError("dup2", "1, 2");
+ _exit(1);
+ }
+
+ /*
+ * We want to switch the child into a different process family so
+ * we can kill it and all its descendants in one fell swoop,
+ * by killing its process family, but not commit suicide.
+ */
+#if defined(MAKE_NATIVE) || defined(HAVE_SETPGID)
+#if defined(SYSV)
+ /* XXX: dsl - I'm sure this should be setpgrp()... */
+ (void)setsid();
+#else
+ (void)setpgid(0, getpid());
+#endif
+#endif
+
+ Var_ExportVars();
+
+ (void)execv(shellPath, argv);
+ execError("exec", shellPath);
+ _exit(1);
+ }
+
+ /* Parent, continuing after the child exec */
+ job->pid = cpid;
+
+ Trace_Log(JOBSTART, job);
+
+ /*
+ * Set the current position in the buffer to the beginning
+ * and mark another stream to watch in the outputs mask
+ */
+ job->curPos = 0;
+
+ watchfd(job);
+
+ if (job->cmdFILE != NULL && job->cmdFILE != stdout) {
+ (void)fclose(job->cmdFILE);
+ job->cmdFILE = NULL;
+ }
+
+ /*
+ * Now the job is actually running, add it to the table.
+ */
+ if (DEBUG(JOB)) {
+ fprintf(debug_file, "JobExec(%s): pid %d added to jobs table\n",
+ job->node->name, job->pid);
+ job_table_dump("job started");
+ }
+ JobSigUnlock(&mask);
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * JobMakeArgv --
+ * Create the argv needed to execute the shell for a given job.
+ *
+ *
+ * Results:
+ *
+ * Side Effects:
+ *
+ *-----------------------------------------------------------------------
+ */
+static void
+JobMakeArgv(Job *job, char **argv)
+{
+ int argc;
+ static char args[10]; /* For merged arguments */
+
+ argv[0] = UNCONST(shellName);
+ argc = 1;
+
+ if ((commandShell->exit && (*commandShell->exit != '-')) ||
+ (commandShell->echo && (*commandShell->echo != '-')))
+ {
+ /*
+ * At least one of the flags doesn't have a minus before it, so
+ * merge them together. Have to do this because the *(&(@*#*&#$#
+ * Bourne shell thinks its second argument is a file to source.
+ * Grrrr. Note the ten-character limitation on the combined arguments.
+ */
+ (void)snprintf(args, sizeof(args), "-%s%s",
+ ((job->flags & JOB_IGNERR) ? "" :
+ (commandShell->exit ? commandShell->exit : "")),
+ ((job->flags & JOB_SILENT) ? "" :
+ (commandShell->echo ? commandShell->echo : "")));
+
+ if (args[1]) {
+ argv[argc] = args;
+ argc++;
+ }
+ } else {
+ if (!(job->flags & JOB_IGNERR) && commandShell->exit) {
+ argv[argc] = UNCONST(commandShell->exit);
+ argc++;
+ }
+ if (!(job->flags & JOB_SILENT) && commandShell->echo) {
+ argv[argc] = UNCONST(commandShell->echo);
+ argc++;
+ }
+ }
+ argv[argc] = NULL;
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * JobStart --
+ * Start a target-creation process going for the target described
+ * by the graph node gn.
+ *
+ * Input:
+ * gn target to create
+ * flags flags for the job to override normal ones.
+ * e.g. JOB_SPECIAL or JOB_IGNDOTS
+ * previous The previous Job structure for this node, if any.
+ *
+ * Results:
+ * JOB_ERROR if there was an error in the commands, JOB_FINISHED
+ * if there isn't actually anything left to do for the job and
+ * JOB_RUNNING if the job has been started.
+ *
+ * Side Effects:
+ * A new Job node is created and added to the list of running
+ * jobs. PMake is forked and a child shell created.
+ *
+ * NB: I'm fairly sure that this code is never called with JOB_SPECIAL set
+ * JOB_IGNDOTS is never set (dsl)
+ * Also the return value is ignored by everyone.
+ *-----------------------------------------------------------------------
+ */
+static int
+JobStart(GNode *gn, int flags)
+{
+ Job *job; /* new job descriptor */
+ char *argv[10]; /* Argument vector to shell */
+ Boolean cmdsOK; /* true if the nodes commands were all right */
+ Boolean noExec; /* Set true if we decide not to run the job */
+ int tfd; /* File descriptor to the temp file */
+
+ for (job = job_table; job < job_table_end; job++) {
+ if (job->job_state == JOB_ST_FREE)
+ break;
+ }
+ if (job >= job_table_end)
+ Punt("JobStart no job slots vacant");
+
+ memset(job, 0, sizeof *job);
+ job->job_state = JOB_ST_SETUP;
+ if (gn->type & OP_SPECIAL)
+ flags |= JOB_SPECIAL;
+
+ job->node = gn;
+ job->tailCmds = NULL;
+
+ /*
+ * Set the initial value of the flags for this job based on the global
+ * ones and the node's attributes... Any flags supplied by the caller
+ * are also added to the field.
+ */
+ job->flags = 0;
+ if (Targ_Ignore(gn)) {
+ job->flags |= JOB_IGNERR;
+ }
+ if (Targ_Silent(gn)) {
+ job->flags |= JOB_SILENT;
+ }
+ job->flags |= flags;
+
+ /*
+ * Check the commands now so any attributes from .DEFAULT have a chance
+ * to migrate to the node
+ */
+ cmdsOK = Job_CheckCommands(gn, Error);
+
+ job->inPollfd = NULL;
+ /*
+ * If the -n flag wasn't given, we open up OUR (not the child's)
+ * temporary file to stuff commands in it. The thing is rd/wr so we don't
+ * need to reopen it to feed it to the shell. If the -n flag *was* given,
+ * we just set the file to be stdout. Cute, huh?
+ */
+ if (((gn->type & OP_MAKE) && !(noRecursiveExecute)) ||
+ (!noExecute && !touchFlag)) {
+ /*
+ * tfile is the name of a file into which all shell commands are
+ * put. It is removed before the child shell is executed, unless
+ * DEBUG(SCRIPT) is set.
+ */
+ char *tfile;
+ sigset_t mask;
+ /*
+ * We're serious here, but if the commands were bogus, we're
+ * also dead...
+ */
+ if (!cmdsOK) {
+ PrintOnError(gn, NULL); /* provide some clue */
+ DieHorribly();
+ }
+
+ JobSigLock(&mask);
+ tfd = mkTempFile(TMPPAT, &tfile);
+ if (!DEBUG(SCRIPT))
+ (void)eunlink(tfile);
+ JobSigUnlock(&mask);
+
+ job->cmdFILE = fdopen(tfd, "w+");
+ if (job->cmdFILE == NULL) {
+ Punt("Could not fdopen %s", tfile);
+ }
+ (void)fcntl(FILENO(job->cmdFILE), F_SETFD, FD_CLOEXEC);
+ /*
+ * Send the commands to the command file, flush all its buffers then
+ * rewind and remove the thing.
+ */
+ noExec = FALSE;
+
+#ifdef USE_META
+ if (useMeta) {
+ meta_job_start(job, gn);
+ if (Targ_Silent(gn)) { /* might have changed */
+ job->flags |= JOB_SILENT;
+ }
+ }
+#endif
+ /*
+ * We can do all the commands at once. hooray for sanity
+ */
+ numCommands = 0;
+ Lst_ForEach(gn->commands, JobPrintCommand, job);
+
+ /*
+ * If we didn't print out any commands to the shell script,
+ * there's not much point in executing the shell, is there?
+ */
+ if (numCommands == 0) {
+ noExec = TRUE;
+ }
+
+ free(tfile);
+ } else if (NoExecute(gn)) {
+ /*
+ * Not executing anything -- just print all the commands to stdout
+ * in one fell swoop. This will still set up job->tailCmds correctly.
+ */
+ if (lastNode != gn) {
+ MESSAGE(stdout, gn);
+ lastNode = gn;
+ }
+ job->cmdFILE = stdout;
+ /*
+ * Only print the commands if they're ok, but don't die if they're
+ * not -- just let the user know they're bad and keep going. It
+ * doesn't do any harm in this case and may do some good.
+ */
+ if (cmdsOK) {
+ Lst_ForEach(gn->commands, JobPrintCommand, job);
+ }
+ /*
+ * Don't execute the shell, thank you.
+ */
+ noExec = TRUE;
+ } else {
+ /*
+ * Just touch the target and note that no shell should be executed.
+ * Set cmdFILE to stdout to make life easier. Check the commands, too,
+ * but don't die if they're no good -- it does no harm to keep working
+ * up the graph.
+ */
+ job->cmdFILE = stdout;
+ Job_Touch(gn, job->flags&JOB_SILENT);
+ noExec = TRUE;
+ }
+ /* Just in case it isn't already... */
+ (void)fflush(job->cmdFILE);
+
+ /*
+ * If we're not supposed to execute a shell, don't.
+ */
+ if (noExec) {
+ if (!(job->flags & JOB_SPECIAL))
+ Job_TokenReturn();
+ /*
+ * Unlink and close the command file if we opened one
+ */
+ if (job->cmdFILE != stdout) {
+ if (job->cmdFILE != NULL) {
+ (void)fclose(job->cmdFILE);
+ job->cmdFILE = NULL;
+ }
+ }
+
+ /*
+ * We only want to work our way up the graph if we aren't here because
+ * the commands for the job were no good.
+ */
+ if (cmdsOK && aborting == 0) {
+ if (job->tailCmds != NULL) {
+ Lst_ForEachFrom(job->node->commands, job->tailCmds,
+ JobSaveCommand,
+ job->node);
+ }
+ job->node->made = MADE;
+ Make_Update(job->node);
+ }
+ job->job_state = JOB_ST_FREE;
+ return cmdsOK ? JOB_FINISHED : JOB_ERROR;
+ }
+
+ /*
+ * Set up the control arguments to the shell. This is based on the flags
+ * set earlier for this job.
+ */
+ JobMakeArgv(job, argv);
+
+ /* Create the pipe by which we'll get the shell's output. */
+ JobCreatePipe(job, 3);
+
+ JobExec(job, argv);
+ return(JOB_RUNNING);
+}
+
+static char *
+JobOutput(Job *job, char *cp, char *endp, int msg)
+{
+ char *ecp;
+
+ if (commandShell->noPrint) {
+ ecp = Str_FindSubstring(cp, commandShell->noPrint);
+ while (ecp != NULL) {
+ if (cp != ecp) {
+ *ecp = '\0';
+ if (!beSilent && msg && job->node != lastNode) {
+ MESSAGE(stdout, job->node);
+ lastNode = job->node;
+ }
+ /*
+ * The only way there wouldn't be a newline after
+ * this line is if it were the last in the buffer.
+ * however, since the non-printable comes after it,
+ * there must be a newline, so we don't print one.
+ */
+ (void)fprintf(stdout, "%s", cp);
+ (void)fflush(stdout);
+ }
+ cp = ecp + commandShell->noPLen;
+ if (cp != endp) {
+ /*
+ * Still more to print, look again after skipping
+ * the whitespace following the non-printable
+ * command....
+ */
+ cp++;
+ while (*cp == ' ' || *cp == '\t' || *cp == '\n') {
+ cp++;
+ }
+ ecp = Str_FindSubstring(cp, commandShell->noPrint);
+ } else {
+ return cp;
+ }
+ }
+ }
+ return cp;
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * JobDoOutput --
+ * This function is called at different times depending on
+ * whether the user has specified that output is to be collected
+ * via pipes or temporary files. In the former case, we are called
+ * whenever there is something to read on the pipe. We collect more
+ * output from the given job and store it in the job's outBuf. If
+ * this makes up a line, we print it tagged by the job's identifier,
+ * as necessary.
+ * If output has been collected in a temporary file, we open the
+ * file and read it line by line, transfering it to our own
+ * output channel until the file is empty. At which point we
+ * remove the temporary file.
+ * In both cases, however, we keep our figurative eye out for the
+ * 'noPrint' line for the shell from which the output came. If
+ * we recognize a line, we don't print it. If the command is not
+ * alone on the line (the character after it is not \0 or \n), we
+ * do print whatever follows it.
+ *
+ * Input:
+ * job the job whose output needs printing
+ * finish TRUE if this is the last time we'll be called
+ * for this job
+ *
+ * Results:
+ * None
+ *
+ * Side Effects:
+ * curPos may be shifted as may the contents of outBuf.
+ *-----------------------------------------------------------------------
+ */
+STATIC void
+JobDoOutput(Job *job, Boolean finish)
+{
+ Boolean gotNL = FALSE; /* true if got a newline */
+ Boolean fbuf; /* true if our buffer filled up */
+ int nr; /* number of bytes read */
+ int i; /* auxiliary index into outBuf */
+ int max; /* limit for i (end of current data) */
+ int nRead; /* (Temporary) number of bytes read */
+
+ /*
+ * Read as many bytes as will fit in the buffer.
+ */
+end_loop:
+ gotNL = FALSE;
+ fbuf = FALSE;
+
+ nRead = read(job->inPipe, &job->outBuf[job->curPos],
+ JOB_BUFSIZE - job->curPos);
+ if (nRead < 0) {
+ if (errno == EAGAIN)
+ return;
+ if (DEBUG(JOB)) {
+ perror("JobDoOutput(piperead)");
+ }
+ nr = 0;
+ } else {
+ nr = nRead;
+ }
+
+ /*
+ * If we hit the end-of-file (the job is dead), we must flush its
+ * remaining output, so pretend we read a newline if there's any
+ * output remaining in the buffer.
+ * Also clear the 'finish' flag so we stop looping.
+ */
+ if ((nr == 0) && (job->curPos != 0)) {
+ job->outBuf[job->curPos] = '\n';
+ nr = 1;
+ finish = FALSE;
+ } else if (nr == 0) {
+ finish = FALSE;
+ }
+
+ /*
+ * Look for the last newline in the bytes we just got. If there is
+ * one, break out of the loop with 'i' as its index and gotNL set
+ * TRUE.
+ */
+ max = job->curPos + nr;
+ for (i = job->curPos + nr - 1; i >= job->curPos; i--) {
+ if (job->outBuf[i] == '\n') {
+ gotNL = TRUE;
+ break;
+ } else if (job->outBuf[i] == '\0') {
+ /*
+ * Why?
+ */
+ job->outBuf[i] = ' ';
+ }
+ }
+
+ if (!gotNL) {
+ job->curPos += nr;
+ if (job->curPos == JOB_BUFSIZE) {
+ /*
+ * If we've run out of buffer space, we have no choice
+ * but to print the stuff. sigh.
+ */
+ fbuf = TRUE;
+ i = job->curPos;
+ }
+ }
+ if (gotNL || fbuf) {
+ /*
+ * Need to send the output to the screen. Null terminate it
+ * first, overwriting the newline character if there was one.
+ * So long as the line isn't one we should filter (according
+ * to the shell description), we print the line, preceded
+ * by a target banner if this target isn't the same as the
+ * one for which we last printed something.
+ * The rest of the data in the buffer are then shifted down
+ * to the start of the buffer and curPos is set accordingly.
+ */
+ job->outBuf[i] = '\0';
+ if (i >= job->curPos) {
+ char *cp;
+
+ cp = JobOutput(job, job->outBuf, &job->outBuf[i], FALSE);
+
+ /*
+ * There's still more in that thar buffer. This time, though,
+ * we know there's no newline at the end, so we add one of
+ * our own free will.
+ */
+ if (*cp != '\0') {
+ if (!beSilent && job->node != lastNode) {
+ MESSAGE(stdout, job->node);
+ lastNode = job->node;
+ }
+#ifdef USE_META
+ if (useMeta) {
+ meta_job_output(job, cp, gotNL ? "\n" : "");
+ }
+#endif
+ (void)fprintf(stdout, "%s%s", cp, gotNL ? "\n" : "");
+ (void)fflush(stdout);
+ }
+ }
+ /*
+ * max is the last offset still in the buffer. Move any remaining
+ * characters to the start of the buffer and update the end marker
+ * curPos.
+ */
+ if (i < max) {
+ (void)memmove(job->outBuf, &job->outBuf[i + 1], max - (i + 1));
+ job->curPos = max - (i + 1);
+ } else {
+ assert(i == max);
+ job->curPos = 0;
+ }
+ }
+ if (finish) {
+ /*
+ * If the finish flag is true, we must loop until we hit
+ * end-of-file on the pipe. This is guaranteed to happen
+ * eventually since the other end of the pipe is now closed
+ * (we closed it explicitly and the child has exited). When
+ * we do get an EOF, finish will be set FALSE and we'll fall
+ * through and out.
+ */
+ goto end_loop;
+ }
+}
+
+static void
+JobRun(GNode *targ)
+{
+#ifdef notyet
+ /*
+ * Unfortunately it is too complicated to run .BEGIN, .END,
+ * and .INTERRUPT job in the parallel job module. This has
+ * the nice side effect that it avoids a lot of other problems.
+ */
+ Lst lst = Lst_Init(FALSE);
+ Lst_AtEnd(lst, targ);
+ (void)Make_Run(lst);
+ Lst_Destroy(lst, NULL);
+ JobStart(targ, JOB_SPECIAL);
+ while (jobTokensRunning) {
+ Job_CatchOutput();
+ }
+#else
+ Compat_Make(targ, targ);
+ if (targ->made == ERROR) {
+ PrintOnError(targ, "\n\nStop.");
+ exit(1);
+ }
+#endif
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * Job_CatchChildren --
+ * Handle the exit of a child. Called from Make_Make.
+ *
+ * Input:
+ * block TRUE if should block on the wait
+ *
+ * Results:
+ * none.
+ *
+ * Side Effects:
+ * The job descriptor is removed from the list of children.
+ *
+ * Notes:
+ * We do waits, blocking or not, according to the wisdom of our
+ * caller, until there are no more children to report. For each
+ * job, call JobFinish to finish things off.
+ *
+ *-----------------------------------------------------------------------
+ */
+
+void
+Job_CatchChildren(void)
+{
+ int pid; /* pid of dead child */
+ int status; /* Exit/termination status */
+
+ /*
+ * Don't even bother if we know there's no one around.
+ */
+ if (jobTokensRunning == 0)
+ return;
+
+ while ((pid = waitpid((pid_t) -1, &status, WNOHANG | WUNTRACED)) > 0) {
+ if (DEBUG(JOB)) {
+ (void)fprintf(debug_file, "Process %d exited/stopped status %x.\n", pid,
+ status);
+ }
+ JobReapChild(pid, status, TRUE);
+ }
+}
+
+/*
+ * It is possible that wait[pid]() was called from elsewhere,
+ * this lets us reap jobs regardless.
+ */
+void
+JobReapChild(pid_t pid, int status, Boolean isJobs)
+{
+ Job *job; /* job descriptor for dead child */
+
+ /*
+ * Don't even bother if we know there's no one around.
+ */
+ if (jobTokensRunning == 0)
+ return;
+
+ job = JobFindPid(pid, JOB_ST_RUNNING, isJobs);
+ if (job == NULL) {
+ if (isJobs) {
+ if (!lurking_children)
+ Error("Child (%d) status %x not in table?", pid, status);
+ }
+ return; /* not ours */
+ }
+ if (WIFSTOPPED(status)) {
+ if (DEBUG(JOB)) {
+ (void)fprintf(debug_file, "Process %d (%s) stopped.\n",
+ job->pid, job->node->name);
+ }
+ if (!make_suspended) {
+ switch (WSTOPSIG(status)) {
+ case SIGTSTP:
+ (void)printf("*** [%s] Suspended\n", job->node->name);
+ break;
+ case SIGSTOP:
+ (void)printf("*** [%s] Stopped\n", job->node->name);
+ break;
+ default:
+ (void)printf("*** [%s] Stopped -- signal %d\n",
+ job->node->name, WSTOPSIG(status));
+ }
+ job->job_suspended = 1;
+ }
+ (void)fflush(stdout);
+ return;
+ }
+
+ job->job_state = JOB_ST_FINISHED;
+ job->exit_status = status;
+
+ JobFinish(job, status);
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * Job_CatchOutput --
+ * Catch the output from our children, if we're using
+ * pipes do so. Otherwise just block time until we get a
+ * signal(most likely a SIGCHLD) since there's no point in
+ * just spinning when there's nothing to do and the reaping
+ * of a child can wait for a while.
+ *
+ * Results:
+ * None
+ *
+ * Side Effects:
+ * Output is read from pipes if we're piping.
+ * -----------------------------------------------------------------------
+ */
+void
+Job_CatchOutput(void)
+{
+ int nready;
+ Job *job;
+ int i;
+
+ (void)fflush(stdout);
+
+ /* The first fd in the list is the job token pipe */
+ do {
+ nready = poll(fds + 1 - wantToken, nfds - 1 + wantToken, POLL_MSEC);
+ } while (nready < 0 && errno == EINTR);
+
+ if (nready < 0)
+ Punt("poll: %s", strerror(errno));
+
+ if (nready > 0 && readyfd(&childExitJob)) {
+ char token = 0;
+ ssize_t count;
+ count = read(childExitJob.inPipe, &token, 1);
+ switch (count) {
+ case 0:
+ Punt("unexpected eof on token pipe");
+ case -1:
+ Punt("token pipe read: %s", strerror(errno));
+ case 1:
+ if (token == DO_JOB_RESUME[0])
+ /* Complete relay requested from our SIGCONT handler */
+ JobRestartJobs();
+ break;
+ default:
+ abort();
+ }
+ --nready;
+ }
+
+ Job_CatchChildren();
+ if (nready == 0)
+ return;
+
+ for (i = 2; i < nfds; i++) {
+ if (!fds[i].revents)
+ continue;
+ job = jobfds[i];
+ if (job->job_state == JOB_ST_RUNNING)
+ JobDoOutput(job, FALSE);
+ if (--nready == 0)
+ return;
+ }
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * Job_Make --
+ * Start the creation of a target. Basically a front-end for
+ * JobStart used by the Make module.
+ *
+ * Results:
+ * None.
+ *
+ * Side Effects:
+ * Another job is started.
+ *
+ *-----------------------------------------------------------------------
+ */
+void
+Job_Make(GNode *gn)
+{
+ (void)JobStart(gn, 0);
+}
+
+void
+Shell_Init(void)
+{
+ if (shellPath == NULL) {
+ /*
+ * We are using the default shell, which may be an absolute
+ * path if DEFSHELL_CUSTOM is defined.
+ */
+ shellName = commandShell->name;
+#ifdef DEFSHELL_CUSTOM
+ if (*shellName == '/') {
+ shellPath = shellName;
+ shellName = strrchr(shellPath, '/');
+ shellName++;
+ } else
+#endif
+ shellPath = str_concat(_PATH_DEFSHELLDIR, shellName, STR_ADDSLASH);
+ }
+ if (commandShell->exit == NULL) {
+ commandShell->exit = "";
+ }
+ if (commandShell->echo == NULL) {
+ commandShell->echo = "";
+ }
+ if (commandShell->hasErrCtl && *commandShell->exit) {
+ if (shellErrFlag &&
+ strcmp(commandShell->exit, &shellErrFlag[1]) != 0) {
+ free(shellErrFlag);
+ shellErrFlag = NULL;
+ }
+ if (!shellErrFlag) {
+ int n = strlen(commandShell->exit) + 2;
+
+ shellErrFlag = bmake_malloc(n);
+ if (shellErrFlag) {
+ snprintf(shellErrFlag, n, "-%s", commandShell->exit);
+ }
+ }
+ } else if (shellErrFlag) {
+ free(shellErrFlag);
+ shellErrFlag = NULL;
+ }
+}
+
+/*-
+ * Returns the string literal that is used in the current command shell
+ * to produce a newline character.
+ */
+const char *
+Shell_GetNewline(void)
+{
+
+ return commandShell->newline;
+}
+
+void
+Job_SetPrefix(void)
+{
+
+ if (targPrefix) {
+ free(targPrefix);
+ } else if (!Var_Exists(MAKE_JOB_PREFIX, VAR_GLOBAL)) {
+ Var_Set(MAKE_JOB_PREFIX, "---", VAR_GLOBAL, 0);
+ }
+
+ targPrefix = Var_Subst(NULL, "${" MAKE_JOB_PREFIX "}",
+ VAR_GLOBAL, VARF_WANTRES);
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * Job_Init --
+ * Initialize the process module
+ *
+ * Input:
+ *
+ * Results:
+ * none
+ *
+ * Side Effects:
+ * lists and counters are initialized
+ *-----------------------------------------------------------------------
+ */
+void
+Job_Init(void)
+{
+ Job_SetPrefix();
+ /* Allocate space for all the job info */
+ job_table = bmake_malloc(maxJobs * sizeof *job_table);
+ memset(job_table, 0, maxJobs * sizeof *job_table);
+ job_table_end = job_table + maxJobs;
+ wantToken = 0;
+
+ aborting = 0;
+ errors = 0;
+
+ lastNode = NULL;
+
+ /*
+ * There is a non-zero chance that we already have children.
+ * eg after 'make -f- <<EOF'
+ * Since their termination causes a 'Child (pid) not in table' message,
+ * Collect the status of any that are already dead, and suppress the
+ * error message if there are any undead ones.
+ */
+ for (;;) {
+ int rval, status;
+ rval = waitpid((pid_t) -1, &status, WNOHANG);
+ if (rval > 0)
+ continue;
+ if (rval == 0)
+ lurking_children = 1;
+ break;
+ }
+
+ Shell_Init();
+
+ JobCreatePipe(&childExitJob, 3);
+
+ /* We can only need to wait for tokens, children and output from each job */
+ fds = bmake_malloc(sizeof (*fds) * (2 + maxJobs));
+ jobfds = bmake_malloc(sizeof (*jobfds) * (2 + maxJobs));
+
+ /* These are permanent entries and take slots 0 and 1 */
+ watchfd(&tokenWaitJob);
+ watchfd(&childExitJob);
+
+ sigemptyset(&caught_signals);
+ /*
+ * Install a SIGCHLD handler.
+ */
+ (void)bmake_signal(SIGCHLD, JobChildSig);
+ sigaddset(&caught_signals, SIGCHLD);
+
+#define ADDSIG(s,h) \
+ if (bmake_signal(s, SIG_IGN) != SIG_IGN) { \
+ sigaddset(&caught_signals, s); \
+ (void)bmake_signal(s, h); \
+ }
+
+ /*
+ * Catch the four signals that POSIX specifies if they aren't ignored.
+ * JobPassSig will take care of calling JobInterrupt if appropriate.
+ */
+ ADDSIG(SIGINT, JobPassSig_int)
+ ADDSIG(SIGHUP, JobPassSig_term)
+ ADDSIG(SIGTERM, JobPassSig_term)
+ ADDSIG(SIGQUIT, JobPassSig_term)
+
+ /*
+ * There are additional signals that need to be caught and passed if
+ * either the export system wants to be told directly of signals or if
+ * we're giving each job its own process group (since then it won't get
+ * signals from the terminal driver as we own the terminal)
+ */
+ ADDSIG(SIGTSTP, JobPassSig_suspend)
+ ADDSIG(SIGTTOU, JobPassSig_suspend)
+ ADDSIG(SIGTTIN, JobPassSig_suspend)
+ ADDSIG(SIGWINCH, JobCondPassSig)
+ ADDSIG(SIGCONT, JobContinueSig)
+#undef ADDSIG
+
+ (void)Job_RunTarget(".BEGIN", NULL);
+ postCommands = Targ_FindNode(".END", TARG_CREATE);
+}
+
+static void JobSigReset(void)
+{
+#define DELSIG(s) \
+ if (sigismember(&caught_signals, s)) { \
+ (void)bmake_signal(s, SIG_DFL); \
+ }
+
+ DELSIG(SIGINT)
+ DELSIG(SIGHUP)
+ DELSIG(SIGQUIT)
+ DELSIG(SIGTERM)
+ DELSIG(SIGTSTP)
+ DELSIG(SIGTTOU)
+ DELSIG(SIGTTIN)
+ DELSIG(SIGWINCH)
+ DELSIG(SIGCONT)
+#undef DELSIG
+ (void)bmake_signal(SIGCHLD, SIG_DFL);
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * JobMatchShell --
+ * Find a shell in 'shells' given its name.
+ *
+ * Results:
+ * A pointer to the Shell structure.
+ *
+ * Side Effects:
+ * None.
+ *
+ *-----------------------------------------------------------------------
+ */
+static Shell *
+JobMatchShell(const char *name)
+{
+ Shell *sh;
+
+ for (sh = shells; sh->name != NULL; sh++) {
+ if (strcmp(name, sh->name) == 0)
+ return (sh);
+ }
+ return NULL;
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * Job_ParseShell --
+ * Parse a shell specification and set up commandShell, shellPath
+ * and shellName appropriately.
+ *
+ * Input:
+ * line The shell spec
+ *
+ * Results:
+ * FAILURE if the specification was incorrect.
+ *
+ * Side Effects:
+ * commandShell points to a Shell structure (either predefined or
+ * created from the shell spec), shellPath is the full path of the
+ * shell described by commandShell, while shellName is just the
+ * final component of shellPath.
+ *
+ * Notes:
+ * A shell specification consists of a .SHELL target, with dependency
+ * operator, followed by a series of blank-separated words. Double
+ * quotes can be used to use blanks in words. A backslash escapes
+ * anything (most notably a double-quote and a space) and
+ * provides the functionality it does in C. Each word consists of
+ * keyword and value separated by an equal sign. There should be no
+ * unnecessary spaces in the word. The keywords are as follows:
+ * name Name of shell.
+ * path Location of shell.
+ * quiet Command to turn off echoing.
+ * echo Command to turn echoing on
+ * filter Result of turning off echoing that shouldn't be
+ * printed.
+ * echoFlag Flag to turn echoing on at the start
+ * errFlag Flag to turn error checking on at the start
+ * hasErrCtl True if shell has error checking control
+ * newline String literal to represent a newline char
+ * check Command to turn on error checking if hasErrCtl
+ * is TRUE or template of command to echo a command
+ * for which error checking is off if hasErrCtl is
+ * FALSE.
+ * ignore Command to turn off error checking if hasErrCtl
+ * is TRUE or template of command to execute a
+ * command so as to ignore any errors it returns if
+ * hasErrCtl is FALSE.
+ *
+ *-----------------------------------------------------------------------
+ */
+ReturnStatus
+Job_ParseShell(char *line)
+{
+ char **words;
+ char **argv;
+ int argc;
+ char *path;
+ Shell newShell;
+ Boolean fullSpec = FALSE;
+ Shell *sh;
+
+ while (isspace((unsigned char)*line)) {
+ line++;
+ }
+
+ free(UNCONST(shellArgv));
+
+ memset(&newShell, 0, sizeof(newShell));
+
+ /*
+ * Parse the specification by keyword
+ */
+ words = brk_string(line, &argc, TRUE, &path);
+ if (words == NULL) {
+ Error("Unterminated quoted string [%s]", line);
+ return FAILURE;
+ }
+ shellArgv = path;
+
+ for (path = NULL, argv = words; argc != 0; argc--, argv++) {
+ if (strncmp(*argv, "path=", 5) == 0) {
+ path = &argv[0][5];
+ } else if (strncmp(*argv, "name=", 5) == 0) {
+ newShell.name = &argv[0][5];
+ } else {
+ if (strncmp(*argv, "quiet=", 6) == 0) {
+ newShell.echoOff = &argv[0][6];
+ } else if (strncmp(*argv, "echo=", 5) == 0) {
+ newShell.echoOn = &argv[0][5];
+ } else if (strncmp(*argv, "filter=", 7) == 0) {
+ newShell.noPrint = &argv[0][7];
+ newShell.noPLen = strlen(newShell.noPrint);
+ } else if (strncmp(*argv, "echoFlag=", 9) == 0) {
+ newShell.echo = &argv[0][9];
+ } else if (strncmp(*argv, "errFlag=", 8) == 0) {
+ newShell.exit = &argv[0][8];
+ } else if (strncmp(*argv, "hasErrCtl=", 10) == 0) {
+ char c = argv[0][10];
+ newShell.hasErrCtl = !((c != 'Y') && (c != 'y') &&
+ (c != 'T') && (c != 't'));
+ } else if (strncmp(*argv, "newline=", 8) == 0) {
+ newShell.newline = &argv[0][8];
+ } else if (strncmp(*argv, "check=", 6) == 0) {
+ newShell.errCheck = &argv[0][6];
+ } else if (strncmp(*argv, "ignore=", 7) == 0) {
+ newShell.ignErr = &argv[0][7];
+ } else if (strncmp(*argv, "errout=", 7) == 0) {
+ newShell.errOut = &argv[0][7];
+ } else if (strncmp(*argv, "comment=", 8) == 0) {
+ newShell.commentChar = argv[0][8];
+ } else {
+ Parse_Error(PARSE_FATAL, "Unknown keyword \"%s\"",
+ *argv);
+ free(words);
+ return(FAILURE);
+ }
+ fullSpec = TRUE;
+ }
+ }
+
+ if (path == NULL) {
+ /*
+ * If no path was given, the user wants one of the pre-defined shells,
+ * yes? So we find the one s/he wants with the help of JobMatchShell
+ * and set things up the right way. shellPath will be set up by
+ * Shell_Init.
+ */
+ if (newShell.name == NULL) {
+ Parse_Error(PARSE_FATAL, "Neither path nor name specified");
+ free(words);
+ return(FAILURE);
+ } else {
+ if ((sh = JobMatchShell(newShell.name)) == NULL) {
+ Parse_Error(PARSE_WARNING, "%s: No matching shell",
+ newShell.name);
+ free(words);
+ return(FAILURE);
+ }
+ commandShell = sh;
+ shellName = newShell.name;
+ if (shellPath) {
+ /* Shell_Init has already been called! Do it again. */
+ free(UNCONST(shellPath));
+ shellPath = NULL;
+ Shell_Init();
+ }
+ }
+ } else {
+ /*
+ * The user provided a path. If s/he gave nothing else (fullSpec is
+ * FALSE), try and find a matching shell in the ones we know of.
+ * Else we just take the specification at its word and copy it
+ * to a new location. In either case, we need to record the
+ * path the user gave for the shell.
+ */
+ shellPath = path;
+ path = strrchr(path, '/');
+ if (path == NULL) {
+ path = UNCONST(shellPath);
+ } else {
+ path += 1;
+ }
+ if (newShell.name != NULL) {
+ shellName = newShell.name;
+ } else {
+ shellName = path;
+ }
+ if (!fullSpec) {
+ if ((sh = JobMatchShell(shellName)) == NULL) {
+ Parse_Error(PARSE_WARNING, "%s: No matching shell",
+ shellName);
+ free(words);
+ return(FAILURE);
+ }
+ commandShell = sh;
+ } else {
+ commandShell = bmake_malloc(sizeof(Shell));
+ *commandShell = newShell;
+ }
+ /* this will take care of shellErrFlag */
+ Shell_Init();
+ }
+
+ if (commandShell->echoOn && commandShell->echoOff) {
+ commandShell->hasEchoCtl = TRUE;
+ }
+
+ if (!commandShell->hasErrCtl) {
+ if (commandShell->errCheck == NULL) {
+ commandShell->errCheck = "";
+ }
+ if (commandShell->ignErr == NULL) {
+ commandShell->ignErr = "%s\n";
+ }
+ }
+
+ /*
+ * Do not free up the words themselves, since they might be in use by the
+ * shell specification.
+ */
+ free(words);
+ return SUCCESS;
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * JobInterrupt --
+ * Handle the receipt of an interrupt.
+ *
+ * Input:
+ * runINTERRUPT Non-zero if commands for the .INTERRUPT target
+ * should be executed
+ * signo signal received
+ *
+ * Results:
+ * None
+ *
+ * Side Effects:
+ * All children are killed. Another job will be started if the
+ * .INTERRUPT target was given.
+ *-----------------------------------------------------------------------
+ */
+static void
+JobInterrupt(int runINTERRUPT, int signo)
+{
+ Job *job; /* job descriptor in that element */
+ GNode *interrupt; /* the node describing the .INTERRUPT target */
+ sigset_t mask;
+ GNode *gn;
+
+ aborting = ABORT_INTERRUPT;
+
+ JobSigLock(&mask);
+
+ for (job = job_table; job < job_table_end; job++) {
+ if (job->job_state != JOB_ST_RUNNING)
+ continue;
+
+ gn = job->node;
+
+ JobDeleteTarget(gn);
+ if (job->pid) {
+ if (DEBUG(JOB)) {
+ (void)fprintf(debug_file,
+ "JobInterrupt passing signal %d to child %d.\n",
+ signo, job->pid);
+ }
+ KILLPG(job->pid, signo);
+ }
+ }
+
+ JobSigUnlock(&mask);
+
+ if (runINTERRUPT && !touchFlag) {
+ interrupt = Targ_FindNode(".INTERRUPT", TARG_NOCREATE);
+ if (interrupt != NULL) {
+ ignoreErrors = FALSE;
+ JobRun(interrupt);
+ }
+ }
+ Trace_Log(MAKEINTR, 0);
+ exit(signo);
+}
+
+/*
+ *-----------------------------------------------------------------------
+ * Job_Finish --
+ * Do final processing such as the running of the commands
+ * attached to the .END target.
+ *
+ * Results:
+ * Number of errors reported.
+ *
+ * Side Effects:
+ * None.
+ *-----------------------------------------------------------------------
+ */
+int
+Job_Finish(void)
+{
+ if (postCommands != NULL &&
+ (!Lst_IsEmpty(postCommands->commands) ||
+ !Lst_IsEmpty(postCommands->children))) {
+ if (errors) {
+ Error("Errors reported so .END ignored");
+ } else {
+ JobRun(postCommands);
+ }
+ }
+ return(errors);
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * Job_End --
+ * Cleanup any memory used by the jobs module
+ *
+ * Results:
+ * None.
+ *
+ * Side Effects:
+ * Memory is freed
+ *-----------------------------------------------------------------------
+ */
+void
+Job_End(void)
+{
+#ifdef CLEANUP
+ free(shellArgv);
+#endif
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * Job_Wait --
+ * Waits for all running jobs to finish and returns. Sets 'aborting'
+ * to ABORT_WAIT to prevent other jobs from starting.
+ *
+ * Results:
+ * None.
+ *
+ * Side Effects:
+ * Currently running jobs finish.
+ *
+ *-----------------------------------------------------------------------
+ */
+void
+Job_Wait(void)
+{
+ aborting = ABORT_WAIT;
+ while (jobTokensRunning != 0) {
+ Job_CatchOutput();
+ }
+ aborting = 0;
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * Job_AbortAll --
+ * Abort all currently running jobs without handling output or anything.
+ * This function is to be called only in the event of a major
+ * error. Most definitely NOT to be called from JobInterrupt.
+ *
+ * Results:
+ * None
+ *
+ * Side Effects:
+ * All children are killed, not just the firstborn
+ *-----------------------------------------------------------------------
+ */
+void
+Job_AbortAll(void)
+{
+ Job *job; /* the job descriptor in that element */
+ int foo;
+
+ aborting = ABORT_ERROR;
+
+ if (jobTokensRunning) {
+ for (job = job_table; job < job_table_end; job++) {
+ if (job->job_state != JOB_ST_RUNNING)
+ continue;
+ /*
+ * kill the child process with increasingly drastic signals to make
+ * darn sure it's dead.
+ */
+ KILLPG(job->pid, SIGINT);
+ KILLPG(job->pid, SIGKILL);
+ }
+ }
+
+ /*
+ * Catch as many children as want to report in at first, then give up
+ */
+ while (waitpid((pid_t) -1, &foo, WNOHANG) > 0)
+ continue;
+}
+
+
+/*-
+ *-----------------------------------------------------------------------
+ * JobRestartJobs --
+ * Tries to restart stopped jobs if there are slots available.
+ * Called in process context in response to a SIGCONT.
+ *
+ * Results:
+ * None.
+ *
+ * Side Effects:
+ * Resumes jobs.
+ *
+ *-----------------------------------------------------------------------
+ */
+static void
+JobRestartJobs(void)
+{
+ Job *job;
+
+ for (job = job_table; job < job_table_end; job++) {
+ if (job->job_state == JOB_ST_RUNNING &&
+ (make_suspended || job->job_suspended)) {
+ if (DEBUG(JOB)) {
+ (void)fprintf(debug_file, "Restarting stopped job pid %d.\n",
+ job->pid);
+ }
+ if (job->job_suspended) {
+ (void)printf("*** [%s] Continued\n", job->node->name);
+ (void)fflush(stdout);
+ }
+ job->job_suspended = 0;
+ if (KILLPG(job->pid, SIGCONT) != 0 && DEBUG(JOB)) {
+ fprintf(debug_file, "Failed to send SIGCONT to %d\n", job->pid);
+ }
+ }
+ if (job->job_state == JOB_ST_FINISHED)
+ /* Job exit deferred after calling waitpid() in a signal handler */
+ JobFinish(job, job->exit_status);
+ }
+ make_suspended = 0;
+}
+
+static void
+watchfd(Job *job)
+{
+ if (job->inPollfd != NULL)
+ Punt("Watching watched job");
+
+ fds[nfds].fd = job->inPipe;
+ fds[nfds].events = POLLIN;
+ jobfds[nfds] = job;
+ job->inPollfd = &fds[nfds];
+ nfds++;
+}
+
+static void
+clearfd(Job *job)
+{
+ int i;
+ if (job->inPollfd == NULL)
+ Punt("Unwatching unwatched job");
+ i = job->inPollfd - fds;
+ nfds--;
+ /*
+ * Move last job in table into hole made by dead job.
+ */
+ if (nfds != i) {
+ fds[i] = fds[nfds];
+ jobfds[i] = jobfds[nfds];
+ jobfds[i]->inPollfd = &fds[i];
+ }
+ job->inPollfd = NULL;
+}
+
+static int
+readyfd(Job *job)
+{
+ if (job->inPollfd == NULL)
+ Punt("Polling unwatched job");
+ return (job->inPollfd->revents & POLLIN) != 0;
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * JobTokenAdd --
+ * Put a token into the job pipe so that some make process can start
+ * another job.
+ *
+ * Side Effects:
+ * Allows more build jobs to be spawned somewhere.
+ *
+ *-----------------------------------------------------------------------
+ */
+
+static void
+JobTokenAdd(void)
+{
+ char tok = JOB_TOKENS[aborting], tok1;
+
+ /* If we are depositing an error token flush everything else */
+ while (tok != '+' && read(tokenWaitJob.inPipe, &tok1, 1) == 1)
+ continue;
+
+ if (DEBUG(JOB))
+ fprintf(debug_file, "(%d) aborting %d, deposit token %c\n",
+ getpid(), aborting, JOB_TOKENS[aborting]);
+ while (write(tokenWaitJob.outPipe, &tok, 1) == -1 && errno == EAGAIN)
+ continue;
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * Job_ServerStartTokenAdd --
+ * Prep the job token pipe in the root make process.
+ *
+ *-----------------------------------------------------------------------
+ */
+
+void
+Job_ServerStart(int max_tokens, int jp_0, int jp_1)
+{
+ int i;
+ char jobarg[64];
+
+ if (jp_0 >= 0 && jp_1 >= 0) {
+ /* Pipe passed in from parent */
+ tokenWaitJob.inPipe = jp_0;
+ tokenWaitJob.outPipe = jp_1;
+ (void)fcntl(jp_0, F_SETFD, FD_CLOEXEC);
+ (void)fcntl(jp_1, F_SETFD, FD_CLOEXEC);
+ return;
+ }
+
+ JobCreatePipe(&tokenWaitJob, 15);
+
+ snprintf(jobarg, sizeof(jobarg), "%d,%d",
+ tokenWaitJob.inPipe, tokenWaitJob.outPipe);
+
+ Var_Append(MAKEFLAGS, "-J", VAR_GLOBAL);
+ Var_Append(MAKEFLAGS, jobarg, VAR_GLOBAL);
+
+ /*
+ * Preload the job pipe with one token per job, save the one
+ * "extra" token for the primary job.
+ *
+ * XXX should clip maxJobs against PIPE_BUF -- if max_tokens is
+ * larger than the write buffer size of the pipe, we will
+ * deadlock here.
+ */
+ for (i = 1; i < max_tokens; i++)
+ JobTokenAdd();
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * Job_TokenReturn --
+ * Return a withdrawn token to the pool.
+ *
+ *-----------------------------------------------------------------------
+ */
+
+void
+Job_TokenReturn(void)
+{
+ jobTokensRunning--;
+ if (jobTokensRunning < 0)
+ Punt("token botch");
+ if (jobTokensRunning || JOB_TOKENS[aborting] != '+')
+ JobTokenAdd();
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * Job_TokenWithdraw --
+ * Attempt to withdraw a token from the pool.
+ *
+ * Results:
+ * Returns TRUE if a token was withdrawn, and FALSE if the pool
+ * is currently empty.
+ *
+ * Side Effects:
+ * If pool is empty, set wantToken so that we wake up
+ * when a token is released.
+ *
+ *-----------------------------------------------------------------------
+ */
+
+
+Boolean
+Job_TokenWithdraw(void)
+{
+ char tok, tok1;
+ int count;
+
+ wantToken = 0;
+ if (DEBUG(JOB))
+ fprintf(debug_file, "Job_TokenWithdraw(%d): aborting %d, running %d\n",
+ getpid(), aborting, jobTokensRunning);
+
+ if (aborting || (jobTokensRunning >= maxJobs))
+ return FALSE;
+
+ count = read(tokenWaitJob.inPipe, &tok, 1);
+ if (count == 0)
+ Fatal("eof on job pipe!");
+ if (count < 0 && jobTokensRunning != 0) {
+ if (errno != EAGAIN) {
+ Fatal("job pipe read: %s", strerror(errno));
+ }
+ if (DEBUG(JOB))
+ fprintf(debug_file, "(%d) blocked for token\n", getpid());
+ return FALSE;
+ }
+
+ if (count == 1 && tok != '+') {
+ /* make being abvorted - remove any other job tokens */
+ if (DEBUG(JOB))
+ fprintf(debug_file, "(%d) aborted by token %c\n", getpid(), tok);
+ while (read(tokenWaitJob.inPipe, &tok1, 1) == 1)
+ continue;
+ /* And put the stopper back */
+ while (write(tokenWaitJob.outPipe, &tok, 1) == -1 && errno == EAGAIN)
+ continue;
+ Fatal("A failure has been detected in another branch of the parallel make");
+ }
+
+ if (count == 1 && jobTokensRunning == 0)
+ /* We didn't want the token really */
+ while (write(tokenWaitJob.outPipe, &tok, 1) == -1 && errno == EAGAIN)
+ continue;
+
+ jobTokensRunning++;
+ if (DEBUG(JOB))
+ fprintf(debug_file, "(%d) withdrew token\n", getpid());
+ return TRUE;
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * Job_RunTarget --
+ * Run the named target if found. If a filename is specified, then
+ * set that to the sources.
+ *
+ * Results:
+ * None
+ *
+ * Side Effects:
+ * exits if the target fails.
+ *
+ *-----------------------------------------------------------------------
+ */
+Boolean
+Job_RunTarget(const char *target, const char *fname) {
+ GNode *gn = Targ_FindNode(target, TARG_NOCREATE);
+
+ if (gn == NULL)
+ return FALSE;
+
+ if (fname)
+ Var_Set(ALLSRC, fname, gn, 0);
+
+ JobRun(gn);
+ if (gn->made == ERROR) {
+ PrintOnError(gn, "\n\nStop.");
+ exit(1);
+ }
+ return TRUE;
+}
+
+#ifdef USE_SELECT
+int
+emul_poll(struct pollfd *fd, int nfd, int timeout)
+{
+ fd_set rfds, wfds;
+ int i, maxfd, nselect, npoll;
+ struct timeval tv, *tvp;
+ long usecs;
+
+ FD_ZERO(&rfds);
+ FD_ZERO(&wfds);
+
+ maxfd = -1;
+ for (i = 0; i < nfd; i++) {
+ fd[i].revents = 0;
+
+ if (fd[i].events & POLLIN)
+ FD_SET(fd[i].fd, &rfds);
+
+ if (fd[i].events & POLLOUT)
+ FD_SET(fd[i].fd, &wfds);
+
+ if (fd[i].fd > maxfd)
+ maxfd = fd[i].fd;
+ }
+
+ if (maxfd >= FD_SETSIZE) {
+ Punt("Ran out of fd_set slots; "
+ "recompile with a larger FD_SETSIZE.");
+ }
+
+ if (timeout < 0) {
+ tvp = NULL;
+ } else {
+ usecs = timeout * 1000;
+ tv.tv_sec = usecs / 1000000;
+ tv.tv_usec = usecs % 1000000;
+ tvp = &tv;
+ }
+
+ nselect = select(maxfd + 1, &rfds, &wfds, 0, tvp);
+
+ if (nselect <= 0)
+ return nselect;
+
+ npoll = 0;
+ for (i = 0; i < nfd; i++) {
+ if (FD_ISSET(fd[i].fd, &rfds))
+ fd[i].revents |= POLLIN;
+
+ if (FD_ISSET(fd[i].fd, &wfds))
+ fd[i].revents |= POLLOUT;
+
+ if (fd[i].revents)
+ npoll++;
+ }
+
+ return npoll;
+}
+#endif /* USE_SELECT */
diff --git a/usr.bin/make/job.h b/usr.bin/make/job.h
new file mode 100644
index 0000000..91e2c87
--- /dev/null
+++ b/usr.bin/make/job.h
@@ -0,0 +1,274 @@
+/* $NetBSD: job.h,v 1.42 2013/07/05 22:14:56 sjg Exp $ */
+
+/*
+ * Copyright (c) 1988, 1989, 1990 The Regents of the University of California.
+ * All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Adam de Boor.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * from: @(#)job.h 8.1 (Berkeley) 6/6/93
+ */
+
+/*
+ * Copyright (c) 1988, 1989 by Adam de Boor
+ * Copyright (c) 1989 by Berkeley Softworks
+ * All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Adam de Boor.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. All advertising materials mentioning features or use of this software
+ * must display the following acknowledgement:
+ * This product includes software developed by the University of
+ * California, Berkeley and its contributors.
+ * 4. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * from: @(#)job.h 8.1 (Berkeley) 6/6/93
+ */
+
+/*-
+ * job.h --
+ * Definitions pertaining to the running of jobs in parallel mode.
+ */
+#ifndef _JOB_H_
+#define _JOB_H_
+
+#define TMPPAT "makeXXXXXX" /* relative to tmpdir */
+
+#ifdef USE_SELECT
+/*
+ * Emulate poll() in terms of select(). This is not a complete
+ * emulation but it is sufficient for make's purposes.
+ */
+
+#define poll emul_poll
+#define pollfd emul_pollfd
+
+struct emul_pollfd {
+ int fd;
+ short events;
+ short revents;
+};
+
+#define POLLIN 0x0001
+#define POLLOUT 0x0004
+
+int
+emul_poll(struct pollfd *fd, int nfd, int timeout);
+#endif
+
+/*
+ * The POLL_MSEC constant determines the maximum number of milliseconds spent
+ * in poll before coming out to see if a child has finished.
+ */
+#define POLL_MSEC 5000
+
+
+/*-
+ * Job Table definitions.
+ *
+ * Each job has several things associated with it:
+ * 1) The process id of the child shell
+ * 2) The graph node describing the target being made by this job
+ * 3) A LstNode for the first command to be saved after the job
+ * completes. This is NULL if there was no "..." in the job's
+ * commands.
+ * 4) An FILE* for writing out the commands. This is only
+ * used before the job is actually started.
+ * 5) The output is being caught via a pipe and
+ * the descriptors of our pipe, an array in which output is line
+ * buffered and the current position in that buffer are all
+ * maintained for each job.
+ * 6) A word of flags which determine how the module handles errors,
+ * echoing, etc. for the job
+ *
+ * When a job is finished, the Make_Update function is called on each of the
+ * parents of the node which was just remade. This takes care of the upward
+ * traversal of the dependency graph.
+ */
+struct pollfd;
+
+
+#ifdef USE_META
+# include "meta.h"
+#endif
+
+#define JOB_BUFSIZE 1024
+typedef struct Job {
+ int pid; /* The child's process ID */
+ GNode *node; /* The target the child is making */
+ LstNode tailCmds; /* The node of the first command to be
+ * saved when the job has been run */
+ FILE *cmdFILE; /* When creating the shell script, this is
+ * where the commands go */
+ int exit_status; /* from wait4() in signal handler */
+ char job_state; /* status of the job entry */
+#define JOB_ST_FREE 0 /* Job is available */
+#define JOB_ST_SETUP 1 /* Job is allocated but otherwise invalid */
+#define JOB_ST_RUNNING 3 /* Job is running, pid valid */
+#define JOB_ST_FINISHED 4 /* Job is done (ie after SIGCHILD) */
+ char job_suspended;
+ short flags; /* Flags to control treatment of job */
+#define JOB_IGNERR 0x001 /* Ignore non-zero exits */
+#define JOB_SILENT 0x002 /* no output */
+#define JOB_SPECIAL 0x004 /* Target is a special one. i.e. run it locally
+ * if we can't export it and maxLocal is 0 */
+#define JOB_IGNDOTS 0x008 /* Ignore "..." lines when processing
+ * commands */
+#define JOB_TRACED 0x400 /* we've sent 'set -x' */
+
+ int jobPipe[2]; /* Pipe for readind output from job */
+ struct pollfd *inPollfd; /* pollfd associated with inPipe */
+ char outBuf[JOB_BUFSIZE + 1];
+ /* Buffer for storing the output of the
+ * job, line by line */
+ int curPos; /* Current position in op_outBuf */
+
+#ifdef USE_META
+ struct BuildMon bm;
+#endif
+} Job;
+
+#define inPipe jobPipe[0]
+#define outPipe jobPipe[1]
+
+
+/*-
+ * Shell Specifications:
+ * Each shell type has associated with it the following information:
+ * 1) The string which must match the last character of the shell name
+ * for the shell to be considered of this type. The longest match
+ * wins.
+ * 2) A command to issue to turn off echoing of command lines
+ * 3) A command to issue to turn echoing back on again
+ * 4) What the shell prints, and its length, when given the echo-off
+ * command. This line will not be printed when received from the shell
+ * 5) A boolean to tell if the shell has the ability to control
+ * error checking for individual commands.
+ * 6) The string to turn this checking on.
+ * 7) The string to turn it off.
+ * 8) The command-flag to give to cause the shell to start echoing
+ * commands right away.
+ * 9) The command-flag to cause the shell to Lib_Exit when an error is
+ * detected in one of the commands.
+ *
+ * Some special stuff goes on if a shell doesn't have error control. In such
+ * a case, errCheck becomes a printf template for echoing the command,
+ * should echoing be on and ignErr becomes another printf template for
+ * executing the command while ignoring the return status. Finally errOut
+ * is a printf template for running the command and causing the shell to
+ * exit on error. If any of these strings are empty when hasErrCtl is FALSE,
+ * the command will be executed anyway as is and if it causes an error, so be
+ * it. Any templates setup to echo the command will escape any '$ ` \ "'i
+ * characters in the command string to avoid common problems with
+ * echo "%s\n" as a template.
+ */
+typedef struct Shell {
+ const char *name; /* the name of the shell. For Bourne and C
+ * shells, this is used only to find the
+ * shell description when used as the single
+ * source of a .SHELL target. For user-defined
+ * shells, this is the full path of the shell.
+ */
+ Boolean hasEchoCtl; /* True if both echoOff and echoOn defined */
+ const char *echoOff; /* command to turn off echo */
+ const char *echoOn; /* command to turn it back on again */
+ const char *noPrint; /* command to skip when printing output from
+ * shell. This is usually the command which
+ * was executed to turn off echoing */
+ int noPLen; /* length of noPrint command */
+ Boolean hasErrCtl; /* set if can control error checking for
+ * individual commands */
+ const char *errCheck; /* string to turn error checking on */
+ const char *ignErr; /* string to turn off error checking */
+ const char *errOut; /* string to use for testing exit code */
+ const char *newline; /* string literal that results in a newline
+ * character when it appears outside of any
+ * 'quote' or "quote" characters */
+ char commentChar; /* character used by shell for comment lines */
+
+ /*
+ * command-line flags
+ */
+ const char *echo; /* echo commands */
+ const char *exit; /* exit on error */
+} Shell;
+
+extern const char *shellPath;
+extern const char *shellName;
+extern char *shellErrFlag;
+
+extern int jobTokensRunning; /* tokens currently "out" */
+extern int maxJobs; /* Max jobs we can run */
+
+void Shell_Init(void);
+const char *Shell_GetNewline(void);
+void Job_Touch(GNode *, Boolean);
+Boolean Job_CheckCommands(GNode *, void (*abortProc )(const char *, ...));
+#define CATCH_BLOCK 1
+void Job_CatchChildren(void);
+void Job_CatchOutput(void);
+void Job_Make(GNode *);
+void Job_Init(void);
+Boolean Job_Full(void);
+Boolean Job_Empty(void);
+ReturnStatus Job_ParseShell(char *);
+int Job_Finish(void);
+void Job_End(void);
+void Job_Wait(void);
+void Job_AbortAll(void);
+void JobFlagForMigration(int);
+void Job_TokenReturn(void);
+Boolean Job_TokenWithdraw(void);
+void Job_ServerStart(int, int, int);
+void Job_SetPrefix(void);
+Boolean Job_RunTarget(const char *, const char *);
+
+#endif /* _JOB_H_ */
diff --git a/usr.bin/make/lst.h b/usr.bin/make/lst.h
new file mode 100644
index 0000000..e207bc8
--- /dev/null
+++ b/usr.bin/make/lst.h
@@ -0,0 +1,189 @@
+/* $NetBSD: lst.h,v 1.20 2014/09/07 20:55:34 joerg Exp $ */
+
+/*
+ * Copyright (c) 1988, 1989, 1990 The Regents of the University of California.
+ * All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Adam de Boor.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * from: @(#)lst.h 8.1 (Berkeley) 6/6/93
+ */
+
+/*
+ * Copyright (c) 1988, 1989 by Adam de Boor
+ * Copyright (c) 1989 by Berkeley Softworks
+ * All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Adam de Boor.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. All advertising materials mentioning features or use of this software
+ * must display the following acknowledgement:
+ * This product includes software developed by the University of
+ * California, Berkeley and its contributors.
+ * 4. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * from: @(#)lst.h 8.1 (Berkeley) 6/6/93
+ */
+
+/*-
+ * lst.h --
+ * Header for using the list library
+ */
+#ifndef _LST_H_
+#define _LST_H_
+
+#include <sys/param.h>
+#include <stdlib.h>
+
+#include "sprite.h"
+
+/*
+ * basic typedef. This is what the Lst_ functions handle
+ */
+
+typedef struct List *Lst;
+typedef struct ListNode *LstNode;
+
+typedef void *DuplicateProc(void *);
+typedef void FreeProc(void *);
+
+#define LST_CONCNEW 0 /* create new LstNode's when using Lst_Concat */
+#define LST_CONCLINK 1 /* relink LstNode's when using Lst_Concat */
+
+/*
+ * Creation/destruction functions
+ */
+/* Create a new list */
+Lst Lst_Init(Boolean);
+/* Duplicate an existing list */
+Lst Lst_Duplicate(Lst, DuplicateProc *);
+/* Destroy an old one */
+void Lst_Destroy(Lst, FreeProc *);
+/* True if list is empty */
+Boolean Lst_IsEmpty(Lst);
+
+/*
+ * Functions to modify a list
+ */
+/* Insert an element before another */
+ReturnStatus Lst_InsertBefore(Lst, LstNode, void *);
+/* Insert an element after another */
+ReturnStatus Lst_InsertAfter(Lst, LstNode, void *);
+/* Place an element at the front of a lst. */
+ReturnStatus Lst_AtFront(Lst, void *);
+/* Place an element at the end of a lst. */
+ReturnStatus Lst_AtEnd(Lst, void *);
+/* Remove an element */
+ReturnStatus Lst_Remove(Lst, LstNode);
+/* Replace a node with a new value */
+ReturnStatus Lst_Replace(LstNode, void *);
+/* Concatenate two lists */
+ReturnStatus Lst_Concat(Lst, Lst, int);
+
+/*
+ * Node-specific functions
+ */
+/* Return first element in list */
+LstNode Lst_First(Lst);
+/* Return last element in list */
+LstNode Lst_Last(Lst);
+/* Return successor to given element */
+LstNode Lst_Succ(LstNode);
+/* Return predecessor to given element */
+LstNode Lst_Prev(LstNode);
+/* Get datum from LstNode */
+void *Lst_Datum(LstNode);
+
+/*
+ * Functions for entire lists
+ */
+/* Find an element in a list */
+LstNode Lst_Find(Lst, const void *, int (*)(const void *, const void *));
+/* Find an element starting from somewhere */
+LstNode Lst_FindFrom(Lst, LstNode, const void *,
+ int (*cProc)(const void *, const void *));
+/*
+ * See if the given datum is on the list. Returns the LstNode containing
+ * the datum
+ */
+LstNode Lst_Member(Lst, void *);
+/* Apply a function to all elements of a lst */
+int Lst_ForEach(Lst, int (*)(void *, void *), void *);
+/*
+ * Apply a function to all elements of a lst starting from a certain point.
+ * If the list is circular, the application will wrap around to the
+ * beginning of the list again.
+ */
+int Lst_ForEachFrom(Lst, LstNode, int (*)(void *, void *),
+ void *);
+/*
+ * these functions are for dealing with a list as a table, of sorts.
+ * An idea of the "current element" is kept and used by all the functions
+ * between Lst_Open() and Lst_Close().
+ */
+/* Open the list */
+ReturnStatus Lst_Open(Lst);
+/* Next element please */
+LstNode Lst_Next(Lst);
+/* Done yet? */
+Boolean Lst_IsAtEnd(Lst);
+/* Finish table access */
+void Lst_Close(Lst);
+
+/*
+ * for using the list as a queue
+ */
+/* Place an element at tail of queue */
+ReturnStatus Lst_EnQueue(Lst, void *);
+/* Remove an element from head of queue */
+void *Lst_DeQueue(Lst);
+
+#endif /* _LST_H_ */
diff --git a/usr.bin/make/lst.lib/Makefile b/usr.bin/make/lst.lib/Makefile
new file mode 100644
index 0000000..5b33a50
--- /dev/null
+++ b/usr.bin/make/lst.lib/Makefile
@@ -0,0 +1,10 @@
+# $NetBSD: Makefile,v 1.6 2006/11/11 21:23:36 dsl Exp $
+
+OBJ=lstAppend.o lstDupl.o lstInit.o lstOpen.o lstAtEnd.o lstEnQueue.o \
+ lstInsert.o lstAtFront.o lstIsAtEnd.o lstClose.o lstFind.o lstIsEmpty.o \
+ lstRemove.o lstConcat.o lstFindFrom.o lstLast.o lstReplace.o lstFirst.o \
+ lstDatum.o lstForEach.o lstMember.o lstSucc.o lstDeQueue.o \
+ lstForEachFrom.o lstDestroy.o lstNext.o lstPrev.o
+
+CPPFLAGS=-I${.CURDIR}/..
+all: ${OBJ}
diff --git a/usr.bin/make/lst.lib/lstAppend.c b/usr.bin/make/lst.lib/lstAppend.c
new file mode 100644
index 0000000..4dafe83
--- /dev/null
+++ b/usr.bin/make/lst.lib/lstAppend.c
@@ -0,0 +1,122 @@
+/* $NetBSD: lstAppend.c,v 1.14 2009/01/23 21:26:30 dsl Exp $ */
+
+/*
+ * Copyright (c) 1988, 1989, 1990, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Adam de Boor.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#ifndef MAKE_NATIVE
+static char rcsid[] = "$NetBSD: lstAppend.c,v 1.14 2009/01/23 21:26:30 dsl Exp $";
+#else
+#include <sys/cdefs.h>
+#ifndef lint
+#if 0
+static char sccsid[] = "@(#)lstAppend.c 8.1 (Berkeley) 6/6/93";
+#else
+__RCSID("$NetBSD: lstAppend.c,v 1.14 2009/01/23 21:26:30 dsl Exp $");
+#endif
+#endif /* not lint */
+#endif
+
+/*-
+ * LstAppend.c --
+ * Add a new node with a new datum after an existing node
+ */
+
+#include "lstInt.h"
+
+/*-
+ *-----------------------------------------------------------------------
+ * Lst_InsertAfter --
+ * Create a new node and add it to the given list after the given node.
+ *
+ * Input:
+ * l affected list
+ * ln node after which to append the datum
+ * d said datum
+ *
+ * Results:
+ * SUCCESS if all went well.
+ *
+ * Side Effects:
+ * A new ListNode is created and linked in to the List. The lastPtr
+ * field of the List will be altered if ln is the last node in the
+ * list. lastPtr and firstPtr will alter if the list was empty and
+ * ln was NULL.
+ *
+ *-----------------------------------------------------------------------
+ */
+ReturnStatus
+Lst_InsertAfter(Lst l, LstNode ln, void *d)
+{
+ List list;
+ ListNode lNode;
+ ListNode nLNode;
+
+ if (LstValid (l) && (ln == NULL && LstIsEmpty (l))) {
+ goto ok;
+ }
+
+ if (!LstValid (l) || LstIsEmpty (l) || ! LstNodeValid (ln, l)) {
+ return (FAILURE);
+ }
+ ok:
+
+ list = l;
+ lNode = ln;
+
+ PAlloc (nLNode, ListNode);
+ nLNode->datum = d;
+ nLNode->useCount = nLNode->flags = 0;
+
+ if (lNode == NULL) {
+ if (list->isCirc) {
+ nLNode->nextPtr = nLNode->prevPtr = nLNode;
+ } else {
+ nLNode->nextPtr = nLNode->prevPtr = NULL;
+ }
+ list->firstPtr = list->lastPtr = nLNode;
+ } else {
+ nLNode->prevPtr = lNode;
+ nLNode->nextPtr = lNode->nextPtr;
+
+ lNode->nextPtr = nLNode;
+ if (nLNode->nextPtr != NULL) {
+ nLNode->nextPtr->prevPtr = nLNode;
+ }
+
+ if (lNode == list->lastPtr) {
+ list->lastPtr = nLNode;
+ }
+ }
+
+ return (SUCCESS);
+}
+
diff --git a/usr.bin/make/lst.lib/lstAtEnd.c b/usr.bin/make/lst.lib/lstAtEnd.c
new file mode 100644
index 0000000..10f191a
--- /dev/null
+++ b/usr.bin/make/lst.lib/lstAtEnd.c
@@ -0,0 +1,79 @@
+/* $NetBSD: lstAtEnd.c,v 1.13 2009/01/23 21:26:30 dsl Exp $ */
+
+/*
+ * Copyright (c) 1988, 1989, 1990, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Adam de Boor.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#ifndef MAKE_NATIVE
+static char rcsid[] = "$NetBSD: lstAtEnd.c,v 1.13 2009/01/23 21:26:30 dsl Exp $";
+#else
+#include <sys/cdefs.h>
+#ifndef lint
+#if 0
+static char sccsid[] = "@(#)lstAtEnd.c 8.1 (Berkeley) 6/6/93";
+#else
+__RCSID("$NetBSD: lstAtEnd.c,v 1.13 2009/01/23 21:26:30 dsl Exp $");
+#endif
+#endif /* not lint */
+#endif
+
+/*-
+ * LstAtEnd.c --
+ * Add a node at the end of the list
+ */
+
+#include "lstInt.h"
+
+/*-
+ *-----------------------------------------------------------------------
+ * Lst_AtEnd --
+ * Add a node to the end of the given list
+ *
+ * Input:
+ * l List to which to add the datum
+ * d Datum to add
+ *
+ * Results:
+ * SUCCESS if life is good.
+ *
+ * Side Effects:
+ * A new ListNode is created and added to the list.
+ *
+ *-----------------------------------------------------------------------
+ */
+ReturnStatus
+Lst_AtEnd(Lst l, void *d)
+{
+ LstNode end;
+
+ end = Lst_Last(l);
+ return (Lst_InsertAfter(l, end, d));
+}
diff --git a/usr.bin/make/lst.lib/lstAtFront.c b/usr.bin/make/lst.lib/lstAtFront.c
new file mode 100644
index 0000000..d8be166
--- /dev/null
+++ b/usr.bin/make/lst.lib/lstAtFront.c
@@ -0,0 +1,76 @@
+/* $NetBSD: lstAtFront.c,v 1.13 2009/01/23 21:26:30 dsl Exp $ */
+
+/*
+ * Copyright (c) 1988, 1989, 1990, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Adam de Boor.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#ifndef MAKE_NATIVE
+static char rcsid[] = "$NetBSD: lstAtFront.c,v 1.13 2009/01/23 21:26:30 dsl Exp $";
+#else
+#include <sys/cdefs.h>
+#ifndef lint
+#if 0
+static char sccsid[] = "@(#)lstAtFront.c 8.1 (Berkeley) 6/6/93";
+#else
+__RCSID("$NetBSD: lstAtFront.c,v 1.13 2009/01/23 21:26:30 dsl Exp $");
+#endif
+#endif /* not lint */
+#endif
+
+/*-
+ * LstAtFront.c --
+ * Add a node at the front of the list
+ */
+
+#include "lstInt.h"
+
+/*-
+ *-----------------------------------------------------------------------
+ * Lst_AtFront --
+ * Place a piece of data at the front of a list
+ *
+ * Results:
+ * SUCCESS or FAILURE
+ *
+ * Side Effects:
+ * A new ListNode is created and stuck at the front of the list.
+ * hence, firstPtr (and possible lastPtr) in the list are altered.
+ *
+ *-----------------------------------------------------------------------
+ */
+ReturnStatus
+Lst_AtFront(Lst l, void *d)
+{
+ LstNode front;
+
+ front = Lst_First(l);
+ return (Lst_InsertBefore(l, front, d));
+}
diff --git a/usr.bin/make/lst.lib/lstClose.c b/usr.bin/make/lst.lib/lstClose.c
new file mode 100644
index 0000000..06b68c5
--- /dev/null
+++ b/usr.bin/make/lst.lib/lstClose.c
@@ -0,0 +1,86 @@
+/* $NetBSD: lstClose.c,v 1.11 2006/10/27 21:37:25 dsl Exp $ */
+
+/*
+ * Copyright (c) 1988, 1989, 1990, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Adam de Boor.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#ifndef MAKE_NATIVE
+static char rcsid[] = "$NetBSD: lstClose.c,v 1.11 2006/10/27 21:37:25 dsl Exp $";
+#else
+#include <sys/cdefs.h>
+#ifndef lint
+#if 0
+static char sccsid[] = "@(#)lstClose.c 8.1 (Berkeley) 6/6/93";
+#else
+__RCSID("$NetBSD: lstClose.c,v 1.11 2006/10/27 21:37:25 dsl Exp $");
+#endif
+#endif /* not lint */
+#endif
+
+/*-
+ * LstClose.c --
+ * Close a list for sequential access.
+ * The sequential functions access the list in a slightly different way.
+ * CurPtr points to their idea of the current node in the list and they
+ * access the list based on it. Because the list is circular, Lst_Next
+ * and Lst_Prev will go around the list forever. Lst_IsAtEnd must be
+ * used to determine when to stop.
+ */
+
+#include "lstInt.h"
+
+/*-
+ *-----------------------------------------------------------------------
+ * Lst_Close --
+ * Close a list which was opened for sequential access.
+ *
+ * Input:
+ * l The list to close
+ *
+ * Results:
+ * None.
+ *
+ * Side Effects:
+ * The list is closed.
+ *
+ *-----------------------------------------------------------------------
+ */
+void
+Lst_Close(Lst l)
+{
+ List list = l;
+
+ if (LstValid(l) == TRUE) {
+ list->isOpen = FALSE;
+ list->atEnd = Unknown;
+ }
+}
+
diff --git a/usr.bin/make/lst.lib/lstConcat.c b/usr.bin/make/lst.lib/lstConcat.c
new file mode 100644
index 0000000..534d34e
--- /dev/null
+++ b/usr.bin/make/lst.lib/lstConcat.c
@@ -0,0 +1,185 @@
+/* $NetBSD: lstConcat.c,v 1.16 2008/12/13 15:19:29 dsl Exp $ */
+
+/*
+ * Copyright (c) 1988, 1989, 1990, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Adam de Boor.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#ifndef MAKE_NATIVE
+static char rcsid[] = "$NetBSD: lstConcat.c,v 1.16 2008/12/13 15:19:29 dsl Exp $";
+#else
+#include <sys/cdefs.h>
+#ifndef lint
+#if 0
+static char sccsid[] = "@(#)lstConcat.c 8.1 (Berkeley) 6/6/93";
+#else
+__RCSID("$NetBSD: lstConcat.c,v 1.16 2008/12/13 15:19:29 dsl Exp $");
+#endif
+#endif /* not lint */
+#endif
+
+/*-
+ * listConcat.c --
+ * Function to concatentate two lists.
+ */
+
+#include "lstInt.h"
+
+/*-
+ *-----------------------------------------------------------------------
+ * Lst_Concat --
+ * Concatenate two lists. New elements are created to hold the data
+ * elements, if specified, but the elements themselves are not copied.
+ * If the elements should be duplicated to avoid confusion with another
+ * list, the Lst_Duplicate function should be called first.
+ * If LST_CONCLINK is specified, the second list is destroyed since
+ * its pointers have been corrupted and the list is no longer useable.
+ *
+ * Input:
+ * l1 The list to which l2 is to be appended
+ * l2 The list to append to l1
+ * flags LST_CONCNEW if LstNode's should be duplicated
+ * LST_CONCLINK if should just be relinked
+ *
+ * Results:
+ * SUCCESS if all went well. FAILURE otherwise.
+ *
+ * Side Effects:
+ * New elements are created and appended the first list.
+ *-----------------------------------------------------------------------
+ */
+ReturnStatus
+Lst_Concat(Lst l1, Lst l2, int flags)
+{
+ ListNode ln; /* original LstNode */
+ ListNode nln; /* new LstNode */
+ ListNode last; /* the last element in the list. Keeps
+ * bookkeeping until the end */
+ List list1 = l1;
+ List list2 = l2;
+
+ if (!LstValid (l1) || !LstValid (l2)) {
+ return (FAILURE);
+ }
+
+ if (flags == LST_CONCLINK) {
+ if (list2->firstPtr != NULL) {
+ /*
+ * We set the nextPtr of the
+ * last element of list two to be NIL to make the loop easier and
+ * so we don't need an extra case should the first list turn
+ * out to be non-circular -- the final element will already point
+ * to NIL space and the first element will be untouched if it
+ * existed before and will also point to NIL space if it didn't.
+ */
+ list2->lastPtr->nextPtr = NULL;
+ /*
+ * So long as the second list isn't empty, we just link the
+ * first element of the second list to the last element of the
+ * first list. If the first list isn't empty, we then link the
+ * last element of the list to the first element of the second list
+ * The last element of the second list, if it exists, then becomes
+ * the last element of the first list.
+ */
+ list2->firstPtr->prevPtr = list1->lastPtr;
+ if (list1->lastPtr != NULL) {
+ list1->lastPtr->nextPtr = list2->firstPtr;
+ } else {
+ list1->firstPtr = list2->firstPtr;
+ }
+ list1->lastPtr = list2->lastPtr;
+ }
+ if (list1->isCirc && list1->firstPtr != NULL) {
+ /*
+ * If the first list is supposed to be circular and it is (now)
+ * non-empty, we must make sure it's circular by linking the
+ * first element to the last and vice versa
+ */
+ list1->firstPtr->prevPtr = list1->lastPtr;
+ list1->lastPtr->nextPtr = list1->firstPtr;
+ }
+ free(l2);
+ } else if (list2->firstPtr != NULL) {
+ /*
+ * We set the nextPtr of the last element of list 2 to be nil to make
+ * the loop less difficult. The loop simply goes through the entire
+ * second list creating new LstNodes and filling in the nextPtr, and
+ * prevPtr to fit into l1 and its datum field from the
+ * datum field of the corresponding element in l2. The 'last' node
+ * follows the last of the new nodes along until the entire l2 has
+ * been appended. Only then does the bookkeeping catch up with the
+ * changes. During the first iteration of the loop, if 'last' is nil,
+ * the first list must have been empty so the newly-created node is
+ * made the first node of the list.
+ */
+ list2->lastPtr->nextPtr = NULL;
+ for (last = list1->lastPtr, ln = list2->firstPtr;
+ ln != NULL;
+ ln = ln->nextPtr)
+ {
+ PAlloc (nln, ListNode);
+ nln->datum = ln->datum;
+ if (last != NULL) {
+ last->nextPtr = nln;
+ } else {
+ list1->firstPtr = nln;
+ }
+ nln->prevPtr = last;
+ nln->flags = nln->useCount = 0;
+ last = nln;
+ }
+
+ /*
+ * Finish bookkeeping. The last new element becomes the last element
+ * of list one.
+ */
+ list1->lastPtr = last;
+
+ /*
+ * The circularity of both list one and list two must be corrected
+ * for -- list one because of the new nodes added to it; list two
+ * because of the alteration of list2->lastPtr's nextPtr to ease the
+ * above for loop.
+ */
+ if (list1->isCirc) {
+ list1->lastPtr->nextPtr = list1->firstPtr;
+ list1->firstPtr->prevPtr = list1->lastPtr;
+ } else {
+ last->nextPtr = NULL;
+ }
+
+ if (list2->isCirc) {
+ list2->lastPtr->nextPtr = list2->firstPtr;
+ }
+ }
+
+ return (SUCCESS);
+}
+
diff --git a/usr.bin/make/lst.lib/lstDatum.c b/usr.bin/make/lst.lib/lstDatum.c
new file mode 100644
index 0000000..6e2d9ad
--- /dev/null
+++ b/usr.bin/make/lst.lib/lstDatum.c
@@ -0,0 +1,77 @@
+/* $NetBSD: lstDatum.c,v 1.13 2009/01/23 21:26:30 dsl Exp $ */
+
+/*
+ * Copyright (c) 1988, 1989, 1990, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Adam de Boor.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#ifndef MAKE_NATIVE
+static char rcsid[] = "$NetBSD: lstDatum.c,v 1.13 2009/01/23 21:26:30 dsl Exp $";
+#else
+#include <sys/cdefs.h>
+#ifndef lint
+#if 0
+static char sccsid[] = "@(#)lstDatum.c 8.1 (Berkeley) 6/6/93";
+#else
+__RCSID("$NetBSD: lstDatum.c,v 1.13 2009/01/23 21:26:30 dsl Exp $");
+#endif
+#endif /* not lint */
+#endif
+
+/*-
+ * LstDatum.c --
+ * Return the datum associated with a list node.
+ */
+
+#include "lstInt.h"
+
+/*-
+ *-----------------------------------------------------------------------
+ * Lst_Datum --
+ * Return the datum stored in the given node.
+ *
+ * Results:
+ * The datum or NULL if the node is invalid.
+ *
+ * Side Effects:
+ * None.
+ *
+ *-----------------------------------------------------------------------
+ */
+void *
+Lst_Datum(LstNode ln)
+{
+ if (ln != NULL) {
+ return ((ln)->datum);
+ } else {
+ return NULL;
+ }
+}
+
diff --git a/usr.bin/make/lst.lib/lstDeQueue.c b/usr.bin/make/lst.lib/lstDeQueue.c
new file mode 100644
index 0000000..bdb05cc
--- /dev/null
+++ b/usr.bin/make/lst.lib/lstDeQueue.c
@@ -0,0 +1,87 @@
+/* $NetBSD: lstDeQueue.c,v 1.14 2009/01/23 21:26:30 dsl Exp $ */
+
+/*
+ * Copyright (c) 1988, 1989, 1990, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Adam de Boor.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#ifndef MAKE_NATIVE
+static char rcsid[] = "$NetBSD: lstDeQueue.c,v 1.14 2009/01/23 21:26:30 dsl Exp $";
+#else
+#include <sys/cdefs.h>
+#ifndef lint
+#if 0
+static char sccsid[] = "@(#)lstDeQueue.c 8.1 (Berkeley) 6/6/93";
+#else
+__RCSID("$NetBSD: lstDeQueue.c,v 1.14 2009/01/23 21:26:30 dsl Exp $");
+#endif
+#endif /* not lint */
+#endif
+
+/*-
+ * LstDeQueue.c --
+ * Remove the node and return its datum from the head of the list
+ */
+
+#include "lstInt.h"
+
+/*-
+ *-----------------------------------------------------------------------
+ * Lst_DeQueue --
+ * Remove and return the datum at the head of the given list.
+ *
+ * Results:
+ * The datum in the node at the head or NULL if the list
+ * is empty.
+ *
+ * Side Effects:
+ * The head node is removed from the list.
+ *
+ *-----------------------------------------------------------------------
+ */
+void *
+Lst_DeQueue(Lst l)
+{
+ void *rd;
+ ListNode tln;
+
+ tln = Lst_First(l);
+ if (tln == NULL) {
+ return NULL;
+ }
+
+ rd = tln->datum;
+ if (Lst_Remove(l, tln) == FAILURE) {
+ return NULL;
+ } else {
+ return (rd);
+ }
+}
+
diff --git a/usr.bin/make/lst.lib/lstDestroy.c b/usr.bin/make/lst.lib/lstDestroy.c
new file mode 100644
index 0000000..92c5b2b
--- /dev/null
+++ b/usr.bin/make/lst.lib/lstDestroy.c
@@ -0,0 +1,101 @@
+/* $NetBSD: lstDestroy.c,v 1.16 2008/12/13 15:19:29 dsl Exp $ */
+
+/*
+ * Copyright (c) 1988, 1989, 1990, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Adam de Boor.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#ifndef MAKE_NATIVE
+static char rcsid[] = "$NetBSD: lstDestroy.c,v 1.16 2008/12/13 15:19:29 dsl Exp $";
+#else
+#include <sys/cdefs.h>
+#ifndef lint
+#if 0
+static char sccsid[] = "@(#)lstDestroy.c 8.1 (Berkeley) 6/6/93";
+#else
+__RCSID("$NetBSD: lstDestroy.c,v 1.16 2008/12/13 15:19:29 dsl Exp $");
+#endif
+#endif /* not lint */
+#endif
+
+/*-
+ * LstDestroy.c --
+ * Nuke a list and all its resources
+ */
+
+#include "lstInt.h"
+
+/*-
+ *-----------------------------------------------------------------------
+ * Lst_Destroy --
+ * Destroy a list and free all its resources. If the freeProc is
+ * given, it is called with the datum from each node in turn before
+ * the node is freed.
+ *
+ * Results:
+ * None.
+ *
+ * Side Effects:
+ * The given list is freed in its entirety.
+ *
+ *-----------------------------------------------------------------------
+ */
+void
+Lst_Destroy(Lst list, FreeProc *freeProc)
+{
+ ListNode ln;
+ ListNode tln = NULL;
+
+ if (list == NULL)
+ return;
+
+ /* To ease scanning */
+ if (list->lastPtr != NULL)
+ list->lastPtr->nextPtr = NULL;
+ else {
+ free(list);
+ return;
+ }
+
+ if (freeProc) {
+ for (ln = list->firstPtr; ln != NULL; ln = tln) {
+ tln = ln->nextPtr;
+ freeProc(ln->datum);
+ free(ln);
+ }
+ } else {
+ for (ln = list->firstPtr; ln != NULL; ln = tln) {
+ tln = ln->nextPtr;
+ free(ln);
+ }
+ }
+
+ free(list);
+}
diff --git a/usr.bin/make/lst.lib/lstDupl.c b/usr.bin/make/lst.lib/lstDupl.c
new file mode 100644
index 0000000..2174ff7
--- /dev/null
+++ b/usr.bin/make/lst.lib/lstDupl.c
@@ -0,0 +1,107 @@
+/* $NetBSD: lstDupl.c,v 1.16 2009/01/23 21:26:30 dsl Exp $ */
+
+/*
+ * Copyright (c) 1988, 1989, 1990, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Adam de Boor.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#ifndef MAKE_NATIVE
+static char rcsid[] = "$NetBSD: lstDupl.c,v 1.16 2009/01/23 21:26:30 dsl Exp $";
+#else
+#include <sys/cdefs.h>
+#ifndef lint
+#if 0
+static char sccsid[] = "@(#)lstDupl.c 8.1 (Berkeley) 6/6/93";
+#else
+__RCSID("$NetBSD: lstDupl.c,v 1.16 2009/01/23 21:26:30 dsl Exp $");
+#endif
+#endif /* not lint */
+#endif
+
+/*-
+ * listDupl.c --
+ * Duplicate a list. This includes duplicating the individual
+ * elements.
+ */
+
+#include "lstInt.h"
+
+/*-
+ *-----------------------------------------------------------------------
+ * Lst_Duplicate --
+ * Duplicate an entire list. If a function to copy a void *is
+ * given, the individual client elements will be duplicated as well.
+ *
+ * Input:
+ * l the list to duplicate
+ * copyProc A function to duplicate each void *
+ *
+ * Results:
+ * The new Lst structure or NULL if failure.
+ *
+ * Side Effects:
+ * A new list is created.
+ *-----------------------------------------------------------------------
+ */
+Lst
+Lst_Duplicate(Lst l, DuplicateProc *copyProc)
+{
+ Lst nl;
+ ListNode ln;
+ List list = l;
+
+ if (!LstValid (l)) {
+ return NULL;
+ }
+
+ nl = Lst_Init(list->isCirc);
+ if (nl == NULL) {
+ return NULL;
+ }
+
+ ln = list->firstPtr;
+ while (ln != NULL) {
+ if (copyProc != NULL) {
+ if (Lst_AtEnd(nl, copyProc(ln->datum)) == FAILURE) {
+ return NULL;
+ }
+ } else if (Lst_AtEnd(nl, ln->datum) == FAILURE) {
+ return NULL;
+ }
+
+ if (list->isCirc && ln == list->lastPtr) {
+ ln = NULL;
+ } else {
+ ln = ln->nextPtr;
+ }
+ }
+
+ return (nl);
+}
diff --git a/usr.bin/make/lst.lib/lstEnQueue.c b/usr.bin/make/lst.lib/lstEnQueue.c
new file mode 100644
index 0000000..be386c9
--- /dev/null
+++ b/usr.bin/make/lst.lib/lstEnQueue.c
@@ -0,0 +1,78 @@
+/* $NetBSD: lstEnQueue.c,v 1.13 2009/01/23 21:26:30 dsl Exp $ */
+
+/*
+ * Copyright (c) 1988, 1989, 1990, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Adam de Boor.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#ifndef MAKE_NATIVE
+static char rcsid[] = "$NetBSD: lstEnQueue.c,v 1.13 2009/01/23 21:26:30 dsl Exp $";
+#else
+#include <sys/cdefs.h>
+#ifndef lint
+#if 0
+static char sccsid[] = "@(#)lstEnQueue.c 8.1 (Berkeley) 6/6/93";
+#else
+__RCSID("$NetBSD: lstEnQueue.c,v 1.13 2009/01/23 21:26:30 dsl Exp $");
+#endif
+#endif /* not lint */
+#endif
+
+/*-
+ * LstEnQueue.c--
+ * Treat the list as a queue and place a datum at its end
+ */
+
+#include "lstInt.h"
+
+/*-
+ *-----------------------------------------------------------------------
+ * Lst_EnQueue --
+ * Add the datum to the tail of the given list.
+ *
+ * Results:
+ * SUCCESS or FAILURE as returned by Lst_InsertAfter.
+ *
+ * Side Effects:
+ * the lastPtr field is altered all the time and the firstPtr field
+ * will be altered if the list used to be empty.
+ *
+ *-----------------------------------------------------------------------
+ */
+ReturnStatus
+Lst_EnQueue(Lst l, void *d)
+{
+ if (LstValid (l) == FALSE) {
+ return (FAILURE);
+ }
+
+ return (Lst_InsertAfter(l, Lst_Last(l), d));
+}
+
diff --git a/usr.bin/make/lst.lib/lstFind.c b/usr.bin/make/lst.lib/lstFind.c
new file mode 100644
index 0000000..d07dbe7
--- /dev/null
+++ b/usr.bin/make/lst.lib/lstFind.c
@@ -0,0 +1,74 @@
+/* $NetBSD: lstFind.c,v 1.15 2009/01/23 21:58:28 dsl Exp $ */
+
+/*
+ * Copyright (c) 1988, 1989, 1990, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Adam de Boor.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#ifndef MAKE_NATIVE
+static char rcsid[] = "$NetBSD: lstFind.c,v 1.15 2009/01/23 21:58:28 dsl Exp $";
+#else
+#include <sys/cdefs.h>
+#ifndef lint
+#if 0
+static char sccsid[] = "@(#)lstFind.c 8.1 (Berkeley) 6/6/93";
+#else
+__RCSID("$NetBSD: lstFind.c,v 1.15 2009/01/23 21:58:28 dsl Exp $");
+#endif
+#endif /* not lint */
+#endif
+
+/*-
+ * LstFind.c --
+ * Find a node on a list.
+ */
+
+#include "lstInt.h"
+
+/*-
+ *-----------------------------------------------------------------------
+ * Lst_Find --
+ * Find a node on the given list using the given comparison function
+ * and the given datum.
+ *
+ * Results:
+ * The found node or NULL if none matches.
+ *
+ * Side Effects:
+ * None.
+ *
+ *-----------------------------------------------------------------------
+ */
+LstNode
+Lst_Find(Lst l, const void *d, int (*cProc)(const void *, const void *))
+{
+ return (Lst_FindFrom(l, Lst_First(l), d, cProc));
+}
+
diff --git a/usr.bin/make/lst.lib/lstFindFrom.c b/usr.bin/make/lst.lib/lstFindFrom.c
new file mode 100644
index 0000000..e2beab6
--- /dev/null
+++ b/usr.bin/make/lst.lib/lstFindFrom.c
@@ -0,0 +1,90 @@
+/* $NetBSD: lstFindFrom.c,v 1.15 2009/01/23 21:58:28 dsl Exp $ */
+
+/*
+ * Copyright (c) 1988, 1989, 1990, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Adam de Boor.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#ifndef MAKE_NATIVE
+static char rcsid[] = "$NetBSD: lstFindFrom.c,v 1.15 2009/01/23 21:58:28 dsl Exp $";
+#else
+#include <sys/cdefs.h>
+#ifndef lint
+#if 0
+static char sccsid[] = "@(#)lstFindFrom.c 8.1 (Berkeley) 6/6/93";
+#else
+__RCSID("$NetBSD: lstFindFrom.c,v 1.15 2009/01/23 21:58:28 dsl Exp $");
+#endif
+#endif /* not lint */
+#endif
+
+/*-
+ * LstFindFrom.c --
+ * Find a node on a list from a given starting point. Used by Lst_Find.
+ */
+
+#include "lstInt.h"
+
+/*-
+ *-----------------------------------------------------------------------
+ * Lst_FindFrom --
+ * Search for a node starting and ending with the given one on the
+ * given list using the passed datum and comparison function to
+ * determine when it has been found.
+ *
+ * Results:
+ * The found node or NULL
+ *
+ * Side Effects:
+ * None.
+ *
+ *-----------------------------------------------------------------------
+ */
+LstNode
+Lst_FindFrom(Lst l, LstNode ln, const void *d,
+ int (*cProc)(const void *, const void *))
+{
+ ListNode tln;
+
+ if (!LstValid (l) || LstIsEmpty (l) || !LstNodeValid (ln, l)) {
+ return NULL;
+ }
+
+ tln = ln;
+
+ do {
+ if ((*cProc)(tln->datum, d) == 0)
+ return (tln);
+ tln = tln->nextPtr;
+ } while (tln != ln && tln != NULL);
+
+ return NULL;
+}
+
diff --git a/usr.bin/make/lst.lib/lstFirst.c b/usr.bin/make/lst.lib/lstFirst.c
new file mode 100644
index 0000000..4e8334f
--- /dev/null
+++ b/usr.bin/make/lst.lib/lstFirst.c
@@ -0,0 +1,77 @@
+/* $NetBSD: lstFirst.c,v 1.12 2008/12/13 15:19:29 dsl Exp $ */
+
+/*
+ * Copyright (c) 1988, 1989, 1990, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Adam de Boor.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#ifndef MAKE_NATIVE
+static char rcsid[] = "$NetBSD: lstFirst.c,v 1.12 2008/12/13 15:19:29 dsl Exp $";
+#else
+#include <sys/cdefs.h>
+#ifndef lint
+#if 0
+static char sccsid[] = "@(#)lstFirst.c 8.1 (Berkeley) 6/6/93";
+#else
+__RCSID("$NetBSD: lstFirst.c,v 1.12 2008/12/13 15:19:29 dsl Exp $");
+#endif
+#endif /* not lint */
+#endif
+
+/*-
+ * LstFirst.c --
+ * Return the first node of a list
+ */
+
+#include "lstInt.h"
+
+/*-
+ *-----------------------------------------------------------------------
+ * Lst_First --
+ * Return the first node on the given list.
+ *
+ * Results:
+ * The first node or NULL if the list is empty.
+ *
+ * Side Effects:
+ * None.
+ *
+ *-----------------------------------------------------------------------
+ */
+LstNode
+Lst_First(Lst l)
+{
+ if (!LstValid (l) || LstIsEmpty (l)) {
+ return NULL;
+ } else {
+ return (l->firstPtr);
+ }
+}
+
diff --git a/usr.bin/make/lst.lib/lstForEach.c b/usr.bin/make/lst.lib/lstForEach.c
new file mode 100644
index 0000000..917e4ea
--- /dev/null
+++ b/usr.bin/make/lst.lib/lstForEach.c
@@ -0,0 +1,76 @@
+/* $NetBSD: lstForEach.c,v 1.13 2009/01/23 21:26:30 dsl Exp $ */
+
+/*
+ * Copyright (c) 1988, 1989, 1990, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Adam de Boor.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#ifndef MAKE_NATIVE
+static char rcsid[] = "$NetBSD: lstForEach.c,v 1.13 2009/01/23 21:26:30 dsl Exp $";
+#else
+#include <sys/cdefs.h>
+#ifndef lint
+#if 0
+static char sccsid[] = "@(#)lstForEach.c 8.1 (Berkeley) 6/6/93";
+#else
+__RCSID("$NetBSD: lstForEach.c,v 1.13 2009/01/23 21:26:30 dsl Exp $");
+#endif
+#endif /* not lint */
+#endif
+
+/*-
+ * LstForeach.c --
+ * Perform a given function on all elements of a list.
+ */
+
+#include "lstInt.h"
+
+/*-
+ *-----------------------------------------------------------------------
+ * Lst_ForEach --
+ * Apply the given function to each element of the given list. The
+ * function should return 0 if Lst_ForEach should continue and non-
+ * zero if it should abort.
+ *
+ * Results:
+ * None.
+ *
+ * Side Effects:
+ * Only those created by the passed-in function.
+ *
+ *-----------------------------------------------------------------------
+ */
+/*VARARGS2*/
+int
+Lst_ForEach(Lst l, int (*proc)(void *, void *), void *d)
+{
+ return Lst_ForEachFrom(l, Lst_First(l), proc, d);
+}
+
diff --git a/usr.bin/make/lst.lib/lstForEachFrom.c b/usr.bin/make/lst.lib/lstForEachFrom.c
new file mode 100644
index 0000000..c7f44ad
--- /dev/null
+++ b/usr.bin/make/lst.lib/lstForEachFrom.c
@@ -0,0 +1,125 @@
+/* $NetBSD: lstForEachFrom.c,v 1.17 2009/01/23 21:26:30 dsl Exp $ */
+
+/*
+ * Copyright (c) 1988, 1989, 1990, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Adam de Boor.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#ifndef MAKE_NATIVE
+static char rcsid[] = "$NetBSD: lstForEachFrom.c,v 1.17 2009/01/23 21:26:30 dsl Exp $";
+#else
+#include <sys/cdefs.h>
+#ifndef lint
+#if 0
+static char sccsid[] = "@(#)lstForEachFrom.c 8.1 (Berkeley) 6/6/93";
+#else
+__RCSID("$NetBSD: lstForEachFrom.c,v 1.17 2009/01/23 21:26:30 dsl Exp $");
+#endif
+#endif /* not lint */
+#endif
+
+/*-
+ * lstForEachFrom.c --
+ * Perform a given function on all elements of a list starting from
+ * a given point.
+ */
+
+#include "lstInt.h"
+
+/*-
+ *-----------------------------------------------------------------------
+ * Lst_ForEachFrom --
+ * Apply the given function to each element of the given list. The
+ * function should return 0 if traversal should continue and non-
+ * zero if it should abort.
+ *
+ * Results:
+ * None.
+ *
+ * Side Effects:
+ * Only those created by the passed-in function.
+ *
+ *-----------------------------------------------------------------------
+ */
+/*VARARGS2*/
+int
+Lst_ForEachFrom(Lst l, LstNode ln, int (*proc)(void *, void *),
+ void *d)
+{
+ ListNode tln = ln;
+ List list = l;
+ ListNode next;
+ Boolean done;
+ int result;
+
+ if (!LstValid (list) || LstIsEmpty (list)) {
+ return 0;
+ }
+
+ do {
+ /*
+ * Take care of having the current element deleted out from under
+ * us.
+ */
+
+ next = tln->nextPtr;
+
+ /*
+ * We're done with the traversal if
+ * - the next node to examine is the first in the queue or
+ * doesn't exist and
+ * - nothing's been added after the current node (check this
+ * after proc() has been called).
+ */
+ done = (next == NULL || next == list->firstPtr);
+
+ (void) tln->useCount++;
+ result = (*proc) (tln->datum, d);
+ (void) tln->useCount--;
+
+ /*
+ * Now check whether a node has been added.
+ * Note: this doesn't work if this node was deleted before
+ * the new node was added.
+ */
+ if (next != tln->nextPtr) {
+ next = tln->nextPtr;
+ done = 0;
+ }
+
+ if (tln->flags & LN_DELETED) {
+ free((char *)tln);
+ }
+ tln = next;
+ } while (!result && !LstIsEmpty(list) && !done);
+
+ return result;
+}
+
diff --git a/usr.bin/make/lst.lib/lstInit.c b/usr.bin/make/lst.lib/lstInit.c
new file mode 100644
index 0000000..f98ac42
--- /dev/null
+++ b/usr.bin/make/lst.lib/lstInit.c
@@ -0,0 +1,85 @@
+/* $NetBSD: lstInit.c,v 1.12 2008/12/13 15:19:29 dsl Exp $ */
+
+/*
+ * Copyright (c) 1988, 1989, 1990, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Adam de Boor.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#ifndef MAKE_NATIVE
+static char rcsid[] = "$NetBSD: lstInit.c,v 1.12 2008/12/13 15:19:29 dsl Exp $";
+#else
+#include <sys/cdefs.h>
+#ifndef lint
+#if 0
+static char sccsid[] = "@(#)lstInit.c 8.1 (Berkeley) 6/6/93";
+#else
+__RCSID("$NetBSD: lstInit.c,v 1.12 2008/12/13 15:19:29 dsl Exp $");
+#endif
+#endif /* not lint */
+#endif
+
+/*-
+ * init.c --
+ * Initialize a new linked list.
+ */
+
+#include "lstInt.h"
+
+/*-
+ *-----------------------------------------------------------------------
+ * Lst_Init --
+ * Create and initialize a new list.
+ *
+ * Input:
+ * circ TRUE if the list should be made circular
+ *
+ * Results:
+ * The created list.
+ *
+ * Side Effects:
+ * A list is created, what else?
+ *
+ *-----------------------------------------------------------------------
+ */
+Lst
+Lst_Init(Boolean circ)
+{
+ List nList;
+
+ PAlloc (nList, List);
+
+ nList->firstPtr = NULL;
+ nList->lastPtr = NULL;
+ nList->isOpen = FALSE;
+ nList->isCirc = circ;
+ nList->atEnd = Unknown;
+
+ return (nList);
+}
diff --git a/usr.bin/make/lst.lib/lstInsert.c b/usr.bin/make/lst.lib/lstInsert.c
new file mode 100644
index 0000000..77187bb
--- /dev/null
+++ b/usr.bin/make/lst.lib/lstInsert.c
@@ -0,0 +1,122 @@
+/* $NetBSD: lstInsert.c,v 1.14 2009/01/23 21:26:30 dsl Exp $ */
+
+/*
+ * Copyright (c) 1988, 1989, 1990, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Adam de Boor.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#ifndef MAKE_NATIVE
+static char rcsid[] = "$NetBSD: lstInsert.c,v 1.14 2009/01/23 21:26:30 dsl Exp $";
+#else
+#include <sys/cdefs.h>
+#ifndef lint
+#if 0
+static char sccsid[] = "@(#)lstInsert.c 8.1 (Berkeley) 6/6/93";
+#else
+__RCSID("$NetBSD: lstInsert.c,v 1.14 2009/01/23 21:26:30 dsl Exp $");
+#endif
+#endif /* not lint */
+#endif
+
+/*-
+ * LstInsert.c --
+ * Insert a new datum before an old one
+ */
+
+#include "lstInt.h"
+
+/*-
+ *-----------------------------------------------------------------------
+ * Lst_InsertBefore --
+ * Insert a new node with the given piece of data before the given
+ * node in the given list.
+ *
+ * Input:
+ * l list to manipulate
+ * ln node before which to insert d
+ * d datum to be inserted
+ *
+ * Results:
+ * SUCCESS or FAILURE.
+ *
+ * Side Effects:
+ * the firstPtr field will be changed if ln is the first node in the
+ * list.
+ *
+ *-----------------------------------------------------------------------
+ */
+ReturnStatus
+Lst_InsertBefore(Lst l, LstNode ln, void *d)
+{
+ ListNode nLNode; /* new lnode for d */
+ ListNode lNode = ln;
+ List list = l;
+
+
+ /*
+ * check validity of arguments
+ */
+ if (LstValid (l) && (LstIsEmpty (l) && ln == NULL))
+ goto ok;
+
+ if (!LstValid (l) || LstIsEmpty (l) || !LstNodeValid (ln, l)) {
+ return (FAILURE);
+ }
+
+ ok:
+ PAlloc (nLNode, ListNode);
+
+ nLNode->datum = d;
+ nLNode->useCount = nLNode->flags = 0;
+
+ if (ln == NULL) {
+ if (list->isCirc) {
+ nLNode->prevPtr = nLNode->nextPtr = nLNode;
+ } else {
+ nLNode->prevPtr = nLNode->nextPtr = NULL;
+ }
+ list->firstPtr = list->lastPtr = nLNode;
+ } else {
+ nLNode->prevPtr = lNode->prevPtr;
+ nLNode->nextPtr = lNode;
+
+ if (nLNode->prevPtr != NULL) {
+ nLNode->prevPtr->nextPtr = nLNode;
+ }
+ lNode->prevPtr = nLNode;
+
+ if (lNode == list->firstPtr) {
+ list->firstPtr = nLNode;
+ }
+ }
+
+ return (SUCCESS);
+}
+
diff --git a/usr.bin/make/lst.lib/lstInt.h b/usr.bin/make/lst.lib/lstInt.h
new file mode 100644
index 0000000..ac53dcb
--- /dev/null
+++ b/usr.bin/make/lst.lib/lstInt.h
@@ -0,0 +1,105 @@
+/* $NetBSD: lstInt.h,v 1.22 2014/09/07 20:55:34 joerg Exp $ */
+
+/*
+ * Copyright (c) 1988, 1989, 1990, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Adam de Boor.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * from: @(#)lstInt.h 8.1 (Berkeley) 6/6/93
+ */
+
+/*-
+ * lstInt.h --
+ * Internals for the list library
+ */
+#ifndef _LSTINT_H_
+#define _LSTINT_H_
+
+#include "../lst.h"
+#include "../make_malloc.h"
+
+typedef struct ListNode {
+ struct ListNode *prevPtr; /* previous element in list */
+ struct ListNode *nextPtr; /* next in list */
+ unsigned int useCount:8, /* Count of functions using the node.
+ * node may not be deleted until count
+ * goes to 0 */
+ flags:8; /* Node status flags */
+ void *datum; /* datum associated with this element */
+} *ListNode;
+/*
+ * Flags required for synchronization
+ */
+#define LN_DELETED 0x0001 /* List node should be removed when done */
+
+typedef enum {
+ Head, Middle, Tail, Unknown
+} Where;
+
+typedef struct List {
+ ListNode firstPtr; /* first node in list */
+ ListNode lastPtr; /* last node in list */
+ Boolean isCirc; /* true if the list should be considered
+ * circular */
+/*
+ * fields for sequential access
+ */
+ Where atEnd; /* Where in the list the last access was */
+ Boolean isOpen; /* true if list has been Lst_Open'ed */
+ ListNode curPtr; /* current node, if open. NULL if
+ * *just* opened */
+ ListNode prevPtr; /* Previous node, if open. Used by
+ * Lst_Remove */
+} *List;
+
+/*
+ * PAlloc (var, ptype) --
+ * Allocate a pointer-typedef structure 'ptype' into the variable 'var'
+ */
+#define PAlloc(var,ptype) var = (ptype) bmake_malloc(sizeof *(var))
+
+/*
+ * LstValid (l) --
+ * Return TRUE if the list l is valid
+ */
+#define LstValid(l) ((Lst)(l) != NULL)
+
+/*
+ * LstNodeValid (ln, l) --
+ * Return TRUE if the LstNode ln is valid with respect to l
+ */
+#define LstNodeValid(ln, l) ((ln) != NULL)
+
+/*
+ * LstIsEmpty (l) --
+ * TRUE if the list l is empty.
+ */
+#define LstIsEmpty(l) (((List)(l))->firstPtr == NULL)
+
+#endif /* _LSTINT_H_ */
diff --git a/usr.bin/make/lst.lib/lstIsAtEnd.c b/usr.bin/make/lst.lib/lstIsAtEnd.c
new file mode 100644
index 0000000..70270d2
--- /dev/null
+++ b/usr.bin/make/lst.lib/lstIsAtEnd.c
@@ -0,0 +1,87 @@
+/* $NetBSD: lstIsAtEnd.c,v 1.13 2008/02/15 21:29:50 christos Exp $ */
+
+/*
+ * Copyright (c) 1988, 1989, 1990, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Adam de Boor.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#ifndef MAKE_NATIVE
+static char rcsid[] = "$NetBSD: lstIsAtEnd.c,v 1.13 2008/02/15 21:29:50 christos Exp $";
+#else
+#include <sys/cdefs.h>
+#ifndef lint
+#if 0
+static char sccsid[] = "@(#)lstIsAtEnd.c 8.1 (Berkeley) 6/6/93";
+#else
+__RCSID("$NetBSD: lstIsAtEnd.c,v 1.13 2008/02/15 21:29:50 christos Exp $");
+#endif
+#endif /* not lint */
+#endif
+
+/*-
+ * LstIsAtEnd.c --
+ * Tell if the current node is at the end of the list.
+ * The sequential functions access the list in a slightly different way.
+ * CurPtr points to their idea of the current node in the list and they
+ * access the list based on it. Because the list is circular, Lst_Next
+ * and Lst_Prev will go around the list forever. Lst_IsAtEnd must be
+ * used to determine when to stop.
+ */
+
+#include "lstInt.h"
+
+/*-
+ *-----------------------------------------------------------------------
+ * Lst_IsAtEnd --
+ * Return true if have reached the end of the given list.
+ *
+ * Results:
+ * TRUE if at the end of the list (this includes the list not being
+ * open or being invalid) or FALSE if not. We return TRUE if the list
+ * is invalid or unopend so as to cause the caller to exit its loop
+ * asap, the assumption being that the loop is of the form
+ * while (!Lst_IsAtEnd (l)) {
+ * ...
+ * }
+ *
+ * Side Effects:
+ * None.
+ *
+ *-----------------------------------------------------------------------
+ */
+Boolean
+Lst_IsAtEnd(Lst l)
+{
+ List list = l;
+
+ return (!LstValid (l) || !list->isOpen ||
+ (list->atEnd == Head) || (list->atEnd == Tail));
+}
+
diff --git a/usr.bin/make/lst.lib/lstIsEmpty.c b/usr.bin/make/lst.lib/lstIsEmpty.c
new file mode 100644
index 0000000..8b1d6ed
--- /dev/null
+++ b/usr.bin/make/lst.lib/lstIsEmpty.c
@@ -0,0 +1,75 @@
+/* $NetBSD: lstIsEmpty.c,v 1.11 2008/12/13 15:19:29 dsl Exp $ */
+
+/*
+ * Copyright (c) 1988, 1989, 1990, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Adam de Boor.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#ifndef MAKE_NATIVE
+static char rcsid[] = "$NetBSD: lstIsEmpty.c,v 1.11 2008/12/13 15:19:29 dsl Exp $";
+#else
+#include <sys/cdefs.h>
+#ifndef lint
+#if 0
+static char sccsid[] = "@(#)lstIsEmpty.c 8.1 (Berkeley) 6/6/93";
+#else
+__RCSID("$NetBSD: lstIsEmpty.c,v 1.11 2008/12/13 15:19:29 dsl Exp $");
+#endif
+#endif /* not lint */
+#endif
+
+/*-
+ * LstIsEmpty.c --
+ * A single function to decide if a list is empty
+ */
+
+#include "lstInt.h"
+
+/*-
+ *-----------------------------------------------------------------------
+ * Lst_IsEmpty --
+ * Return TRUE if the given list is empty.
+ *
+ * Results:
+ * TRUE if the list is empty, FALSE otherwise.
+ *
+ * Side Effects:
+ * None.
+ *
+ * A list is considered empty if its firstPtr == NULL (or if
+ * the list itself is NULL).
+ *-----------------------------------------------------------------------
+ */
+Boolean
+Lst_IsEmpty(Lst l)
+{
+ return ( ! LstValid (l) || LstIsEmpty(l));
+}
+
diff --git a/usr.bin/make/lst.lib/lstLast.c b/usr.bin/make/lst.lib/lstLast.c
new file mode 100644
index 0000000..096ca24
--- /dev/null
+++ b/usr.bin/make/lst.lib/lstLast.c
@@ -0,0 +1,77 @@
+/* $NetBSD: lstLast.c,v 1.12 2008/12/13 15:19:29 dsl Exp $ */
+
+/*
+ * Copyright (c) 1988, 1989, 1990, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Adam de Boor.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#ifndef MAKE_NATIVE
+static char rcsid[] = "$NetBSD: lstLast.c,v 1.12 2008/12/13 15:19:29 dsl Exp $";
+#else
+#include <sys/cdefs.h>
+#ifndef lint
+#if 0
+static char sccsid[] = "@(#)lstLast.c 8.1 (Berkeley) 6/6/93";
+#else
+__RCSID("$NetBSD: lstLast.c,v 1.12 2008/12/13 15:19:29 dsl Exp $");
+#endif
+#endif /* not lint */
+#endif
+
+/*-
+ * LstLast.c --
+ * Return the last element of a list
+ */
+
+#include "lstInt.h"
+
+/*-
+ *-----------------------------------------------------------------------
+ * Lst_Last --
+ * Return the last node on the list l.
+ *
+ * Results:
+ * The requested node or NULL if the list is empty.
+ *
+ * Side Effects:
+ * None.
+ *
+ *-----------------------------------------------------------------------
+ */
+LstNode
+Lst_Last(Lst l)
+{
+ if (!LstValid(l) || LstIsEmpty (l)) {
+ return NULL;
+ } else {
+ return (l->lastPtr);
+ }
+}
+
diff --git a/usr.bin/make/lst.lib/lstMember.c b/usr.bin/make/lst.lib/lstMember.c
new file mode 100644
index 0000000..e9046ac
--- /dev/null
+++ b/usr.bin/make/lst.lib/lstMember.c
@@ -0,0 +1,77 @@
+/* $NetBSD: lstMember.c,v 1.14 2013/11/14 00:01:28 sjg Exp $ */
+
+/*
+ * Copyright (c) 1988, 1989, 1990, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Adam de Boor.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#ifndef MAKE_NATIVE
+static char rcsid[] = "$NetBSD: lstMember.c,v 1.14 2013/11/14 00:01:28 sjg Exp $";
+#else
+#include <sys/cdefs.h>
+#ifndef lint
+#if 0
+static char sccsid[] = "@(#)lstMember.c 8.1 (Berkeley) 6/6/93";
+#else
+__RCSID("$NetBSD: lstMember.c,v 1.14 2013/11/14 00:01:28 sjg Exp $");
+#endif
+#endif /* not lint */
+#endif
+
+/*-
+ * lstMember.c --
+ * See if a given datum is on a given list.
+ */
+
+#include "lstInt.h"
+
+LstNode
+Lst_Member(Lst l, void *d)
+{
+ List list = l;
+ ListNode lNode;
+
+ if (list == NULL) {
+ return NULL;
+ }
+ lNode = list->firstPtr;
+ if (lNode == NULL) {
+ return NULL;
+ }
+
+ do {
+ if (lNode->datum == d) {
+ return lNode;
+ }
+ lNode = lNode->nextPtr;
+ } while (lNode != NULL && lNode != list->firstPtr);
+
+ return NULL;
+}
diff --git a/usr.bin/make/lst.lib/lstNext.c b/usr.bin/make/lst.lib/lstNext.c
new file mode 100644
index 0000000..5c2e0ee
--- /dev/null
+++ b/usr.bin/make/lst.lib/lstNext.c
@@ -0,0 +1,120 @@
+/* $NetBSD: lstNext.c,v 1.12 2008/12/13 15:19:29 dsl Exp $ */
+
+/*
+ * Copyright (c) 1988, 1989, 1990, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Adam de Boor.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#ifndef MAKE_NATIVE
+static char rcsid[] = "$NetBSD: lstNext.c,v 1.12 2008/12/13 15:19:29 dsl Exp $";
+#else
+#include <sys/cdefs.h>
+#ifndef lint
+#if 0
+static char sccsid[] = "@(#)lstNext.c 8.1 (Berkeley) 6/6/93";
+#else
+__RCSID("$NetBSD: lstNext.c,v 1.12 2008/12/13 15:19:29 dsl Exp $");
+#endif
+#endif /* not lint */
+#endif
+
+/*-
+ * LstNext.c --
+ * Return the next node for a list.
+ * The sequential functions access the list in a slightly different way.
+ * CurPtr points to their idea of the current node in the list and they
+ * access the list based on it. Because the list is circular, Lst_Next
+ * and Lst_Prev will go around the list forever. Lst_IsAtEnd must be
+ * used to determine when to stop.
+ */
+
+#include "lstInt.h"
+
+/*-
+ *-----------------------------------------------------------------------
+ * Lst_Next --
+ * Return the next node for the given list.
+ *
+ * Results:
+ * The next node or NULL if the list has yet to be opened. Also
+ * if the list is non-circular and the end has been reached, NULL
+ * is returned.
+ *
+ * Side Effects:
+ * the curPtr field is updated.
+ *
+ *-----------------------------------------------------------------------
+ */
+LstNode
+Lst_Next(Lst l)
+{
+ ListNode tln;
+ List list = l;
+
+ if ((LstValid (l) == FALSE) ||
+ (list->isOpen == FALSE)) {
+ return NULL;
+ }
+
+ list->prevPtr = list->curPtr;
+
+ if (list->curPtr == NULL) {
+ if (list->atEnd == Unknown) {
+ /*
+ * If we're just starting out, atEnd will be Unknown.
+ * Then we want to start this thing off in the right
+ * direction -- at the start with atEnd being Middle.
+ */
+ list->curPtr = tln = list->firstPtr;
+ list->atEnd = Middle;
+ } else {
+ tln = NULL;
+ list->atEnd = Tail;
+ }
+ } else {
+ tln = list->curPtr->nextPtr;
+ list->curPtr = tln;
+
+ if (tln == list->firstPtr || tln == NULL) {
+ /*
+ * If back at the front, then we've hit the end...
+ */
+ list->atEnd = Tail;
+ } else {
+ /*
+ * Reset to Middle if gone past first.
+ */
+ list->atEnd = Middle;
+ }
+ }
+
+ return (tln);
+}
+
diff --git a/usr.bin/make/lst.lib/lstOpen.c b/usr.bin/make/lst.lib/lstOpen.c
new file mode 100644
index 0000000..941293e
--- /dev/null
+++ b/usr.bin/make/lst.lib/lstOpen.c
@@ -0,0 +1,87 @@
+/* $NetBSD: lstOpen.c,v 1.12 2008/12/13 15:19:29 dsl Exp $ */
+
+/*
+ * Copyright (c) 1988, 1989, 1990, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Adam de Boor.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#ifndef MAKE_NATIVE
+static char rcsid[] = "$NetBSD: lstOpen.c,v 1.12 2008/12/13 15:19:29 dsl Exp $";
+#else
+#include <sys/cdefs.h>
+#ifndef lint
+#if 0
+static char sccsid[] = "@(#)lstOpen.c 8.1 (Berkeley) 6/6/93";
+#else
+__RCSID("$NetBSD: lstOpen.c,v 1.12 2008/12/13 15:19:29 dsl Exp $");
+#endif
+#endif /* not lint */
+#endif
+
+/*-
+ * LstOpen.c --
+ * Open a list for sequential access. The sequential functions access the
+ * list in a slightly different way. CurPtr points to their idea of the
+ * current node in the list and they access the list based on it.
+ * If the list is circular, Lst_Next and Lst_Prev will go around
+ * the list forever. Lst_IsAtEnd must be used to determine when to stop.
+ */
+
+#include "lstInt.h"
+
+/*-
+ *-----------------------------------------------------------------------
+ * Lst_Open --
+ * Open a list for sequential access. A list can still be searched,
+ * etc., without confusing these functions.
+ *
+ * Results:
+ * SUCCESS or FAILURE.
+ *
+ * Side Effects:
+ * isOpen is set TRUE and curPtr is set to NULL so the
+ * other sequential functions no it was just opened and can choose
+ * the first element accessed based on this.
+ *
+ *-----------------------------------------------------------------------
+ */
+ReturnStatus
+Lst_Open(Lst l)
+{
+ if (LstValid (l) == FALSE) {
+ return (FAILURE);
+ }
+ (l)->isOpen = TRUE;
+ (l)->atEnd = LstIsEmpty (l) ? Head : Unknown;
+ (l)->curPtr = NULL;
+
+ return (SUCCESS);
+}
+
diff --git a/usr.bin/make/lst.lib/lstPrev.c b/usr.bin/make/lst.lib/lstPrev.c
new file mode 100644
index 0000000..0ec865d
--- /dev/null
+++ b/usr.bin/make/lst.lib/lstPrev.c
@@ -0,0 +1,79 @@
+/* $NetBSD: lstPrev.c,v 1.3 2008/12/13 15:19:29 dsl Exp $ */
+
+/*
+ * Copyright (c) 1988, 1989, 1990, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Adam de Boor.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#ifndef MAKE_NATIVE
+static char rcsid[] = "$NetBSD: lstPrev.c,v 1.3 2008/12/13 15:19:29 dsl Exp $";
+#else
+#include <sys/cdefs.h>
+#ifndef lint
+#if 0
+static char sccsid[] = "@(#)lstSucc.c 8.1 (Berkeley) 6/6/93";
+#else
+__RCSID("$NetBSD: lstPrev.c,v 1.3 2008/12/13 15:19:29 dsl Exp $");
+#endif
+#endif /* not lint */
+#endif
+
+/*-
+ * LstPrev.c --
+ * return the predecessor to a given node
+ */
+
+#include "lstInt.h"
+
+/*-
+ *-----------------------------------------------------------------------
+ * Lst_Prev --
+ * Return the predecessor to the given node on its list.
+ *
+ * Results:
+ * The predecessor of the node, if it exists (note that on a circular
+ * list, if the node is the only one in the list, it is its own
+ * predecessor).
+ *
+ * Side Effects:
+ * None.
+ *
+ *-----------------------------------------------------------------------
+ */
+LstNode
+Lst_Prev(LstNode ln)
+{
+ if (ln == NULL) {
+ return NULL;
+ } else {
+ return (ln->prevPtr);
+ }
+}
+
diff --git a/usr.bin/make/lst.lib/lstRemove.c b/usr.bin/make/lst.lib/lstRemove.c
new file mode 100644
index 0000000..7480d30
--- /dev/null
+++ b/usr.bin/make/lst.lib/lstRemove.c
@@ -0,0 +1,136 @@
+/* $NetBSD: lstRemove.c,v 1.16 2014/09/07 20:55:34 joerg Exp $ */
+
+/*
+ * Copyright (c) 1988, 1989, 1990, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Adam de Boor.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#ifndef MAKE_NATIVE
+static char rcsid[] = "$NetBSD: lstRemove.c,v 1.16 2014/09/07 20:55:34 joerg Exp $";
+#else
+#include <sys/cdefs.h>
+#ifndef lint
+#if 0
+static char sccsid[] = "@(#)lstRemove.c 8.1 (Berkeley) 6/6/93";
+#else
+__RCSID("$NetBSD: lstRemove.c,v 1.16 2014/09/07 20:55:34 joerg Exp $");
+#endif
+#endif /* not lint */
+#endif
+
+/*-
+ * LstRemove.c --
+ * Remove an element from a list
+ */
+
+#include "lstInt.h"
+
+/*-
+ *-----------------------------------------------------------------------
+ * Lst_Remove --
+ * Remove the given node from the given list.
+ *
+ * Results:
+ * SUCCESS or FAILURE.
+ *
+ * Side Effects:
+ * The list's firstPtr will be set to NULL if ln is the last
+ * node on the list. firsPtr and lastPtr will be altered if ln is
+ * either the first or last node, respectively, on the list.
+ *
+ *-----------------------------------------------------------------------
+ */
+ReturnStatus
+Lst_Remove(Lst l, LstNode ln)
+{
+ List list = l;
+ ListNode lNode = ln;
+
+ if (!LstValid (l) ||
+ !LstNodeValid (ln, l)) {
+ return (FAILURE);
+ }
+
+ /*
+ * unlink it from the list
+ */
+ if (lNode->nextPtr != NULL) {
+ lNode->nextPtr->prevPtr = lNode->prevPtr;
+ }
+ if (lNode->prevPtr != NULL) {
+ lNode->prevPtr->nextPtr = lNode->nextPtr;
+ }
+
+ /*
+ * if either the firstPtr or lastPtr of the list point to this node,
+ * adjust them accordingly
+ */
+ if (list->firstPtr == lNode) {
+ list->firstPtr = lNode->nextPtr;
+ }
+ if (list->lastPtr == lNode) {
+ list->lastPtr = lNode->prevPtr;
+ }
+
+ /*
+ * Sequential access stuff. If the node we're removing is the current
+ * node in the list, reset the current node to the previous one. If the
+ * previous one was non-existent (prevPtr == NULL), we set the
+ * end to be Unknown, since it is.
+ */
+ if (list->isOpen && (list->curPtr == lNode)) {
+ list->curPtr = list->prevPtr;
+ if (list->curPtr == NULL) {
+ list->atEnd = Unknown;
+ }
+ }
+
+ /*
+ * the only way firstPtr can still point to ln is if ln is the last
+ * node on the list (the list is circular, so lNode->nextptr == lNode in
+ * this case). The list is, therefore, empty and is marked as such
+ */
+ if (list->firstPtr == lNode) {
+ list->firstPtr = NULL;
+ }
+
+ /*
+ * note that the datum is unmolested. The caller must free it as
+ * necessary and as expected.
+ */
+ if (lNode->useCount == 0) {
+ free(ln);
+ } else {
+ lNode->flags |= LN_DELETED;
+ }
+
+ return (SUCCESS);
+}
+
diff --git a/usr.bin/make/lst.lib/lstReplace.c b/usr.bin/make/lst.lib/lstReplace.c
new file mode 100644
index 0000000..090e91a
--- /dev/null
+++ b/usr.bin/make/lst.lib/lstReplace.c
@@ -0,0 +1,78 @@
+/* $NetBSD: lstReplace.c,v 1.13 2009/01/23 21:26:30 dsl Exp $ */
+
+/*
+ * Copyright (c) 1988, 1989, 1990, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Adam de Boor.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#ifndef MAKE_NATIVE
+static char rcsid[] = "$NetBSD: lstReplace.c,v 1.13 2009/01/23 21:26:30 dsl Exp $";
+#else
+#include <sys/cdefs.h>
+#ifndef lint
+#if 0
+static char sccsid[] = "@(#)lstReplace.c 8.1 (Berkeley) 6/6/93";
+#else
+__RCSID("$NetBSD: lstReplace.c,v 1.13 2009/01/23 21:26:30 dsl Exp $");
+#endif
+#endif /* not lint */
+#endif
+
+/*-
+ * LstReplace.c --
+ * Replace the datum in a node with a new datum
+ */
+
+#include "lstInt.h"
+
+/*-
+ *-----------------------------------------------------------------------
+ * Lst_Replace --
+ * Replace the datum in the given node with the new datum
+ *
+ * Results:
+ * SUCCESS or FAILURE.
+ *
+ * Side Effects:
+ * The datum field fo the node is altered.
+ *
+ *-----------------------------------------------------------------------
+ */
+ReturnStatus
+Lst_Replace(LstNode ln, void *d)
+{
+ if (ln == NULL) {
+ return (FAILURE);
+ } else {
+ (ln)->datum = d;
+ return (SUCCESS);
+ }
+}
+
diff --git a/usr.bin/make/lst.lib/lstSucc.c b/usr.bin/make/lst.lib/lstSucc.c
new file mode 100644
index 0000000..3f13aa5
--- /dev/null
+++ b/usr.bin/make/lst.lib/lstSucc.c
@@ -0,0 +1,79 @@
+/* $NetBSD: lstSucc.c,v 1.13 2008/12/13 15:19:29 dsl Exp $ */
+
+/*
+ * Copyright (c) 1988, 1989, 1990, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Adam de Boor.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#ifndef MAKE_NATIVE
+static char rcsid[] = "$NetBSD: lstSucc.c,v 1.13 2008/12/13 15:19:29 dsl Exp $";
+#else
+#include <sys/cdefs.h>
+#ifndef lint
+#if 0
+static char sccsid[] = "@(#)lstSucc.c 8.1 (Berkeley) 6/6/93";
+#else
+__RCSID("$NetBSD: lstSucc.c,v 1.13 2008/12/13 15:19:29 dsl Exp $");
+#endif
+#endif /* not lint */
+#endif
+
+/*-
+ * LstSucc.c --
+ * return the successor to a given node
+ */
+
+#include "lstInt.h"
+
+/*-
+ *-----------------------------------------------------------------------
+ * Lst_Succ --
+ * Return the successor to the given node on its list.
+ *
+ * Results:
+ * The successor of the node, if it exists (note that on a circular
+ * list, if the node is the only one in the list, it is its own
+ * successor).
+ *
+ * Side Effects:
+ * None.
+ *
+ *-----------------------------------------------------------------------
+ */
+LstNode
+Lst_Succ(LstNode ln)
+{
+ if (ln == NULL) {
+ return NULL;
+ } else {
+ return (ln->nextPtr);
+ }
+}
+
diff --git a/usr.bin/make/main.c b/usr.bin/make/main.c
new file mode 100644
index 0000000..a9e756a
--- /dev/null
+++ b/usr.bin/make/main.c
@@ -0,0 +1,2189 @@
+/* $NetBSD: main.c,v 1.273 2017/10/28 21:54:54 sjg Exp $ */
+
+/*
+ * Copyright (c) 1988, 1989, 1990, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Adam de Boor.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+/*
+ * Copyright (c) 1989 by Berkeley Softworks
+ * All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Adam de Boor.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. All advertising materials mentioning features or use of this software
+ * must display the following acknowledgement:
+ * This product includes software developed by the University of
+ * California, Berkeley and its contributors.
+ * 4. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#ifndef MAKE_NATIVE
+static char rcsid[] = "$NetBSD: main.c,v 1.273 2017/10/28 21:54:54 sjg Exp $";
+#else
+#include <sys/cdefs.h>
+#ifndef lint
+__COPYRIGHT("@(#) Copyright (c) 1988, 1989, 1990, 1993\
+ The Regents of the University of California. All rights reserved.");
+#endif /* not lint */
+
+#ifndef lint
+#if 0
+static char sccsid[] = "@(#)main.c 8.3 (Berkeley) 3/19/94";
+#else
+__RCSID("$NetBSD: main.c,v 1.273 2017/10/28 21:54:54 sjg Exp $");
+#endif
+#endif /* not lint */
+#endif
+
+/*-
+ * main.c --
+ * The main file for this entire program. Exit routines etc
+ * reside here.
+ *
+ * Utility functions defined in this file:
+ * Main_ParseArgLine Takes a line of arguments, breaks them and
+ * treats them as if they were given when first
+ * invoked. Used by the parse module to implement
+ * the .MFLAGS target.
+ *
+ * Error Print a tagged error message. The global
+ * MAKE variable must have been defined. This
+ * takes a format string and optional arguments
+ * for it.
+ *
+ * Fatal Print an error message and exit. Also takes
+ * a format string and arguments for it.
+ *
+ * Punt Aborts all jobs and exits with a message. Also
+ * takes a format string and arguments for it.
+ *
+ * Finish Finish things up by printing the number of
+ * errors which occurred, as passed to it, and
+ * exiting.
+ */
+
+#include <sys/types.h>
+#include <sys/time.h>
+#include <sys/param.h>
+#include <sys/resource.h>
+#include <sys/stat.h>
+#ifdef MAKE_NATIVE
+#include <sys/sysctl.h>
+#endif
+#include <sys/utsname.h>
+#include <sys/wait.h>
+
+#include <errno.h>
+#include <signal.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <time.h>
+#include <ctype.h>
+
+#include "make.h"
+#include "hash.h"
+#include "dir.h"
+#include "job.h"
+#include "pathnames.h"
+#include "trace.h"
+
+#ifdef USE_IOVEC
+#include <sys/uio.h>
+#endif
+
+#ifndef DEFMAXLOCAL
+#define DEFMAXLOCAL DEFMAXJOBS
+#endif /* DEFMAXLOCAL */
+
+Lst create; /* Targets to be made */
+time_t now; /* Time at start of make */
+GNode *DEFAULT; /* .DEFAULT node */
+Boolean allPrecious; /* .PRECIOUS given on line by itself */
+Boolean deleteOnError; /* .DELETE_ON_ERROR: set */
+
+static Boolean noBuiltins; /* -r flag */
+static Lst makefiles; /* ordered list of makefiles to read */
+static int printVars; /* -[vV] argument */
+#define COMPAT_VARS 1
+#define EXPAND_VARS 2
+static Lst variables; /* list of variables to print */
+int maxJobs; /* -j argument */
+static int maxJobTokens; /* -j argument */
+Boolean compatMake; /* -B argument */
+int debug; /* -d argument */
+Boolean debugVflag; /* -dV */
+Boolean noExecute; /* -n flag */
+Boolean noRecursiveExecute; /* -N flag */
+Boolean keepgoing; /* -k flag */
+Boolean queryFlag; /* -q flag */
+Boolean touchFlag; /* -t flag */
+Boolean enterFlag; /* -w flag */
+Boolean enterFlagObj; /* -w and objdir != srcdir */
+Boolean ignoreErrors; /* -i flag */
+Boolean beSilent; /* -s flag */
+Boolean oldVars; /* variable substitution style */
+Boolean checkEnvFirst; /* -e flag */
+Boolean parseWarnFatal; /* -W flag */
+Boolean jobServer; /* -J flag */
+static int jp_0 = -1, jp_1 = -1; /* ends of parent job pipe */
+Boolean varNoExportEnv; /* -X flag */
+Boolean doing_depend; /* Set while reading .depend */
+static Boolean jobsRunning; /* TRUE if the jobs might be running */
+static const char * tracefile;
+static void MainParseArgs(int, char **);
+static int ReadMakefile(const void *, const void *);
+static void usage(void) MAKE_ATTR_DEAD;
+static void purge_cached_realpaths(void);
+
+static Boolean ignorePWD; /* if we use -C, PWD is meaningless */
+static char objdir[MAXPATHLEN + 1]; /* where we chdir'ed to */
+char curdir[MAXPATHLEN + 1]; /* Startup directory */
+char *progname; /* the program name */
+char *makeDependfile;
+pid_t myPid;
+int makelevel;
+
+Boolean forceJobs = FALSE;
+
+extern Lst parseIncPath;
+
+/*
+ * For compatibility with the POSIX version of MAKEFLAGS that includes
+ * all the options with out -, convert flags to -f -l -a -g -s.
+ */
+static char *
+explode(const char *flags)
+{
+ size_t len;
+ char *nf, *st;
+ const char *f;
+
+ if (flags == NULL)
+ return NULL;
+
+ for (f = flags; *f; f++)
+ if (!isalpha((unsigned char)*f))
+ break;
+
+ if (*f)
+ return bmake_strdup(flags);
+
+ len = strlen(flags);
+ st = nf = bmake_malloc(len * 3 + 1);
+ while (*flags) {
+ *nf++ = '-';
+ *nf++ = *flags++;
+ *nf++ = ' ';
+ }
+ *nf = '\0';
+ return st;
+}
+
+static void
+parse_debug_options(const char *argvalue)
+{
+ const char *modules;
+ const char *mode;
+ char *fname;
+ int len;
+
+ for (modules = argvalue; *modules; ++modules) {
+ switch (*modules) {
+ case 'A':
+ debug = ~0;
+ break;
+ case 'a':
+ debug |= DEBUG_ARCH;
+ break;
+ case 'C':
+ debug |= DEBUG_CWD;
+ break;
+ case 'c':
+ debug |= DEBUG_COND;
+ break;
+ case 'd':
+ debug |= DEBUG_DIR;
+ break;
+ case 'e':
+ debug |= DEBUG_ERROR;
+ break;
+ case 'f':
+ debug |= DEBUG_FOR;
+ break;
+ case 'g':
+ if (modules[1] == '1') {
+ debug |= DEBUG_GRAPH1;
+ ++modules;
+ }
+ else if (modules[1] == '2') {
+ debug |= DEBUG_GRAPH2;
+ ++modules;
+ }
+ else if (modules[1] == '3') {
+ debug |= DEBUG_GRAPH3;
+ ++modules;
+ }
+ break;
+ case 'j':
+ debug |= DEBUG_JOB;
+ break;
+ case 'l':
+ debug |= DEBUG_LOUD;
+ break;
+ case 'M':
+ debug |= DEBUG_META;
+ break;
+ case 'm':
+ debug |= DEBUG_MAKE;
+ break;
+ case 'n':
+ debug |= DEBUG_SCRIPT;
+ break;
+ case 'p':
+ debug |= DEBUG_PARSE;
+ break;
+ case 's':
+ debug |= DEBUG_SUFF;
+ break;
+ case 't':
+ debug |= DEBUG_TARG;
+ break;
+ case 'V':
+ debugVflag = TRUE;
+ break;
+ case 'v':
+ debug |= DEBUG_VAR;
+ break;
+ case 'x':
+ debug |= DEBUG_SHELL;
+ break;
+ case 'F':
+ if (debug_file != stdout && debug_file != stderr)
+ fclose(debug_file);
+ if (*++modules == '+') {
+ modules++;
+ mode = "a";
+ } else
+ mode = "w";
+ if (strcmp(modules, "stdout") == 0) {
+ debug_file = stdout;
+ goto debug_setbuf;
+ }
+ if (strcmp(modules, "stderr") == 0) {
+ debug_file = stderr;
+ goto debug_setbuf;
+ }
+ len = strlen(modules);
+ fname = bmake_malloc(len + 20);
+ memcpy(fname, modules, len + 1);
+ /* Let the filename be modified by the pid */
+ if (strcmp(fname + len - 3, ".%d") == 0)
+ snprintf(fname + len - 2, 20, "%d", getpid());
+ debug_file = fopen(fname, mode);
+ if (!debug_file) {
+ fprintf(stderr, "Cannot open debug file %s\n",
+ fname);
+ usage();
+ }
+ free(fname);
+ goto debug_setbuf;
+ default:
+ (void)fprintf(stderr,
+ "%s: illegal argument to d option -- %c\n",
+ progname, *modules);
+ usage();
+ }
+ }
+debug_setbuf:
+ /*
+ * Make the debug_file unbuffered, and make
+ * stdout line buffered (unless debugfile == stdout).
+ */
+ setvbuf(debug_file, NULL, _IONBF, 0);
+ if (debug_file != stdout) {
+ setvbuf(stdout, NULL, _IOLBF, 0);
+ }
+}
+
+/*
+ * does path contain any relative components
+ */
+static int
+is_relpath(const char *path)
+{
+ const char *cp;
+
+ if (path[0] != '/')
+ return TRUE;
+ cp = path;
+ do {
+ cp = strstr(cp, "/.");
+ if (!cp)
+ break;
+ cp += 2;
+ if (cp[0] == '/' || cp[0] == '\0')
+ return TRUE;
+ else if (cp[0] == '.') {
+ if (cp[1] == '/' || cp[1] == '\0')
+ return TRUE;
+ }
+ } while (cp);
+ return FALSE;
+}
+
+/*-
+ * MainParseArgs --
+ * Parse a given argument vector. Called from main() and from
+ * Main_ParseArgLine() when the .MAKEFLAGS target is used.
+ *
+ * XXX: Deal with command line overriding .MAKEFLAGS in makefile
+ *
+ * Results:
+ * None
+ *
+ * Side Effects:
+ * Various global and local flags will be set depending on the flags
+ * given
+ */
+static void
+MainParseArgs(int argc, char **argv)
+{
+ char *p;
+ int c = '?';
+ int arginc;
+ char *argvalue;
+ const char *getopt_def;
+ struct stat sa, sb;
+ char *optscan;
+ Boolean inOption, dashDash = FALSE;
+ char found_path[MAXPATHLEN + 1]; /* for searching for sys.mk */
+
+#define OPTFLAGS "BC:D:I:J:NST:V:WXd:ef:ij:km:nqrstv:w"
+/* Can't actually use getopt(3) because rescanning is not portable */
+
+ getopt_def = OPTFLAGS;
+rearg:
+ inOption = FALSE;
+ optscan = NULL;
+ while(argc > 1) {
+ char *getopt_spec;
+ if(!inOption)
+ optscan = argv[1];
+ c = *optscan++;
+ arginc = 0;
+ if(inOption) {
+ if(c == '\0') {
+ ++argv;
+ --argc;
+ inOption = FALSE;
+ continue;
+ }
+ } else {
+ if (c != '-' || dashDash)
+ break;
+ inOption = TRUE;
+ c = *optscan++;
+ }
+ /* '-' found at some earlier point */
+ getopt_spec = strchr(getopt_def, c);
+ if(c != '\0' && getopt_spec != NULL && getopt_spec[1] == ':') {
+ /* -<something> found, and <something> should have an arg */
+ inOption = FALSE;
+ arginc = 1;
+ argvalue = optscan;
+ if(*argvalue == '\0') {
+ if (argc < 3)
+ goto noarg;
+ argvalue = argv[2];
+ arginc = 2;
+ }
+ } else {
+ argvalue = NULL;
+ }
+ switch(c) {
+ case '\0':
+ arginc = 1;
+ inOption = FALSE;
+ break;
+ case 'B':
+ compatMake = TRUE;
+ Var_Append(MAKEFLAGS, "-B", VAR_GLOBAL);
+ Var_Set(MAKE_MODE, "compat", VAR_GLOBAL, 0);
+ break;
+ case 'C':
+ if (chdir(argvalue) == -1) {
+ (void)fprintf(stderr,
+ "%s: chdir %s: %s\n",
+ progname, argvalue,
+ strerror(errno));
+ exit(1);
+ }
+ if (getcwd(curdir, MAXPATHLEN) == NULL) {
+ (void)fprintf(stderr, "%s: %s.\n", progname, strerror(errno));
+ exit(2);
+ }
+ if (!is_relpath(argvalue) &&
+ stat(argvalue, &sa) != -1 &&
+ stat(curdir, &sb) != -1 &&
+ sa.st_ino == sb.st_ino &&
+ sa.st_dev == sb.st_dev)
+ strncpy(curdir, argvalue, MAXPATHLEN);
+ ignorePWD = TRUE;
+ break;
+ case 'D':
+ if (argvalue == NULL || argvalue[0] == 0) goto noarg;
+ Var_Set(argvalue, "1", VAR_GLOBAL, 0);
+ Var_Append(MAKEFLAGS, "-D", VAR_GLOBAL);
+ Var_Append(MAKEFLAGS, argvalue, VAR_GLOBAL);
+ break;
+ case 'I':
+ if (argvalue == NULL) goto noarg;
+ Parse_AddIncludeDir(argvalue);
+ Var_Append(MAKEFLAGS, "-I", VAR_GLOBAL);
+ Var_Append(MAKEFLAGS, argvalue, VAR_GLOBAL);
+ break;
+ case 'J':
+ if (argvalue == NULL) goto noarg;
+ if (sscanf(argvalue, "%d,%d", &jp_0, &jp_1) != 2) {
+ (void)fprintf(stderr,
+ "%s: internal error -- J option malformed (%s)\n",
+ progname, argvalue);
+ usage();
+ }
+ if ((fcntl(jp_0, F_GETFD, 0) < 0) ||
+ (fcntl(jp_1, F_GETFD, 0) < 0)) {
+#if 0
+ (void)fprintf(stderr,
+ "%s: ###### warning -- J descriptors were closed!\n",
+ progname);
+ exit(2);
+#endif
+ jp_0 = -1;
+ jp_1 = -1;
+ compatMake = TRUE;
+ } else {
+ Var_Append(MAKEFLAGS, "-J", VAR_GLOBAL);
+ Var_Append(MAKEFLAGS, argvalue, VAR_GLOBAL);
+ jobServer = TRUE;
+ }
+ break;
+ case 'N':
+ noExecute = TRUE;
+ noRecursiveExecute = TRUE;
+ Var_Append(MAKEFLAGS, "-N", VAR_GLOBAL);
+ break;
+ case 'S':
+ keepgoing = FALSE;
+ Var_Append(MAKEFLAGS, "-S", VAR_GLOBAL);
+ break;
+ case 'T':
+ if (argvalue == NULL) goto noarg;
+ tracefile = bmake_strdup(argvalue);
+ Var_Append(MAKEFLAGS, "-T", VAR_GLOBAL);
+ Var_Append(MAKEFLAGS, argvalue, VAR_GLOBAL);
+ break;
+ case 'V':
+ case 'v':
+ if (argvalue == NULL) goto noarg;
+ printVars = c == 'v' ? EXPAND_VARS : COMPAT_VARS;
+ (void)Lst_AtEnd(variables, argvalue);
+ Var_Append(MAKEFLAGS, "-V", VAR_GLOBAL);
+ Var_Append(MAKEFLAGS, argvalue, VAR_GLOBAL);
+ break;
+ case 'W':
+ parseWarnFatal = TRUE;
+ break;
+ case 'X':
+ varNoExportEnv = TRUE;
+ Var_Append(MAKEFLAGS, "-X", VAR_GLOBAL);
+ break;
+ case 'd':
+ if (argvalue == NULL) goto noarg;
+ /* If '-d-opts' don't pass to children */
+ if (argvalue[0] == '-')
+ argvalue++;
+ else {
+ Var_Append(MAKEFLAGS, "-d", VAR_GLOBAL);
+ Var_Append(MAKEFLAGS, argvalue, VAR_GLOBAL);
+ }
+ parse_debug_options(argvalue);
+ break;
+ case 'e':
+ checkEnvFirst = TRUE;
+ Var_Append(MAKEFLAGS, "-e", VAR_GLOBAL);
+ break;
+ case 'f':
+ if (argvalue == NULL) goto noarg;
+ (void)Lst_AtEnd(makefiles, argvalue);
+ break;
+ case 'i':
+ ignoreErrors = TRUE;
+ Var_Append(MAKEFLAGS, "-i", VAR_GLOBAL);
+ break;
+ case 'j':
+ if (argvalue == NULL) goto noarg;
+ forceJobs = TRUE;
+ maxJobs = strtol(argvalue, &p, 0);
+ if (*p != '\0' || maxJobs < 1) {
+ (void)fprintf(stderr, "%s: illegal argument to -j -- must be positive integer!\n",
+ progname);
+ exit(1);
+ }
+ Var_Append(MAKEFLAGS, "-j", VAR_GLOBAL);
+ Var_Append(MAKEFLAGS, argvalue, VAR_GLOBAL);
+ Var_Set(".MAKE.JOBS", argvalue, VAR_GLOBAL, 0);
+ maxJobTokens = maxJobs;
+ break;
+ case 'k':
+ keepgoing = TRUE;
+ Var_Append(MAKEFLAGS, "-k", VAR_GLOBAL);
+ break;
+ case 'm':
+ if (argvalue == NULL) goto noarg;
+ /* look for magic parent directory search string */
+ if (strncmp(".../", argvalue, 4) == 0) {
+ if (!Dir_FindHereOrAbove(curdir, argvalue+4,
+ found_path, sizeof(found_path)))
+ break; /* nothing doing */
+ (void)Dir_AddDir(sysIncPath, found_path);
+ } else {
+ (void)Dir_AddDir(sysIncPath, argvalue);
+ }
+ Var_Append(MAKEFLAGS, "-m", VAR_GLOBAL);
+ Var_Append(MAKEFLAGS, argvalue, VAR_GLOBAL);
+ break;
+ case 'n':
+ noExecute = TRUE;
+ Var_Append(MAKEFLAGS, "-n", VAR_GLOBAL);
+ break;
+ case 'q':
+ queryFlag = TRUE;
+ /* Kind of nonsensical, wot? */
+ Var_Append(MAKEFLAGS, "-q", VAR_GLOBAL);
+ break;
+ case 'r':
+ noBuiltins = TRUE;
+ Var_Append(MAKEFLAGS, "-r", VAR_GLOBAL);
+ break;
+ case 's':
+ beSilent = TRUE;
+ Var_Append(MAKEFLAGS, "-s", VAR_GLOBAL);
+ break;
+ case 't':
+ touchFlag = TRUE;
+ Var_Append(MAKEFLAGS, "-t", VAR_GLOBAL);
+ break;
+ case 'w':
+ enterFlag = TRUE;
+ Var_Append(MAKEFLAGS, "-w", VAR_GLOBAL);
+ break;
+ case '-':
+ dashDash = TRUE;
+ break;
+ default:
+ case '?':
+ usage();
+ }
+ argv += arginc;
+ argc -= arginc;
+ }
+
+ oldVars = TRUE;
+
+ /*
+ * See if the rest of the arguments are variable assignments and
+ * perform them if so. Else take them to be targets and stuff them
+ * on the end of the "create" list.
+ */
+ for (; argc > 1; ++argv, --argc)
+ if (Parse_IsVar(argv[1])) {
+ Parse_DoVar(argv[1], VAR_CMD);
+ } else {
+ if (!*argv[1])
+ Punt("illegal (null) argument.");
+ if (*argv[1] == '-' && !dashDash)
+ goto rearg;
+ (void)Lst_AtEnd(create, bmake_strdup(argv[1]));
+ }
+
+ return;
+noarg:
+ (void)fprintf(stderr, "%s: option requires an argument -- %c\n",
+ progname, c);
+ usage();
+}
+
+/*-
+ * Main_ParseArgLine --
+ * Used by the parse module when a .MFLAGS or .MAKEFLAGS target
+ * is encountered and by main() when reading the .MAKEFLAGS envariable.
+ * Takes a line of arguments and breaks it into its
+ * component words and passes those words and the number of them to the
+ * MainParseArgs function.
+ * The line should have all its leading whitespace removed.
+ *
+ * Input:
+ * line Line to fracture
+ *
+ * Results:
+ * None
+ *
+ * Side Effects:
+ * Only those that come from the various arguments.
+ */
+void
+Main_ParseArgLine(const char *line)
+{
+ char **argv; /* Manufactured argument vector */
+ int argc; /* Number of arguments in argv */
+ char *args; /* Space used by the args */
+ char *buf, *p1;
+ char *argv0 = Var_Value(".MAKE", VAR_GLOBAL, &p1);
+ size_t len;
+
+ if (line == NULL)
+ return;
+ for (; *line == ' '; ++line)
+ continue;
+ if (!*line)
+ return;
+
+ buf = bmake_malloc(len = strlen(line) + strlen(argv0) + 2);
+ (void)snprintf(buf, len, "%s %s", argv0, line);
+ free(p1);
+
+ argv = brk_string(buf, &argc, TRUE, &args);
+ if (argv == NULL) {
+ Error("Unterminated quoted string [%s]", buf);
+ free(buf);
+ return;
+ }
+ free(buf);
+ MainParseArgs(argc, argv);
+
+ free(args);
+ free(argv);
+}
+
+Boolean
+Main_SetObjdir(const char *fmt, ...)
+{
+ struct stat sb;
+ char *path;
+ char buf[MAXPATHLEN + 1];
+ char buf2[MAXPATHLEN + 1];
+ Boolean rc = FALSE;
+ va_list ap;
+
+ va_start(ap, fmt);
+ vsnprintf(path = buf, MAXPATHLEN, fmt, ap);
+ va_end(ap);
+
+ if (path[0] != '/') {
+ snprintf(buf2, MAXPATHLEN, "%s/%s", curdir, path);
+ path = buf2;
+ }
+
+ /* look for the directory and try to chdir there */
+ if (stat(path, &sb) == 0 && S_ISDIR(sb.st_mode)) {
+ if (chdir(path)) {
+ (void)fprintf(stderr, "make warning: %s: %s.\n",
+ path, strerror(errno));
+ } else {
+ strncpy(objdir, path, MAXPATHLEN);
+ Var_Set(".OBJDIR", objdir, VAR_GLOBAL, 0);
+ setenv("PWD", objdir, 1);
+ Dir_InitDot();
+ purge_cached_realpaths();
+ rc = TRUE;
+ if (enterFlag && strcmp(objdir, curdir) != 0)
+ enterFlagObj = TRUE;
+ }
+ }
+
+ return rc;
+}
+
+static Boolean
+Main_SetVarObjdir(const char *var, const char *suffix)
+{
+ char *p, *path, *xpath;
+
+ if ((path = Var_Value(var, VAR_CMD, &p)) == NULL ||
+ *path == '\0')
+ return FALSE;
+
+ /* expand variable substitutions */
+ if (strchr(path, '$') != 0)
+ xpath = Var_Subst(NULL, path, VAR_GLOBAL, VARF_WANTRES);
+ else
+ xpath = path;
+
+ (void)Main_SetObjdir("%s%s", xpath, suffix);
+
+ if (xpath != path)
+ free(xpath);
+ free(p);
+ return TRUE;
+}
+
+/*-
+ * ReadAllMakefiles --
+ * wrapper around ReadMakefile() to read all.
+ *
+ * Results:
+ * TRUE if ok, FALSE on error
+ */
+static int
+ReadAllMakefiles(const void *p, const void *q)
+{
+ return (ReadMakefile(p, q) == 0);
+}
+
+int
+str2Lst_Append(Lst lp, char *str, const char *sep)
+{
+ char *cp;
+ int n;
+
+ if (!sep)
+ sep = " \t";
+
+ for (n = 0, cp = strtok(str, sep); cp; cp = strtok(NULL, sep)) {
+ (void)Lst_AtEnd(lp, cp);
+ n++;
+ }
+ return (n);
+}
+
+#ifdef SIGINFO
+/*ARGSUSED*/
+static void
+siginfo(int signo MAKE_ATTR_UNUSED)
+{
+ char dir[MAXPATHLEN];
+ char str[2 * MAXPATHLEN];
+ int len;
+ if (getcwd(dir, sizeof(dir)) == NULL)
+ return;
+ len = snprintf(str, sizeof(str), "%s: Working in: %s\n", progname, dir);
+ if (len > 0)
+ (void)write(STDERR_FILENO, str, (size_t)len);
+}
+#endif
+
+/*
+ * Allow makefiles some control over the mode we run in.
+ */
+void
+MakeMode(const char *mode)
+{
+ char *mp = NULL;
+
+ if (!mode)
+ mode = mp = Var_Subst(NULL, "${" MAKE_MODE ":tl}",
+ VAR_GLOBAL, VARF_WANTRES);
+
+ if (mode && *mode) {
+ if (strstr(mode, "compat")) {
+ compatMake = TRUE;
+ forceJobs = FALSE;
+ }
+#if USE_META
+ if (strstr(mode, "meta"))
+ meta_mode_init(mode);
+#endif
+ }
+
+ free(mp);
+}
+
+static void
+doPrintVars(void)
+{
+ LstNode ln;
+ Boolean expandVars;
+
+ if (printVars == EXPAND_VARS)
+ expandVars = TRUE;
+ else if (debugVflag)
+ expandVars = FALSE;
+ else
+ expandVars = getBoolean(".MAKE.EXPAND_VARIABLES", FALSE);
+
+ for (ln = Lst_First(variables); ln != NULL;
+ ln = Lst_Succ(ln)) {
+ char *var = (char *)Lst_Datum(ln);
+ char *value;
+ char *p1;
+
+ if (strchr(var, '$')) {
+ value = p1 = Var_Subst(NULL, var, VAR_GLOBAL,
+ VARF_WANTRES);
+ } else if (expandVars) {
+ char tmp[128];
+ int len = snprintf(tmp, sizeof(tmp), "${%s}", var);
+
+ if (len >= (int)sizeof(tmp))
+ Fatal("%s: variable name too big: %s",
+ progname, var);
+ value = p1 = Var_Subst(NULL, tmp, VAR_GLOBAL,
+ VARF_WANTRES);
+ } else {
+ value = Var_Value(var, VAR_GLOBAL, &p1);
+ }
+ printf("%s\n", value ? value : "");
+ free(p1);
+ }
+}
+
+static Boolean
+runTargets(void)
+{
+ Lst targs; /* target nodes to create -- passed to Make_Init */
+ Boolean outOfDate; /* FALSE if all targets up to date */
+
+ /*
+ * Have now read the entire graph and need to make a list of
+ * targets to create. If none was given on the command line,
+ * we consult the parsing module to find the main target(s)
+ * to create.
+ */
+ if (Lst_IsEmpty(create))
+ targs = Parse_MainName();
+ else
+ targs = Targ_FindList(create, TARG_CREATE);
+
+ if (!compatMake) {
+ /*
+ * Initialize job module before traversing the graph
+ * now that any .BEGIN and .END targets have been read.
+ * This is done only if the -q flag wasn't given
+ * (to prevent the .BEGIN from being executed should
+ * it exist).
+ */
+ if (!queryFlag) {
+ Job_Init();
+ jobsRunning = TRUE;
+ }
+
+ /* Traverse the graph, checking on all the targets */
+ outOfDate = Make_Run(targs);
+ } else {
+ /*
+ * Compat_Init will take care of creating all the
+ * targets as well as initializing the module.
+ */
+ Compat_Run(targs);
+ outOfDate = FALSE;
+ }
+ Lst_Destroy(targs, NULL);
+ return outOfDate;
+}
+
+/*-
+ * main --
+ * The main function, for obvious reasons. Initializes variables
+ * and a few modules, then parses the arguments give it in the
+ * environment and on the command line. Reads the system makefile
+ * followed by either Makefile, makefile or the file given by the
+ * -f argument. Sets the .MAKEFLAGS PMake variable based on all the
+ * flags it has received by then uses either the Make or the Compat
+ * module to create the initial list of targets.
+ *
+ * Results:
+ * If -q was given, exits -1 if anything was out-of-date. Else it exits
+ * 0.
+ *
+ * Side Effects:
+ * The program exits when done. Targets are created. etc. etc. etc.
+ */
+int
+main(int argc, char **argv)
+{
+ Boolean outOfDate; /* FALSE if all targets up to date */
+ struct stat sb, sa;
+ char *p1, *path;
+ char mdpath[MAXPATHLEN];
+ const char *machine = getenv("MACHINE");
+ const char *machine_arch = getenv("MACHINE_ARCH");
+ char *syspath = getenv("MAKESYSPATH");
+ Lst sysMkPath; /* Path of sys.mk */
+ char *cp = NULL, *start;
+ /* avoid faults on read-only strings */
+ static char defsyspath[] = _PATH_DEFSYSPATH;
+ char found_path[MAXPATHLEN + 1]; /* for searching for sys.mk */
+ struct timeval rightnow; /* to initialize random seed */
+ struct utsname utsname;
+
+ /* default to writing debug to stderr */
+ debug_file = stderr;
+
+#ifdef SIGINFO
+ (void)bmake_signal(SIGINFO, siginfo);
+#endif
+ /*
+ * Set the seed to produce a different random sequence
+ * on each program execution.
+ */
+ gettimeofday(&rightnow, NULL);
+ srandom(rightnow.tv_sec + rightnow.tv_usec);
+
+ if ((progname = strrchr(argv[0], '/')) != NULL)
+ progname++;
+ else
+ progname = argv[0];
+#if defined(MAKE_NATIVE) || (defined(HAVE_SETRLIMIT) && defined(RLIMIT_NOFILE))
+ /*
+ * get rid of resource limit on file descriptors
+ */
+ {
+ struct rlimit rl;
+ if (getrlimit(RLIMIT_NOFILE, &rl) != -1 &&
+ rl.rlim_cur != rl.rlim_max) {
+ rl.rlim_cur = rl.rlim_max;
+ (void)setrlimit(RLIMIT_NOFILE, &rl);
+ }
+ }
+#endif
+
+ if (uname(&utsname) == -1) {
+ (void)fprintf(stderr, "%s: uname failed (%s).\n", progname,
+ strerror(errno));
+ exit(2);
+ }
+
+ /*
+ * Get the name of this type of MACHINE from utsname
+ * so we can share an executable for similar machines.
+ * (i.e. m68k: amiga hp300, mac68k, sun3, ...)
+ *
+ * Note that both MACHINE and MACHINE_ARCH are decided at
+ * run-time.
+ */
+ if (!machine) {
+#ifdef MAKE_NATIVE
+ machine = utsname.machine;
+#else
+#ifdef MAKE_MACHINE
+ machine = MAKE_MACHINE;
+#else
+ machine = "unknown";
+#endif
+#endif
+ }
+
+ if (!machine_arch) {
+#ifdef MAKE_NATIVE
+ static char machine_arch_buf[sizeof(utsname.machine)];
+ const int mib[2] = { CTL_HW, HW_MACHINE_ARCH };
+ size_t len = sizeof(machine_arch_buf);
+
+ if (sysctl(mib, __arraycount(mib), machine_arch_buf,
+ &len, NULL, 0) < 0) {
+ (void)fprintf(stderr, "%s: sysctl failed (%s).\n", progname,
+ strerror(errno));
+ exit(2);
+ }
+
+ machine_arch = machine_arch_buf;
+#else
+#ifndef MACHINE_ARCH
+#ifdef MAKE_MACHINE_ARCH
+ machine_arch = MAKE_MACHINE_ARCH;
+#else
+ machine_arch = "unknown";
+#endif
+#else
+ machine_arch = MACHINE_ARCH;
+#endif
+#endif
+ }
+
+ myPid = getpid(); /* remember this for vFork() */
+
+ /*
+ * Just in case MAKEOBJDIR wants us to do something tricky.
+ */
+ Var_Init(); /* Initialize the lists of variables for
+ * parsing arguments */
+ Var_Set(".MAKE.OS", utsname.sysname, VAR_GLOBAL, 0);
+ Var_Set("MACHINE", machine, VAR_GLOBAL, 0);
+ Var_Set("MACHINE_ARCH", machine_arch, VAR_GLOBAL, 0);
+#ifdef MAKE_VERSION
+ Var_Set("MAKE_VERSION", MAKE_VERSION, VAR_GLOBAL, 0);
+#endif
+ Var_Set(".newline", "\n", VAR_GLOBAL, 0); /* handy for :@ loops */
+ /*
+ * This is the traditional preference for makefiles.
+ */
+#ifndef MAKEFILE_PREFERENCE_LIST
+# define MAKEFILE_PREFERENCE_LIST "makefile Makefile"
+#endif
+ Var_Set(MAKEFILE_PREFERENCE, MAKEFILE_PREFERENCE_LIST,
+ VAR_GLOBAL, 0);
+ Var_Set(MAKE_DEPENDFILE, ".depend", VAR_GLOBAL, 0);
+
+ create = Lst_Init(FALSE);
+ makefiles = Lst_Init(FALSE);
+ printVars = 0;
+ debugVflag = FALSE;
+ variables = Lst_Init(FALSE);
+ beSilent = FALSE; /* Print commands as executed */
+ ignoreErrors = FALSE; /* Pay attention to non-zero returns */
+ noExecute = FALSE; /* Execute all commands */
+ noRecursiveExecute = FALSE; /* Execute all .MAKE targets */
+ keepgoing = FALSE; /* Stop on error */
+ allPrecious = FALSE; /* Remove targets when interrupted */
+ deleteOnError = FALSE; /* Historical default behavior */
+ queryFlag = FALSE; /* This is not just a check-run */
+ noBuiltins = FALSE; /* Read the built-in rules */
+ touchFlag = FALSE; /* Actually update targets */
+ debug = 0; /* No debug verbosity, please. */
+ jobsRunning = FALSE;
+
+ maxJobs = DEFMAXLOCAL; /* Set default local max concurrency */
+ maxJobTokens = maxJobs;
+ compatMake = FALSE; /* No compat mode */
+ ignorePWD = FALSE;
+
+ /*
+ * Initialize the parsing, directory and variable modules to prepare
+ * for the reading of inclusion paths and variable settings on the
+ * command line
+ */
+
+ /*
+ * Initialize various variables.
+ * MAKE also gets this name, for compatibility
+ * .MAKEFLAGS gets set to the empty string just in case.
+ * MFLAGS also gets initialized empty, for compatibility.
+ */
+ Parse_Init();
+ if (argv[0][0] == '/' || strchr(argv[0], '/') == NULL) {
+ /*
+ * Leave alone if it is an absolute path, or if it does
+ * not contain a '/' in which case we need to find it in
+ * the path, like execvp(3) and the shells do.
+ */
+ p1 = argv[0];
+ } else {
+ /*
+ * A relative path, canonicalize it.
+ */
+ p1 = cached_realpath(argv[0], mdpath);
+ if (!p1 || *p1 != '/' || stat(p1, &sb) < 0) {
+ p1 = argv[0]; /* realpath failed */
+ }
+ }
+ Var_Set("MAKE", p1, VAR_GLOBAL, 0);
+ Var_Set(".MAKE", p1, VAR_GLOBAL, 0);
+ Var_Set(MAKEFLAGS, "", VAR_GLOBAL, 0);
+ Var_Set(MAKEOVERRIDES, "", VAR_GLOBAL, 0);
+ Var_Set("MFLAGS", "", VAR_GLOBAL, 0);
+ Var_Set(".ALLTARGETS", "", VAR_GLOBAL, 0);
+ /* some makefiles need to know this */
+ Var_Set(MAKE_LEVEL ".ENV", MAKE_LEVEL_ENV, VAR_CMD, 0);
+
+ /*
+ * Set some other useful macros
+ */
+ {
+ char tmp[64], *ep;
+
+ makelevel = ((ep = getenv(MAKE_LEVEL_ENV)) && *ep) ? atoi(ep) : 0;
+ if (makelevel < 0)
+ makelevel = 0;
+ snprintf(tmp, sizeof(tmp), "%d", makelevel);
+ Var_Set(MAKE_LEVEL, tmp, VAR_GLOBAL, 0);
+ snprintf(tmp, sizeof(tmp), "%u", myPid);
+ Var_Set(".MAKE.PID", tmp, VAR_GLOBAL, 0);
+ snprintf(tmp, sizeof(tmp), "%u", getppid());
+ Var_Set(".MAKE.PPID", tmp, VAR_GLOBAL, 0);
+ }
+ if (makelevel > 0) {
+ char pn[1024];
+ snprintf(pn, sizeof(pn), "%s[%d]", progname, makelevel);
+ progname = bmake_strdup(pn);
+ }
+
+#ifdef USE_META
+ meta_init();
+#endif
+ Dir_Init(NULL); /* Dir_* safe to call from MainParseArgs */
+
+ /*
+ * First snag any flags out of the MAKE environment variable.
+ * (Note this is *not* MAKEFLAGS since /bin/make uses that and it's
+ * in a different format).
+ */
+#ifdef POSIX
+ p1 = explode(getenv("MAKEFLAGS"));
+ Main_ParseArgLine(p1);
+ free(p1);
+#else
+ Main_ParseArgLine(getenv("MAKE"));
+#endif
+
+ /*
+ * Find where we are (now).
+ * We take care of PWD for the automounter below...
+ */
+ if (getcwd(curdir, MAXPATHLEN) == NULL) {
+ (void)fprintf(stderr, "%s: getcwd: %s.\n",
+ progname, strerror(errno));
+ exit(2);
+ }
+
+ MainParseArgs(argc, argv);
+
+ if (enterFlag)
+ printf("%s: Entering directory `%s'\n", progname, curdir);
+
+ /*
+ * Verify that cwd is sane.
+ */
+ if (stat(curdir, &sa) == -1) {
+ (void)fprintf(stderr, "%s: %s: %s.\n",
+ progname, curdir, strerror(errno));
+ exit(2);
+ }
+
+ /*
+ * All this code is so that we know where we are when we start up
+ * on a different machine with pmake.
+ * Overriding getcwd() with $PWD totally breaks MAKEOBJDIRPREFIX
+ * since the value of curdir can vary depending on how we got
+ * here. Ie sitting at a shell prompt (shell that provides $PWD)
+ * or via subdir.mk in which case its likely a shell which does
+ * not provide it.
+ * So, to stop it breaking this case only, we ignore PWD if
+ * MAKEOBJDIRPREFIX is set or MAKEOBJDIR contains a transform.
+ */
+#ifndef NO_PWD_OVERRIDE
+ if (!ignorePWD) {
+ char *pwd, *ptmp1 = NULL, *ptmp2 = NULL;
+
+ if ((pwd = getenv("PWD")) != NULL &&
+ Var_Value("MAKEOBJDIRPREFIX", VAR_CMD, &ptmp1) == NULL) {
+ const char *makeobjdir = Var_Value("MAKEOBJDIR",
+ VAR_CMD, &ptmp2);
+
+ if (makeobjdir == NULL || !strchr(makeobjdir, '$')) {
+ if (stat(pwd, &sb) == 0 &&
+ sa.st_ino == sb.st_ino &&
+ sa.st_dev == sb.st_dev)
+ (void)strncpy(curdir, pwd, MAXPATHLEN);
+ }
+ }
+ free(ptmp1);
+ free(ptmp2);
+ }
+#endif
+ Var_Set(".CURDIR", curdir, VAR_GLOBAL, 0);
+
+ /*
+ * Find the .OBJDIR. If MAKEOBJDIRPREFIX, or failing that,
+ * MAKEOBJDIR is set in the environment, try only that value
+ * and fall back to .CURDIR if it does not exist.
+ *
+ * Otherwise, try _PATH_OBJDIR.MACHINE-MACHINE_ARCH, _PATH_OBJDIR.MACHINE,
+ * and * finally _PATH_OBJDIRPREFIX`pwd`, in that order. If none
+ * of these paths exist, just use .CURDIR.
+ */
+ Dir_Init(curdir);
+ (void)Main_SetObjdir("%s", curdir);
+
+ if (!Main_SetVarObjdir("MAKEOBJDIRPREFIX", curdir) &&
+ !Main_SetVarObjdir("MAKEOBJDIR", "") &&
+ !Main_SetObjdir("%s.%s-%s", _PATH_OBJDIR, machine, machine_arch) &&
+ !Main_SetObjdir("%s.%s", _PATH_OBJDIR, machine) &&
+ !Main_SetObjdir("%s", _PATH_OBJDIR))
+ (void)Main_SetObjdir("%s%s", _PATH_OBJDIRPREFIX, curdir);
+
+ /*
+ * Initialize archive, target and suffix modules in preparation for
+ * parsing the makefile(s)
+ */
+ Arch_Init();
+ Targ_Init();
+ Suff_Init();
+ Trace_Init(tracefile);
+
+ DEFAULT = NULL;
+ (void)time(&now);
+
+ Trace_Log(MAKESTART, NULL);
+
+ /*
+ * Set up the .TARGETS variable to contain the list of targets to be
+ * created. If none specified, make the variable empty -- the parser
+ * will fill the thing in with the default or .MAIN target.
+ */
+ if (!Lst_IsEmpty(create)) {
+ LstNode ln;
+
+ for (ln = Lst_First(create); ln != NULL;
+ ln = Lst_Succ(ln)) {
+ char *name = (char *)Lst_Datum(ln);
+
+ Var_Append(".TARGETS", name, VAR_GLOBAL);
+ }
+ } else
+ Var_Set(".TARGETS", "", VAR_GLOBAL, 0);
+
+
+ /*
+ * If no user-supplied system path was given (through the -m option)
+ * add the directories from the DEFSYSPATH (more than one may be given
+ * as dir1:...:dirn) to the system include path.
+ */
+ if (syspath == NULL || *syspath == '\0')
+ syspath = defsyspath;
+ else
+ syspath = bmake_strdup(syspath);
+
+ for (start = syspath; *start != '\0'; start = cp) {
+ for (cp = start; *cp != '\0' && *cp != ':'; cp++)
+ continue;
+ if (*cp == ':') {
+ *cp++ = '\0';
+ }
+ /* look for magic parent directory search string */
+ if (strncmp(".../", start, 4) != 0) {
+ (void)Dir_AddDir(defIncPath, start);
+ } else {
+ if (Dir_FindHereOrAbove(curdir, start+4,
+ found_path, sizeof(found_path))) {
+ (void)Dir_AddDir(defIncPath, found_path);
+ }
+ }
+ }
+ if (syspath != defsyspath)
+ free(syspath);
+
+ /*
+ * Read in the built-in rules first, followed by the specified
+ * makefile, if it was (makefile != NULL), or the default
+ * makefile and Makefile, in that order, if it wasn't.
+ */
+ if (!noBuiltins) {
+ LstNode ln;
+
+ sysMkPath = Lst_Init(FALSE);
+ Dir_Expand(_PATH_DEFSYSMK,
+ Lst_IsEmpty(sysIncPath) ? defIncPath : sysIncPath,
+ sysMkPath);
+ if (Lst_IsEmpty(sysMkPath))
+ Fatal("%s: no system rules (%s).", progname,
+ _PATH_DEFSYSMK);
+ ln = Lst_Find(sysMkPath, NULL, ReadMakefile);
+ if (ln == NULL)
+ Fatal("%s: cannot open %s.", progname,
+ (char *)Lst_Datum(ln));
+ }
+
+ if (!Lst_IsEmpty(makefiles)) {
+ LstNode ln;
+
+ ln = Lst_Find(makefiles, NULL, ReadAllMakefiles);
+ if (ln != NULL)
+ Fatal("%s: cannot open %s.", progname,
+ (char *)Lst_Datum(ln));
+ } else {
+ p1 = Var_Subst(NULL, "${" MAKEFILE_PREFERENCE "}",
+ VAR_CMD, VARF_WANTRES);
+ if (p1) {
+ (void)str2Lst_Append(makefiles, p1, NULL);
+ (void)Lst_Find(makefiles, NULL, ReadMakefile);
+ free(p1);
+ }
+ }
+
+ /* In particular suppress .depend for '-r -V .OBJDIR -f /dev/null' */
+ if (!noBuiltins || !printVars) {
+ makeDependfile = Var_Subst(NULL, "${.MAKE.DEPENDFILE:T}",
+ VAR_CMD, VARF_WANTRES);
+ doing_depend = TRUE;
+ (void)ReadMakefile(makeDependfile, NULL);
+ doing_depend = FALSE;
+ }
+
+ if (enterFlagObj)
+ printf("%s: Entering directory `%s'\n", progname, objdir);
+
+ MakeMode(NULL);
+
+ Var_Append("MFLAGS", Var_Value(MAKEFLAGS, VAR_GLOBAL, &p1), VAR_GLOBAL);
+ free(p1);
+
+ if (!forceJobs && !compatMake &&
+ Var_Exists(".MAKE.JOBS", VAR_GLOBAL)) {
+ char *value;
+ int n;
+
+ value = Var_Subst(NULL, "${.MAKE.JOBS}", VAR_GLOBAL, VARF_WANTRES);
+ n = strtol(value, NULL, 0);
+ if (n < 1) {
+ (void)fprintf(stderr, "%s: illegal value for .MAKE.JOBS -- must be positive integer!\n",
+ progname);
+ exit(1);
+ }
+ if (n != maxJobs) {
+ Var_Append(MAKEFLAGS, "-j", VAR_GLOBAL);
+ Var_Append(MAKEFLAGS, value, VAR_GLOBAL);
+ }
+ maxJobs = n;
+ maxJobTokens = maxJobs;
+ forceJobs = TRUE;
+ free(value);
+ }
+
+ /*
+ * Be compatible if user did not specify -j and did not explicitly
+ * turned compatibility on
+ */
+ if (!compatMake && !forceJobs) {
+ compatMake = TRUE;
+ }
+
+ if (!compatMake)
+ Job_ServerStart(maxJobTokens, jp_0, jp_1);
+ if (DEBUG(JOB))
+ fprintf(debug_file, "job_pipe %d %d, maxjobs %d, tokens %d, compat %d\n",
+ jp_0, jp_1, maxJobs, maxJobTokens, compatMake);
+
+ if (!printVars)
+ Main_ExportMAKEFLAGS(TRUE); /* initial export */
+
+
+ /*
+ * For compatibility, look at the directories in the VPATH variable
+ * and add them to the search path, if the variable is defined. The
+ * variable's value is in the same format as the PATH envariable, i.e.
+ * <directory>:<directory>:<directory>...
+ */
+ if (Var_Exists("VPATH", VAR_CMD)) {
+ char *vpath, savec;
+ /*
+ * GCC stores string constants in read-only memory, but
+ * Var_Subst will want to write this thing, so store it
+ * in an array
+ */
+ static char VPATH[] = "${VPATH}";
+
+ vpath = Var_Subst(NULL, VPATH, VAR_CMD, VARF_WANTRES);
+ path = vpath;
+ do {
+ /* skip to end of directory */
+ for (cp = path; *cp != ':' && *cp != '\0'; cp++)
+ continue;
+ /* Save terminator character so know when to stop */
+ savec = *cp;
+ *cp = '\0';
+ /* Add directory to search path */
+ (void)Dir_AddDir(dirSearchPath, path);
+ *cp = savec;
+ path = cp + 1;
+ } while (savec == ':');
+ free(vpath);
+ }
+
+ /*
+ * Now that all search paths have been read for suffixes et al, it's
+ * time to add the default search path to their lists...
+ */
+ Suff_DoPaths();
+
+ /*
+ * Propagate attributes through :: dependency lists.
+ */
+ Targ_Propagate();
+
+ /* print the initial graph, if the user requested it */
+ if (DEBUG(GRAPH1))
+ Targ_PrintGraph(1);
+
+ /* print the values of any variables requested by the user */
+ if (printVars) {
+ doPrintVars();
+ outOfDate = FALSE;
+ } else {
+ outOfDate = runTargets();
+ }
+
+#ifdef CLEANUP
+ Lst_Destroy(variables, NULL);
+ Lst_Destroy(makefiles, NULL);
+ Lst_Destroy(create, (FreeProc *)free);
+#endif
+
+ /* print the graph now it's been processed if the user requested it */
+ if (DEBUG(GRAPH2))
+ Targ_PrintGraph(2);
+
+ Trace_Log(MAKEEND, 0);
+
+ if (enterFlagObj)
+ printf("%s: Leaving directory `%s'\n", progname, objdir);
+ if (enterFlag)
+ printf("%s: Leaving directory `%s'\n", progname, curdir);
+
+#ifdef USE_META
+ meta_finish();
+#endif
+ Suff_End();
+ Targ_End();
+ Arch_End();
+ Var_End();
+ Parse_End();
+ Dir_End();
+ Job_End();
+ Trace_End();
+
+ return outOfDate ? 1 : 0;
+}
+
+/*-
+ * ReadMakefile --
+ * Open and parse the given makefile.
+ *
+ * Results:
+ * 0 if ok. -1 if couldn't open file.
+ *
+ * Side Effects:
+ * lots
+ */
+static int
+ReadMakefile(const void *p, const void *q MAKE_ATTR_UNUSED)
+{
+ const char *fname = p; /* makefile to read */
+ int fd;
+ size_t len = MAXPATHLEN;
+ char *name, *path = bmake_malloc(len);
+
+ if (!strcmp(fname, "-")) {
+ Parse_File(NULL /*stdin*/, -1);
+ Var_Set("MAKEFILE", "", VAR_INTERNAL, 0);
+ } else {
+ /* if we've chdir'd, rebuild the path name */
+ if (strcmp(curdir, objdir) && *fname != '/') {
+ size_t plen = strlen(curdir) + strlen(fname) + 2;
+ if (len < plen)
+ path = bmake_realloc(path, len = 2 * plen);
+
+ (void)snprintf(path, len, "%s/%s", curdir, fname);
+ fd = open(path, O_RDONLY);
+ if (fd != -1) {
+ fname = path;
+ goto found;
+ }
+
+ /* If curdir failed, try objdir (ala .depend) */
+ plen = strlen(objdir) + strlen(fname) + 2;
+ if (len < plen)
+ path = bmake_realloc(path, len = 2 * plen);
+ (void)snprintf(path, len, "%s/%s", objdir, fname);
+ fd = open(path, O_RDONLY);
+ if (fd != -1) {
+ fname = path;
+ goto found;
+ }
+ } else {
+ fd = open(fname, O_RDONLY);
+ if (fd != -1)
+ goto found;
+ }
+ /* look in -I and system include directories. */
+ name = Dir_FindFile(fname, parseIncPath);
+ if (!name)
+ name = Dir_FindFile(fname,
+ Lst_IsEmpty(sysIncPath) ? defIncPath : sysIncPath);
+ if (!name || (fd = open(name, O_RDONLY)) == -1) {
+ free(name);
+ free(path);
+ return(-1);
+ }
+ fname = name;
+ /*
+ * set the MAKEFILE variable desired by System V fans -- the
+ * placement of the setting here means it gets set to the last
+ * makefile specified, as it is set by SysV make.
+ */
+found:
+ if (!doing_depend)
+ Var_Set("MAKEFILE", fname, VAR_INTERNAL, 0);
+ Parse_File(fname, fd);
+ }
+ free(path);
+ return(0);
+}
+
+
+
+/*-
+ * Cmd_Exec --
+ * Execute the command in cmd, and return the output of that command
+ * in a string.
+ *
+ * Results:
+ * A string containing the output of the command, or the empty string
+ * If errnum is not NULL, it contains the reason for the command failure
+ *
+ * Side Effects:
+ * The string must be freed by the caller.
+ */
+char *
+Cmd_Exec(const char *cmd, const char **errnum)
+{
+ const char *args[4]; /* Args for invoking the shell */
+ int fds[2]; /* Pipe streams */
+ int cpid; /* Child PID */
+ int pid; /* PID from wait() */
+ char *res; /* result */
+ int status; /* command exit status */
+ Buffer buf; /* buffer to store the result */
+ char *cp;
+ int cc; /* bytes read, or -1 */
+ int savederr; /* saved errno */
+
+
+ *errnum = NULL;
+
+ if (!shellName)
+ Shell_Init();
+ /*
+ * Set up arguments for shell
+ */
+ args[0] = shellName;
+ args[1] = "-c";
+ args[2] = cmd;
+ args[3] = NULL;
+
+ /*
+ * Open a pipe for fetching its output
+ */
+ if (pipe(fds) == -1) {
+ *errnum = "Couldn't create pipe for \"%s\"";
+ goto bad;
+ }
+
+ /*
+ * Fork
+ */
+ switch (cpid = vFork()) {
+ case 0:
+ /*
+ * Close input side of pipe
+ */
+ (void)close(fds[0]);
+
+ /*
+ * Duplicate the output stream to the shell's output, then
+ * shut the extra thing down. Note we don't fetch the error
+ * stream...why not? Why?
+ */
+ (void)dup2(fds[1], 1);
+ (void)close(fds[1]);
+
+ Var_ExportVars();
+
+ (void)execv(shellPath, UNCONST(args));
+ _exit(1);
+ /*NOTREACHED*/
+
+ case -1:
+ *errnum = "Couldn't exec \"%s\"";
+ goto bad;
+
+ default:
+ /*
+ * No need for the writing half
+ */
+ (void)close(fds[1]);
+
+ savederr = 0;
+ Buf_Init(&buf, 0);
+
+ do {
+ char result[BUFSIZ];
+ cc = read(fds[0], result, sizeof(result));
+ if (cc > 0)
+ Buf_AddBytes(&buf, cc, result);
+ }
+ while (cc > 0 || (cc == -1 && errno == EINTR));
+ if (cc == -1)
+ savederr = errno;
+
+ /*
+ * Close the input side of the pipe.
+ */
+ (void)close(fds[0]);
+
+ /*
+ * Wait for the process to exit.
+ */
+ while(((pid = waitpid(cpid, &status, 0)) != cpid) && (pid >= 0)) {
+ JobReapChild(pid, status, FALSE);
+ continue;
+ }
+ cc = Buf_Size(&buf);
+ res = Buf_Destroy(&buf, FALSE);
+
+ if (savederr != 0)
+ *errnum = "Couldn't read shell's output for \"%s\"";
+
+ if (WIFSIGNALED(status))
+ *errnum = "\"%s\" exited on a signal";
+ else if (WEXITSTATUS(status) != 0)
+ *errnum = "\"%s\" returned non-zero status";
+
+ /*
+ * Null-terminate the result, convert newlines to spaces and
+ * install it in the variable.
+ */
+ res[cc] = '\0';
+ cp = &res[cc];
+
+ if (cc > 0 && *--cp == '\n') {
+ /*
+ * A final newline is just stripped
+ */
+ *cp-- = '\0';
+ }
+ while (cp >= res) {
+ if (*cp == '\n') {
+ *cp = ' ';
+ }
+ cp--;
+ }
+ break;
+ }
+ return res;
+bad:
+ res = bmake_malloc(1);
+ *res = '\0';
+ return res;
+}
+
+/*-
+ * Error --
+ * Print an error message given its format.
+ *
+ * Results:
+ * None.
+ *
+ * Side Effects:
+ * The message is printed.
+ */
+/* VARARGS */
+void
+Error(const char *fmt, ...)
+{
+ va_list ap;
+ FILE *err_file;
+
+ err_file = debug_file;
+ if (err_file == stdout)
+ err_file = stderr;
+ (void)fflush(stdout);
+ for (;;) {
+ va_start(ap, fmt);
+ fprintf(err_file, "%s: ", progname);
+ (void)vfprintf(err_file, fmt, ap);
+ va_end(ap);
+ (void)fprintf(err_file, "\n");
+ (void)fflush(err_file);
+ if (err_file == stderr)
+ break;
+ err_file = stderr;
+ }
+}
+
+/*-
+ * Fatal --
+ * Produce a Fatal error message. If jobs are running, waits for them
+ * to finish.
+ *
+ * Results:
+ * None
+ *
+ * Side Effects:
+ * The program exits
+ */
+/* VARARGS */
+void
+Fatal(const char *fmt, ...)
+{
+ va_list ap;
+
+ va_start(ap, fmt);
+ if (jobsRunning)
+ Job_Wait();
+
+ (void)fflush(stdout);
+ (void)vfprintf(stderr, fmt, ap);
+ va_end(ap);
+ (void)fprintf(stderr, "\n");
+ (void)fflush(stderr);
+
+ PrintOnError(NULL, NULL);
+
+ if (DEBUG(GRAPH2) || DEBUG(GRAPH3))
+ Targ_PrintGraph(2);
+ Trace_Log(MAKEERROR, 0);
+ exit(2); /* Not 1 so -q can distinguish error */
+}
+
+/*
+ * Punt --
+ * Major exception once jobs are being created. Kills all jobs, prints
+ * a message and exits.
+ *
+ * Results:
+ * None
+ *
+ * Side Effects:
+ * All children are killed indiscriminately and the program Lib_Exits
+ */
+/* VARARGS */
+void
+Punt(const char *fmt, ...)
+{
+ va_list ap;
+
+ va_start(ap, fmt);
+ (void)fflush(stdout);
+ (void)fprintf(stderr, "%s: ", progname);
+ (void)vfprintf(stderr, fmt, ap);
+ va_end(ap);
+ (void)fprintf(stderr, "\n");
+ (void)fflush(stderr);
+
+ PrintOnError(NULL, NULL);
+
+ DieHorribly();
+}
+
+/*-
+ * DieHorribly --
+ * Exit without giving a message.
+ *
+ * Results:
+ * None
+ *
+ * Side Effects:
+ * A big one...
+ */
+void
+DieHorribly(void)
+{
+ if (jobsRunning)
+ Job_AbortAll();
+ if (DEBUG(GRAPH2))
+ Targ_PrintGraph(2);
+ Trace_Log(MAKEERROR, 0);
+ exit(2); /* Not 1, so -q can distinguish error */
+}
+
+/*
+ * Finish --
+ * Called when aborting due to errors in child shell to signal
+ * abnormal exit.
+ *
+ * Results:
+ * None
+ *
+ * Side Effects:
+ * The program exits
+ */
+void
+Finish(int errors)
+ /* number of errors encountered in Make_Make */
+{
+ Fatal("%d error%s", errors, errors == 1 ? "" : "s");
+}
+
+/*
+ * eunlink --
+ * Remove a file carefully, avoiding directories.
+ */
+int
+eunlink(const char *file)
+{
+ struct stat st;
+
+ if (lstat(file, &st) == -1)
+ return -1;
+
+ if (S_ISDIR(st.st_mode)) {
+ errno = EISDIR;
+ return -1;
+ }
+ return unlink(file);
+}
+
+/*
+ * execError --
+ * Print why exec failed, avoiding stdio.
+ */
+void
+execError(const char *af, const char *av)
+{
+#ifdef USE_IOVEC
+ int i = 0;
+ struct iovec iov[8];
+#define IOADD(s) \
+ (void)(iov[i].iov_base = UNCONST(s), \
+ iov[i].iov_len = strlen(iov[i].iov_base), \
+ i++)
+#else
+#define IOADD(void)write(2, s, strlen(s))
+#endif
+
+ IOADD(progname);
+ IOADD(": ");
+ IOADD(af);
+ IOADD("(");
+ IOADD(av);
+ IOADD(") failed (");
+ IOADD(strerror(errno));
+ IOADD(")\n");
+
+#ifdef USE_IOVEC
+ while (writev(2, iov, 8) == -1 && errno == EAGAIN)
+ continue;
+#endif
+}
+
+/*
+ * usage --
+ * exit with usage message
+ */
+static void
+usage(void)
+{
+ char *p;
+ if ((p = strchr(progname, '[')) != NULL)
+ *p = '\0';
+
+ (void)fprintf(stderr,
+"usage: %s [-BeikNnqrstWwX] \n\
+ [-C directory] [-D variable] [-d flags] [-f makefile]\n\
+ [-I directory] [-J private] [-j max_jobs] [-m directory] [-T file]\n\
+ [-V variable] [-v variable] [variable=value] [target ...]\n",
+ progname);
+ exit(2);
+}
+
+/*
+ * realpath(3) can get expensive, cache results...
+ */
+static GNode *cached_realpaths = NULL;
+
+static GNode *
+get_cached_realpaths(void)
+{
+
+ if (!cached_realpaths) {
+ cached_realpaths = Targ_NewGN("Realpath");
+#ifndef DEBUG_REALPATH_CACHE
+ cached_realpaths->flags = INTERNAL;
+#endif
+ }
+
+ return cached_realpaths;
+}
+
+/* purge any relative paths */
+static void
+purge_cached_realpaths(void)
+{
+ GNode *cache = get_cached_realpaths();
+ Hash_Entry *he, *nhe;
+ Hash_Search hs;
+
+ he = Hash_EnumFirst(&cache->context, &hs);
+ while (he) {
+ nhe = Hash_EnumNext(&hs);
+ if (he->name[0] != '/') {
+ if (DEBUG(DIR))
+ fprintf(stderr, "cached_realpath: purging %s\n", he->name);
+ Hash_DeleteEntry(&cache->context, he);
+ }
+ he = nhe;
+ }
+}
+
+char *
+cached_realpath(const char *pathname, char *resolved)
+{
+ GNode *cache;
+ char *rp, *cp;
+
+ if (!pathname || !pathname[0])
+ return NULL;
+
+ cache = get_cached_realpaths();
+
+ if ((rp = Var_Value(pathname, cache, &cp)) != NULL) {
+ /* a hit */
+ strncpy(resolved, rp, MAXPATHLEN);
+ resolved[MAXPATHLEN - 1] = '\0';
+ } else if ((rp = realpath(pathname, resolved)) != NULL) {
+ Var_Set(pathname, rp, cache, 0);
+ } /* else should we negative-cache? */
+
+ free(cp);
+ return rp ? resolved : NULL;
+}
+
+int
+PrintAddr(void *a, void *b)
+{
+ printf("%lx ", (unsigned long) a);
+ return b ? 0 : 0;
+}
+
+
+static int
+addErrorCMD(void *cmdp, void *gnp)
+{
+ if (cmdp == NULL)
+ return 1; /* stop */
+ Var_Append(".ERROR_CMD", cmdp, VAR_GLOBAL);
+ return 0;
+}
+
+void
+PrintOnError(GNode *gn, const char *s)
+{
+ static GNode *en = NULL;
+ char tmp[64];
+ char *cp;
+
+ if (s)
+ printf("%s", s);
+
+ printf("\n%s: stopped in %s\n", progname, curdir);
+
+ if (en)
+ return; /* we've been here! */
+ if (gn) {
+ /*
+ * We can print this even if there is no .ERROR target.
+ */
+ Var_Set(".ERROR_TARGET", gn->name, VAR_GLOBAL, 0);
+ Var_Delete(".ERROR_CMD", VAR_GLOBAL);
+ Lst_ForEach(gn->commands, addErrorCMD, gn);
+ }
+ strncpy(tmp, "${MAKE_PRINT_VAR_ON_ERROR:@v@$v='${$v}'\n@}",
+ sizeof(tmp) - 1);
+ cp = Var_Subst(NULL, tmp, VAR_GLOBAL, VARF_WANTRES);
+ if (cp) {
+ if (*cp)
+ printf("%s", cp);
+ free(cp);
+ }
+ fflush(stdout);
+
+ /*
+ * Finally, see if there is a .ERROR target, and run it if so.
+ */
+ en = Targ_FindNode(".ERROR", TARG_NOCREATE);
+ if (en) {
+ en->type |= OP_SPECIAL;
+ Compat_Make(en, en);
+ }
+}
+
+void
+Main_ExportMAKEFLAGS(Boolean first)
+{
+ static int once = 1;
+ char tmp[64];
+ char *s;
+
+ if (once != first)
+ return;
+ once = 0;
+
+ strncpy(tmp, "${.MAKEFLAGS} ${.MAKEOVERRIDES:O:u:@v@$v=${$v:Q}@}",
+ sizeof(tmp));
+ s = Var_Subst(NULL, tmp, VAR_CMD, VARF_WANTRES);
+ if (s && *s) {
+#ifdef POSIX
+ setenv("MAKEFLAGS", s, 1);
+#else
+ setenv("MAKE", s, 1);
+#endif
+ }
+}
+
+char *
+getTmpdir(void)
+{
+ static char *tmpdir = NULL;
+
+ if (!tmpdir) {
+ struct stat st;
+
+ /*
+ * Honor $TMPDIR but only if it is valid.
+ * Ensure it ends with /.
+ */
+ tmpdir = Var_Subst(NULL, "${TMPDIR:tA:U" _PATH_TMP "}/", VAR_GLOBAL,
+ VARF_WANTRES);
+ if (stat(tmpdir, &st) < 0 || !S_ISDIR(st.st_mode)) {
+ free(tmpdir);
+ tmpdir = bmake_strdup(_PATH_TMP);
+ }
+ }
+ return tmpdir;
+}
+
+/*
+ * Create and open a temp file using "pattern".
+ * If "fnamep" is provided set it to a copy of the filename created.
+ * Otherwise unlink the file once open.
+ */
+int
+mkTempFile(const char *pattern, char **fnamep)
+{
+ static char *tmpdir = NULL;
+ char tfile[MAXPATHLEN];
+ int fd;
+
+ if (!pattern)
+ pattern = TMPPAT;
+ if (!tmpdir)
+ tmpdir = getTmpdir();
+ if (pattern[0] == '/') {
+ snprintf(tfile, sizeof(tfile), "%s", pattern);
+ } else {
+ snprintf(tfile, sizeof(tfile), "%s%s", tmpdir, pattern);
+ }
+ if ((fd = mkstemp(tfile)) < 0)
+ Punt("Could not create temporary file %s: %s", tfile, strerror(errno));
+ if (fnamep) {
+ *fnamep = bmake_strdup(tfile);
+ } else {
+ unlink(tfile); /* we just want the descriptor */
+ }
+ return fd;
+}
+
+/*
+ * Convert a string representation of a boolean.
+ * Anything that looks like "No", "False", "Off", "0" etc,
+ * is FALSE, otherwise TRUE.
+ */
+Boolean
+s2Boolean(const char *s, Boolean bf)
+{
+ if (s) {
+ switch(*s) {
+ case '\0': /* not set - the default wins */
+ break;
+ case '0':
+ case 'F':
+ case 'f':
+ case 'N':
+ case 'n':
+ bf = FALSE;
+ break;
+ case 'O':
+ case 'o':
+ switch (s[1]) {
+ case 'F':
+ case 'f':
+ bf = FALSE;
+ break;
+ default:
+ bf = TRUE;
+ break;
+ }
+ break;
+ default:
+ bf = TRUE;
+ break;
+ }
+ }
+ return (bf);
+}
+
+/*
+ * Return a Boolean based on setting of a knob.
+ *
+ * If the knob is not set, the supplied default is the return value.
+ * If set, anything that looks or smells like "No", "False", "Off", "0" etc,
+ * is FALSE, otherwise TRUE.
+ */
+Boolean
+getBoolean(const char *name, Boolean bf)
+{
+ char tmp[64];
+ char *cp;
+
+ if (snprintf(tmp, sizeof(tmp), "${%s:U:tl}", name) < (int)(sizeof(tmp))) {
+ cp = Var_Subst(NULL, tmp, VAR_GLOBAL, VARF_WANTRES);
+
+ if (cp) {
+ bf = s2Boolean(cp, bf);
+ free(cp);
+ }
+ }
+ return (bf);
+}
diff --git a/usr.bin/make/make.1 b/usr.bin/make/make.1
new file mode 100644
index 0000000..866631c
--- /dev/null
+++ b/usr.bin/make/make.1
@@ -0,0 +1,2413 @@
+.\" $NetBSD: make.1,v 1.273 2018/05/27 01:14:51 christos Exp $
+.\"
+.\" Copyright (c) 1990, 1993
+.\" The Regents of the University of California. All rights reserved.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\" notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must reproduce the above copyright
+.\" notice, this list of conditions and the following disclaimer in the
+.\" documentation and/or other materials provided with the distribution.
+.\" 3. Neither the name of the University nor the names of its contributors
+.\" may be used to endorse or promote products derived from this software
+.\" without specific prior written permission.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+.\" ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+.\" SUCH DAMAGE.
+.\"
+.\" from: @(#)make.1 8.4 (Berkeley) 3/19/94
+.\"
+.Dd May 26, 2018
+.Dt MAKE 1
+.Os
+.Sh NAME
+.Nm make
+.Nd maintain program dependencies
+.Sh SYNOPSIS
+.Nm
+.Op Fl BeikNnqrstWwX
+.Op Fl C Ar directory
+.Op Fl D Ar variable
+.Op Fl d Ar flags
+.Op Fl f Ar makefile
+.Op Fl I Ar directory
+.Op Fl J Ar private
+.Op Fl j Ar max_jobs
+.Op Fl m Ar directory
+.Op Fl T Ar file
+.Op Fl V Ar variable
+.Op Fl v Ar variable
+.Op Ar variable=value
+.Op Ar target ...
+.Sh DESCRIPTION
+.Nm
+is a program designed to simplify the maintenance of other programs.
+Its input is a list of specifications as to the files upon which programs
+and other files depend.
+If no
+.Fl f Ar makefile
+makefile option is given,
+.Nm
+will try to open
+.Ql Pa makefile
+then
+.Ql Pa Makefile
+in order to find the specifications.
+If the file
+.Ql Pa .depend
+exists, it is read (see
+.Xr mkdep 1 ) .
+.Pp
+This manual page is intended as a reference document only.
+For a more thorough description of
+.Nm
+and makefiles, please refer to
+.%T "PMake \- A Tutorial" .
+.Pp
+.Nm
+will prepend the contents of the
+.Va MAKEFLAGS
+environment variable to the command line arguments before parsing them.
+.Pp
+The options are as follows:
+.Bl -tag -width Ds
+.It Fl B
+Try to be backwards compatible by executing a single shell per command and
+by executing the commands to make the sources of a dependency line in sequence.
+.It Fl C Ar directory
+Change to
+.Ar directory
+before reading the makefiles or doing anything else.
+If multiple
+.Fl C
+options are specified, each is interpreted relative to the previous one:
+.Fl C Pa / Fl C Pa etc
+is equivalent to
+.Fl C Pa /etc .
+.It Fl D Ar variable
+Define
+.Ar variable
+to be 1, in the global context.
+.It Fl d Ar [-]flags
+Turn on debugging, and specify which portions of
+.Nm
+are to print debugging information.
+Unless the flags are preceded by
+.Ql \-
+they are added to the
+.Va MAKEFLAGS
+environment variable and will be processed by any child make processes.
+By default, debugging information is printed to standard error,
+but this can be changed using the
+.Ar F
+debugging flag.
+The debugging output is always unbuffered; in addition, if debugging
+is enabled but debugging output is not directed to standard output,
+then the standard output is line buffered.
+.Ar Flags
+is one or more of the following:
+.Bl -tag -width Ds
+.It Ar A
+Print all possible debugging information;
+equivalent to specifying all of the debugging flags.
+.It Ar a
+Print debugging information about archive searching and caching.
+.It Ar C
+Print debugging information about current working directory.
+.It Ar c
+Print debugging information about conditional evaluation.
+.It Ar d
+Print debugging information about directory searching and caching.
+.It Ar e
+Print debugging information about failed commands and targets.
+.It Ar F Ns Oo Sy \&+ Oc Ns Ar filename
+Specify where debugging output is written.
+This must be the last flag, because it consumes the remainder of
+the argument.
+If the character immediately after the
+.Ql F
+flag is
+.Ql \&+ ,
+then the file will be opened in append mode;
+otherwise the file will be overwritten.
+If the file name is
+.Ql stdout
+or
+.Ql stderr
+then debugging output will be written to the
+standard output or standard error output file descriptors respectively
+(and the
+.Ql \&+
+option has no effect).
+Otherwise, the output will be written to the named file.
+If the file name ends
+.Ql .%d
+then the
+.Ql %d
+is replaced by the pid.
+.It Ar f
+Print debugging information about loop evaluation.
+.It Ar "g1"
+Print the input graph before making anything.
+.It Ar "g2"
+Print the input graph after making everything, or before exiting
+on error.
+.It Ar "g3"
+Print the input graph before exiting on error.
+.It Ar j
+Print debugging information about running multiple shells.
+.It Ar l
+Print commands in Makefiles regardless of whether or not they are prefixed by
+.Ql @
+or other "quiet" flags.
+Also known as "loud" behavior.
+.It Ar M
+Print debugging information about "meta" mode decisions about targets.
+.It Ar m
+Print debugging information about making targets, including modification
+dates.
+.It Ar n
+Don't delete the temporary command scripts created when running commands.
+These temporary scripts are created in the directory
+referred to by the
+.Ev TMPDIR
+environment variable, or in
+.Pa /tmp
+if
+.Ev TMPDIR
+is unset or set to the empty string.
+The temporary scripts are created by
+.Xr mkstemp 3 ,
+and have names of the form
+.Pa makeXXXXXX .
+.Em NOTE :
+This can create many files in
+.Ev TMPDIR
+or
+.Pa /tmp ,
+so use with care.
+.It Ar p
+Print debugging information about makefile parsing.
+.It Ar s
+Print debugging information about suffix-transformation rules.
+.It Ar t
+Print debugging information about target list maintenance.
+.It Ar V
+Force the
+.Fl V
+option to print raw values of variables, overriding the default behavior
+set via
+.Va .MAKE.EXPAND_VARIABLES .
+.It Ar v
+Print debugging information about variable assignment.
+.It Ar x
+Run shell commands with
+.Fl x
+so the actual commands are printed as they are executed.
+.El
+.It Fl e
+Specify that environment variables override macro assignments within
+makefiles.
+.It Fl f Ar makefile
+Specify a makefile to read instead of the default
+.Ql Pa makefile .
+If
+.Ar makefile
+is
+.Ql Fl ,
+standard input is read.
+Multiple makefiles may be specified, and are read in the order specified.
+.It Fl I Ar directory
+Specify a directory in which to search for makefiles and included makefiles.
+The system makefile directory (or directories, see the
+.Fl m
+option) is automatically included as part of this list.
+.It Fl i
+Ignore non-zero exit of shell commands in the makefile.
+Equivalent to specifying
+.Ql Fl
+before each command line in the makefile.
+.It Fl J Ar private
+This option should
+.Em not
+be specified by the user.
+.Pp
+When the
+.Ar j
+option is in use in a recursive build, this option is passed by a make
+to child makes to allow all the make processes in the build to
+cooperate to avoid overloading the system.
+.It Fl j Ar max_jobs
+Specify the maximum number of jobs that
+.Nm
+may have running at any one time.
+The value is saved in
+.Va .MAKE.JOBS .
+Turns compatibility mode off, unless the
+.Ar B
+flag is also specified.
+When compatibility mode is off, all commands associated with a
+target are executed in a single shell invocation as opposed to the
+traditional one shell invocation per line.
+This can break traditional scripts which change directories on each
+command invocation and then expect to start with a fresh environment
+on the next line.
+It is more efficient to correct the scripts rather than turn backwards
+compatibility on.
+.It Fl k
+Continue processing after errors are encountered, but only on those targets
+that do not depend on the target whose creation caused the error.
+.It Fl m Ar directory
+Specify a directory in which to search for sys.mk and makefiles included
+via the
+.Ao Ar file Ac Ns -style
+include statement.
+The
+.Fl m
+option can be used multiple times to form a search path.
+This path will override the default system include path: /usr/share/mk.
+Furthermore the system include path will be appended to the search path used
+for
+.Qo Ar file Qc Ns -style
+include statements (see the
+.Fl I
+option).
+.Pp
+If a file or directory name in the
+.Fl m
+argument (or the
+.Ev MAKESYSPATH
+environment variable) starts with the string
+.Qq \&.../
+then
+.Nm
+will search for the specified file or directory named in the remaining part
+of the argument string.
+The search starts with the current directory of
+the Makefile and then works upward towards the root of the file system.
+If the search is successful, then the resulting directory replaces the
+.Qq \&.../
+specification in the
+.Fl m
+argument.
+If used, this feature allows
+.Nm
+to easily search in the current source tree for customized sys.mk files
+(e.g., by using
+.Qq \&.../mk/sys.mk
+as an argument).
+.It Fl n
+Display the commands that would have been executed, but do not
+actually execute them unless the target depends on the .MAKE special
+source (see below).
+.It Fl N
+Display the commands which would have been executed, but do not
+actually execute any of them; useful for debugging top-level makefiles
+without descending into subdirectories.
+.It Fl q
+Do not execute any commands, but exit 0 if the specified targets are
+up-to-date and 1, otherwise.
+.It Fl r
+Do not use the built-in rules specified in the system makefile.
+.It Fl s
+Do not echo any commands as they are executed.
+Equivalent to specifying
+.Ql Ic @
+before each command line in the makefile.
+.It Fl T Ar tracefile
+When used with the
+.Fl j
+flag,
+append a trace record to
+.Ar tracefile
+for each job started and completed.
+.It Fl t
+Rather than re-building a target as specified in the makefile, create it
+or update its modification time to make it appear up-to-date.
+.It Fl V Ar variable
+Print the value of
+.Ar variable .
+Do not build any targets.
+Multiple instances of this option may be specified;
+the variables will be printed one per line,
+with a blank line for each null or undefined variable.
+The value printed is extracted from the global context after all
+makefiles have been read.
+By default, the raw variable contents (which may
+include additional unexpanded variable references) are shown.
+If
+.Ar variable
+contains a
+.Ql \&$
+then the value will be recursively expanded to its complete resultant
+text before printing.
+The expanded value will also be printed if
+.Va .MAKE.EXPAND_VARIABLES
+is set to true and
+the
+.Fl dV
+option has not been used to override it.
+Note that loop-local and target-local variables, as well as values
+taken temporarily by global variables during makefile processing, are
+not accessible via this option.
+The
+.Fl dv
+debug mode can be used to see these at the cost of generating
+substantial extraneous output.
+.It Fl v Ar variable
+Like
+.Fl V
+but the variable is always expanded to its complete value.
+.It Fl W
+Treat any warnings during makefile parsing as errors.
+.It Fl w
+Print entering and leaving directory messages, pre and post processing.
+.It Fl X
+Don't export variables passed on the command line to the environment
+individually.
+Variables passed on the command line are still exported
+via the
+.Va MAKEFLAGS
+environment variable.
+This option may be useful on systems which have a small limit on the
+size of command arguments.
+.It Ar variable=value
+Set the value of the variable
+.Ar variable
+to
+.Ar value .
+Normally, all values passed on the command line are also exported to
+sub-makes in the environment.
+The
+.Fl X
+flag disables this behavior.
+Variable assignments should follow options for POSIX compatibility
+but no ordering is enforced.
+.El
+.Pp
+There are seven different types of lines in a makefile: file dependency
+specifications, shell commands, variable assignments, include statements,
+conditional directives, for loops, and comments.
+.Pp
+In general, lines may be continued from one line to the next by ending
+them with a backslash
+.Pq Ql \e .
+The trailing newline character and initial whitespace on the following
+line are compressed into a single space.
+.Sh FILE DEPENDENCY SPECIFICATIONS
+Dependency lines consist of one or more targets, an operator, and zero
+or more sources.
+This creates a relationship where the targets
+.Dq depend
+on the sources
+and are usually created from them.
+The exact relationship between the target and the source is determined
+by the operator that separates them.
+The three operators are as follows:
+.Bl -tag -width flag
+.It Ic \&:
+A target is considered out-of-date if its modification time is less than
+those of any of its sources.
+Sources for a target accumulate over dependency lines when this operator
+is used.
+The target is removed if
+.Nm
+is interrupted.
+.It Ic \&!
+Targets are always re-created, but not until all sources have been
+examined and re-created as necessary.
+Sources for a target accumulate over dependency lines when this operator
+is used.
+The target is removed if
+.Nm
+is interrupted.
+.It Ic \&::
+If no sources are specified, the target is always re-created.
+Otherwise, a target is considered out-of-date if any of its sources has
+been modified more recently than the target.
+Sources for a target do not accumulate over dependency lines when this
+operator is used.
+The target will not be removed if
+.Nm
+is interrupted.
+.El
+.Pp
+Targets and sources may contain the shell wildcard values
+.Ql \&? ,
+.Ql * ,
+.Ql [] ,
+and
+.Ql {} .
+The values
+.Ql \&? ,
+.Ql * ,
+and
+.Ql []
+may only be used as part of the final
+component of the target or source, and must be used to describe existing
+files.
+The value
+.Ql {}
+need not necessarily be used to describe existing files.
+Expansion is in directory order, not alphabetically as done in the shell.
+.Sh SHELL COMMANDS
+Each target may have associated with it one or more lines of shell
+commands, normally
+used to create the target.
+Each of the lines in this script
+.Em must
+be preceded by a tab.
+(For historical reasons, spaces are not accepted.)
+While targets can appear in many dependency lines if desired, by
+default only one of these rules may be followed by a creation
+script.
+If the
+.Ql Ic \&::
+operator is used, however, all rules may include scripts and the
+scripts are executed in the order found.
+.Pp
+Each line is treated as a separate shell command, unless the end of
+line is escaped with a backslash
+.Pq Ql \e
+in which case that line and the next are combined.
+.\" The escaped newline is retained and passed to the shell, which
+.\" normally ignores it.
+.\" However, the tab at the beginning of the following line is removed.
+If the first characters of the command are any combination of
+.Ql Ic @ ,
+.Ql Ic + ,
+or
+.Ql Ic \- ,
+the command is treated specially.
+A
+.Ql Ic @
+causes the command not to be echoed before it is executed.
+A
+.Ql Ic +
+causes the command to be executed even when
+.Fl n
+is given.
+This is similar to the effect of the .MAKE special source,
+except that the effect can be limited to a single line of a script.
+A
+.Ql Ic \-
+in compatibility mode
+causes any non-zero exit status of the command line to be ignored.
+.Pp
+When
+.Nm
+is run in jobs mode with
+.Fl j Ar max_jobs ,
+the entire script for the target is fed to a
+single instance of the shell.
+In compatibility (non-jobs) mode, each command is run in a separate process.
+If the command contains any shell meta characters
+.Pq Ql #=|^(){};&<>*?[]:$`\e\en
+it will be passed to the shell; otherwise
+.Nm
+will attempt direct execution.
+If a line starts with
+.Ql Ic \-
+and the shell has ErrCtl enabled then failure of the command line
+will be ignored as in compatibility mode.
+Otherwise
+.Ql Ic \-
+affects the entire job;
+the script will stop at the first command line that fails,
+but the target will not be deemed to have failed.
+.Pp
+Makefiles should be written so that the mode of
+.Nm
+operation does not change their behavior.
+For example, any command which needs to use
+.Dq cd
+or
+.Dq chdir
+without potentially changing the directory for subsequent commands
+should be put in parentheses so it executes in a subshell.
+To force the use of one shell, escape the line breaks so as to make
+the whole script one command.
+For example:
+.Bd -literal -offset indent
+avoid-chdir-side-effects:
+ @echo Building $@ in `pwd`
+ @(cd ${.CURDIR} && ${MAKE} $@)
+ @echo Back in `pwd`
+
+ensure-one-shell-regardless-of-mode:
+ @echo Building $@ in `pwd`; \e
+ (cd ${.CURDIR} && ${MAKE} $@); \e
+ echo Back in `pwd`
+.Ed
+.Pp
+Since
+.Nm
+will
+.Xr chdir 2
+to
+.Ql Va .OBJDIR
+before executing any targets, each child process
+starts with that as its current working directory.
+.Sh VARIABLE ASSIGNMENTS
+Variables in make are much like variables in the shell, and, by tradition,
+consist of all upper-case letters.
+.Ss Variable assignment modifiers
+The five operators that can be used to assign values to variables are as
+follows:
+.Bl -tag -width Ds
+.It Ic \&=
+Assign the value to the variable.
+Any previous value is overridden.
+.It Ic \&+=
+Append the value to the current value of the variable.
+.It Ic \&?=
+Assign the value to the variable if it is not already defined.
+.It Ic \&:=
+Assign with expansion, i.e. expand the value before assigning it
+to the variable.
+Normally, expansion is not done until the variable is referenced.
+.Em NOTE :
+References to undefined variables are
+.Em not
+expanded.
+This can cause problems when variable modifiers are used.
+.It Ic \&!=
+Expand the value and pass it to the shell for execution and assign
+the result to the variable.
+Any newlines in the result are replaced with spaces.
+.El
+.Pp
+Any white-space before the assigned
+.Ar value
+is removed; if the value is being appended, a single space is inserted
+between the previous contents of the variable and the appended value.
+.Pp
+Variables are expanded by surrounding the variable name with either
+curly braces
+.Pq Ql {}
+or parentheses
+.Pq Ql ()
+and preceding it with
+a dollar sign
+.Pq Ql \&$ .
+If the variable name contains only a single letter, the surrounding
+braces or parentheses are not required.
+This shorter form is not recommended.
+.Pp
+If the variable name contains a dollar, then the name itself is expanded first.
+This allows almost arbitrary variable names, however names containing dollar,
+braces, parenthesis, or whitespace are really best avoided!
+.Pp
+If the result of expanding a variable contains a dollar sign
+.Pq Ql \&$
+the string is expanded again.
+.Pp
+Variable substitution occurs at three distinct times, depending on where
+the variable is being used.
+.Bl -enum
+.It
+Variables in dependency lines are expanded as the line is read.
+.It
+Variables in shell commands are expanded when the shell command is
+executed.
+.It
+.Dq .for
+loop index variables are expanded on each loop iteration.
+Note that other variables are not expanded inside loops so
+the following example code:
+.Bd -literal -offset indent
+
+.Dv .for i in 1 2 3
+a+= ${i}
+j= ${i}
+b+= ${j}
+.Dv .endfor
+
+all:
+ @echo ${a}
+ @echo ${b}
+
+.Ed
+will print:
+.Bd -literal -offset indent
+1 2 3
+3 3 3
+
+.Ed
+Because while ${a} contains
+.Dq 1 2 3
+after the loop is executed, ${b}
+contains
+.Dq ${j} ${j} ${j}
+which expands to
+.Dq 3 3 3
+since after the loop completes ${j} contains
+.Dq 3 .
+.El
+.Ss Variable classes
+The four different classes of variables (in order of increasing precedence)
+are:
+.Bl -tag -width Ds
+.It Environment variables
+Variables defined as part of
+.Nm Ns 's
+environment.
+.It Global variables
+Variables defined in the makefile or in included makefiles.
+.It Command line variables
+Variables defined as part of the command line.
+.It Local variables
+Variables that are defined specific to a certain target.
+.El
+.Pp
+Local variables are all built in and their values vary magically from
+target to target.
+It is not currently possible to define new local variables.
+The seven local variables are as follows:
+.Bl -tag -width ".ARCHIVE" -offset indent
+.It Va .ALLSRC
+The list of all sources for this target; also known as
+.Ql Va \&> .
+.It Va .ARCHIVE
+The name of the archive file; also known as
+.Ql Va \&! .
+.It Va .IMPSRC
+In suffix-transformation rules, the name/path of the source from which the
+target is to be transformed (the
+.Dq implied
+source); also known as
+.Ql Va \&< .
+It is not defined in explicit rules.
+.It Va .MEMBER
+The name of the archive member; also known as
+.Ql Va % .
+.It Va .OODATE
+The list of sources for this target that were deemed out-of-date; also
+known as
+.Ql Va \&? .
+.It Va .PREFIX
+The file prefix of the target, containing only the file portion, no suffix
+or preceding directory components; also known as
+.Ql Va * .
+The suffix must be one of the known suffixes declared with
+.Ic .SUFFIXES
+or it will not be recognized.
+.It Va .TARGET
+The name of the target; also known as
+.Ql Va @ .
+For compatibility with other makes this is an alias for
+.Ic .ARCHIVE
+in archive member rules.
+.El
+.Pp
+The shorter forms
+.Ql ( Va > ,
+.Ql Va \&! ,
+.Ql Va < ,
+.Ql Va % ,
+.Ql Va \&? ,
+.Ql Va * ,
+and
+.Ql Va @ )
+are permitted for backward
+compatibility with historical makefiles and legacy POSIX make and are
+not recommended.
+.Pp
+Variants of these variables with the punctuation followed immediately by
+.Ql D
+or
+.Ql F ,
+e.g.
+.Ql Va $(@D) ,
+are legacy forms equivalent to using the
+.Ql :H
+and
+.Ql :T
+modifiers.
+These forms are accepted for compatibility with
+.At V
+makefiles and POSIX but are not recommended.
+.Pp
+Four of the local variables may be used in sources on dependency lines
+because they expand to the proper value for each target on the line.
+These variables are
+.Ql Va .TARGET ,
+.Ql Va .PREFIX ,
+.Ql Va .ARCHIVE ,
+and
+.Ql Va .MEMBER .
+.Ss Additional built-in variables
+In addition,
+.Nm
+sets or knows about the following variables:
+.Bl -tag -width .MAKEOVERRIDES
+.It Va \&$
+A single dollar sign
+.Ql \&$ ,
+i.e.
+.Ql \&$$
+expands to a single dollar
+sign.
+.It Va .ALLTARGETS
+The list of all targets encountered in the Makefile.
+If evaluated during
+Makefile parsing, lists only those targets encountered thus far.
+.It Va .CURDIR
+A path to the directory where
+.Nm
+was executed.
+Refer to the description of
+.Ql Ev PWD
+for more details.
+.It Va .INCLUDEDFROMDIR
+The directory of the file this Makefile was included from.
+.It Va .INCLUDEDFROMFILE
+The filename of the file this Makefile was included from.
+.It Ev MAKE
+The name that
+.Nm
+was executed with
+.Pq Va argv[0] .
+For compatibility
+.Nm
+also sets
+.Va .MAKE
+with the same value.
+The preferred variable to use is the environment variable
+.Ev MAKE
+because it is more compatible with other versions of
+.Nm
+and cannot be confused with the special target with the same name.
+.It Va .MAKE.DEPENDFILE
+Names the makefile (default
+.Ql Pa .depend )
+from which generated dependencies are read.
+.It Va .MAKE.EXPAND_VARIABLES
+A boolean that controls the default behavior of the
+.Fl V
+option.
+If true, variable values printed with
+.Fl V
+are fully expanded; if false, the raw variable contents (which may
+include additional unexpanded variable references) are shown.
+.It Va .MAKE.EXPORTED
+The list of variables exported by
+.Nm .
+.It Va .MAKE.JOBS
+The argument to the
+.Fl j
+option.
+.It Va .MAKE.JOB.PREFIX
+If
+.Nm
+is run with
+.Ar j
+then output for each target is prefixed with a token
+.Ql --- target ---
+the first part of which can be controlled via
+.Va .MAKE.JOB.PREFIX .
+If
+.Va .MAKE.JOB.PREFIX
+is empty, no token is printed.
+.br
+For example:
+.Li .MAKE.JOB.PREFIX=${.newline}---${.MAKE:T}[${.MAKE.PID}]
+would produce tokens like
+.Ql ---make[1234] target ---
+making it easier to track the degree of parallelism being achieved.
+.It Ev MAKEFLAGS
+The environment variable
+.Ql Ev MAKEFLAGS
+may contain anything that
+may be specified on
+.Nm Ns 's
+command line.
+Anything specified on
+.Nm Ns 's
+command line is appended to the
+.Ql Ev MAKEFLAGS
+variable which is then
+entered into the environment for all programs which
+.Nm
+executes.
+.It Va .MAKE.LEVEL
+The recursion depth of
+.Nm .
+The initial instance of
+.Nm
+will be 0, and an incremented value is put into the environment
+to be seen by the next generation.
+This allows tests like:
+.Li .if ${.MAKE.LEVEL} == 0
+to protect things which should only be evaluated in the initial instance of
+.Nm .
+.It Va .MAKE.MAKEFILE_PREFERENCE
+The ordered list of makefile names
+(default
+.Ql Pa makefile ,
+.Ql Pa Makefile )
+that
+.Nm
+will look for.
+.It Va .MAKE.MAKEFILES
+The list of makefiles read by
+.Nm ,
+which is useful for tracking dependencies.
+Each makefile is recorded only once, regardless of the number of times read.
+.It Va .MAKE.MODE
+Processed after reading all makefiles.
+Can affect the mode that
+.Nm
+runs in.
+It can contain a number of keywords:
+.Bl -hang -width missing-filemon=bf.
+.It Pa compat
+Like
+.Fl B ,
+puts
+.Nm
+into "compat" mode.
+.It Pa meta
+Puts
+.Nm
+into "meta" mode, where meta files are created for each target
+to capture the command run, the output generated and if
+.Xr filemon 4
+is available, the system calls which are of interest to
+.Nm .
+The captured output can be very useful when diagnosing errors.
+.It Pa curdirOk= Ar bf
+Normally
+.Nm
+will not create .meta files in
+.Ql Va .CURDIR .
+This can be overridden by setting
+.Va bf
+to a value which represents True.
+.It Pa missing-meta= Ar bf
+If
+.Va bf
+is True, then a missing .meta file makes the target out-of-date.
+.It Pa missing-filemon= Ar bf
+If
+.Va bf
+is True, then missing filemon data makes the target out-of-date.
+.It Pa nofilemon
+Do not use
+.Xr filemon 4 .
+.It Pa env
+For debugging, it can be useful to include the environment
+in the .meta file.
+.It Pa verbose
+If in "meta" mode, print a clue about the target being built.
+This is useful if the build is otherwise running silently.
+The message printed the value of:
+.Va .MAKE.META.PREFIX .
+.It Pa ignore-cmd
+Some makefiles have commands which are simply not stable.
+This keyword causes them to be ignored for
+determining whether a target is out of date in "meta" mode.
+See also
+.Ic .NOMETA_CMP .
+.It Pa silent= Ar bf
+If
+.Va bf
+is True, when a .meta file is created, mark the target
+.Ic .SILENT .
+.El
+.It Va .MAKE.META.BAILIWICK
+In "meta" mode, provides a list of prefixes which
+match the directories controlled by
+.Nm .
+If a file that was generated outside of
+.Va .OBJDIR
+but within said bailiwick is missing,
+the current target is considered out-of-date.
+.It Va .MAKE.META.CREATED
+In "meta" mode, this variable contains a list of all the meta files
+updated.
+If not empty, it can be used to trigger processing of
+.Va .MAKE.META.FILES .
+.It Va .MAKE.META.FILES
+In "meta" mode, this variable contains a list of all the meta files
+used (updated or not).
+This list can be used to process the meta files to extract dependency
+information.
+.It Va .MAKE.META.IGNORE_PATHS
+Provides a list of path prefixes that should be ignored;
+because the contents are expected to change over time.
+The default list includes:
+.Ql Pa /dev /etc /proc /tmp /var/run /var/tmp
+.It Va .MAKE.META.IGNORE_PATTERNS
+Provides a list of patterns to match against pathnames.
+Ignore any that match.
+.It Va .MAKE.META.IGNORE_FILTER
+Provides a list of variable modifiers to apply to each pathname.
+Ignore if the expansion is an empty string.
+.It Va .MAKE.META.PREFIX
+Defines the message printed for each meta file updated in "meta verbose" mode.
+The default value is:
+.Dl Building ${.TARGET:H:tA}/${.TARGET:T}
+.It Va .MAKEOVERRIDES
+This variable is used to record the names of variables assigned to
+on the command line, so that they may be exported as part of
+.Ql Ev MAKEFLAGS .
+This behavior can be disabled by assigning an empty value to
+.Ql Va .MAKEOVERRIDES
+within a makefile.
+Extra variables can be exported from a makefile
+by appending their names to
+.Ql Va .MAKEOVERRIDES .
+.Ql Ev MAKEFLAGS
+is re-exported whenever
+.Ql Va .MAKEOVERRIDES
+is modified.
+.It Va .MAKE.PATH_FILEMON
+If
+.Nm
+was built with
+.Xr filemon 4
+support, this is set to the path of the device node.
+This allows makefiles to test for this support.
+.It Va .MAKE.PID
+The process-id of
+.Nm .
+.It Va .MAKE.PPID
+The parent process-id of
+.Nm .
+.It Va .MAKE.SAVE_DOLLARS
+value should be a boolean that controls whether
+.Ql $$
+are preserved when doing
+.Ql :=
+assignments.
+The default is true, for compatibility with other makes.
+If set to false,
+.Ql $$
+becomes
+.Ql $
+per normal evaluation rules.
+.It Va MAKE_PRINT_VAR_ON_ERROR
+When
+.Nm
+stops due to an error, it sets
+.Ql Va .ERROR_TARGET
+to the name of the target that failed,
+.Ql Va .ERROR_CMD
+to the commands of the failed target,
+and in "meta" mode, it also sets
+.Ql Va .ERROR_CWD
+to the
+.Xr getcwd 3 ,
+and
+.Ql Va .ERROR_META_FILE
+to the path of the meta file (if any) describing the failed target.
+It then prints its name and the value of
+.Ql Va .CURDIR
+as well as the value of any variables named in
+.Ql Va MAKE_PRINT_VAR_ON_ERROR .
+.It Va .newline
+This variable is simply assigned a newline character as its value.
+This allows expansions using the
+.Cm \&:@
+modifier to put a newline between
+iterations of the loop rather than a space.
+For example, the printing of
+.Ql Va MAKE_PRINT_VAR_ON_ERROR
+could be done as ${MAKE_PRINT_VAR_ON_ERROR:@v@$v='${$v}'${.newline}@}.
+.It Va .OBJDIR
+A path to the directory where the targets are built.
+Its value is determined by trying to
+.Xr chdir 2
+to the following directories in order and using the first match:
+.Bl -enum
+.It
+.Ev ${MAKEOBJDIRPREFIX}${.CURDIR}
+.Pp
+(Only if
+.Ql Ev MAKEOBJDIRPREFIX
+is set in the environment or on the command line.)
+.It
+.Ev ${MAKEOBJDIR}
+.Pp
+(Only if
+.Ql Ev MAKEOBJDIR
+is set in the environment or on the command line.)
+.It
+.Ev ${.CURDIR} Ns Pa /obj. Ns Ev ${MACHINE}
+.It
+.Ev ${.CURDIR} Ns Pa /obj
+.It
+.Pa /usr/obj/ Ns Ev ${.CURDIR}
+.It
+.Ev ${.CURDIR}
+.El
+.Pp
+Variable expansion is performed on the value before it's used,
+so expressions such as
+.Dl ${.CURDIR:S,^/usr/src,/var/obj,}
+may be used.
+This is especially useful with
+.Ql Ev MAKEOBJDIR .
+.Pp
+.Ql Va .OBJDIR
+may be modified in the makefile via the special target
+.Ql Ic .OBJDIR .
+In all cases,
+.Nm
+will
+.Xr chdir 2
+to the specified directory if it exists, and set
+.Ql Va .OBJDIR
+and
+.Ql Ev PWD
+to that directory before executing any targets.
+.
+.It Va .PARSEDIR
+A path to the directory of the current
+.Ql Pa Makefile
+being parsed.
+.It Va .PARSEFILE
+The basename of the current
+.Ql Pa Makefile
+being parsed.
+This variable and
+.Ql Va .PARSEDIR
+are both set only while the
+.Ql Pa Makefiles
+are being parsed.
+If you want to retain their current values, assign them to a variable
+using assignment with expansion:
+.Pq Ql Cm \&:= .
+.It Va .PATH
+A variable that represents the list of directories that
+.Nm
+will search for files.
+The search list should be updated using the target
+.Ql Va .PATH
+rather than the variable.
+.It Ev PWD
+Alternate path to the current directory.
+.Nm
+normally sets
+.Ql Va .CURDIR
+to the canonical path given by
+.Xr getcwd 3 .
+However, if the environment variable
+.Ql Ev PWD
+is set and gives a path to the current directory, then
+.Nm
+sets
+.Ql Va .CURDIR
+to the value of
+.Ql Ev PWD
+instead.
+This behavior is disabled if
+.Ql Ev MAKEOBJDIRPREFIX
+is set or
+.Ql Ev MAKEOBJDIR
+contains a variable transform.
+.Ql Ev PWD
+is set to the value of
+.Ql Va .OBJDIR
+for all programs which
+.Nm
+executes.
+.It Ev .TARGETS
+The list of targets explicitly specified on the command line, if any.
+.It Ev VPATH
+Colon-separated
+.Pq Dq \&:
+lists of directories that
+.Nm
+will search for files.
+The variable is supported for compatibility with old make programs only,
+use
+.Ql Va .PATH
+instead.
+.El
+.Ss Variable modifiers
+Variable expansion may be modified to select or modify each word of the
+variable (where a
+.Dq word
+is white-space delimited sequence of characters).
+The general format of a variable expansion is as follows:
+.Pp
+.Dl ${variable[:modifier[:...]]}
+.Pp
+Each modifier begins with a colon,
+which may be escaped with a backslash
+.Pq Ql \e .
+.Pp
+A set of modifiers can be specified via a variable, as follows:
+.Pp
+.Dl modifier_variable=modifier[:...]
+.Dl ${variable:${modifier_variable}[:...]}
+.Pp
+In this case the first modifier in the modifier_variable does not
+start with a colon, since that must appear in the referencing
+variable.
+If any of the modifiers in the modifier_variable contain a dollar sign
+.Pq Ql $ ,
+these must be doubled to avoid early expansion.
+.Pp
+The supported modifiers are:
+.Bl -tag -width EEE
+.It Cm \&:E
+Replaces each word in the variable with its suffix.
+.It Cm \&:H
+Replaces each word in the variable with everything but the last component.
+.It Cm \&:M Ns Ar pattern
+Select only those words that match
+.Ar pattern .
+The standard shell wildcard characters
+.Pf ( Ql * ,
+.Ql \&? ,
+and
+.Ql Oo Oc )
+may
+be used.
+The wildcard characters may be escaped with a backslash
+.Pq Ql \e .
+As a consequence of the way values are split into words, matched,
+and then joined, a construct like
+.Dl ${VAR:M*}
+will normalize the inter-word spacing, removing all leading and
+trailing space, and converting multiple consecutive spaces
+to single spaces.
+.
+.It Cm \&:N Ns Ar pattern
+This is identical to
+.Ql Cm \&:M ,
+but selects all words which do not match
+.Ar pattern .
+.It Cm \&:O
+Order every word in variable alphabetically.
+To sort words in
+reverse order use the
+.Ql Cm \&:O:[-1..1]
+combination of modifiers.
+.It Cm \&:Ox
+Randomize words in variable.
+The results will be different each time you are referring to the
+modified variable; use the assignment with expansion
+.Pq Ql Cm \&:=
+to prevent such behavior.
+For example,
+.Bd -literal -offset indent
+LIST= uno due tre quattro
+RANDOM_LIST= ${LIST:Ox}
+STATIC_RANDOM_LIST:= ${LIST:Ox}
+
+all:
+ @echo "${RANDOM_LIST}"
+ @echo "${RANDOM_LIST}"
+ @echo "${STATIC_RANDOM_LIST}"
+ @echo "${STATIC_RANDOM_LIST}"
+.Ed
+may produce output similar to:
+.Bd -literal -offset indent
+quattro due tre uno
+tre due quattro uno
+due uno quattro tre
+due uno quattro tre
+.Ed
+.It Cm \&:Q
+Quotes every shell meta-character in the variable, so that it can be passed
+safely to the shell.
+.It Cm \&:q
+Quotes every shell meta-character in the variable, and also doubles
+.Sq $
+characters so that it can be passed
+safely through recursive invocations of
+.Nm .
+This is equivalent to:
+.Sq \&:S/\e\&$/&&/g:Q .
+.It Cm \&:R
+Replaces each word in the variable with everything but its suffix.
+.It Cm \&:range[=count]
+The value is an integer sequence representing the words of the original
+value, or the supplied
+.Va count .
+.It Cm \&:gmtime[=utc]
+The value is a format string for
+.Xr strftime 3 ,
+using
+.Xr gmtime 3 .
+If a
+.Va utc
+value is not provided or is 0, the current time is used.
+.It Cm \&:hash
+Compute a 32-bit hash of the value and encode it as hex digits.
+.It Cm \&:localtime[=utc]
+The value is a format string for
+.Xr strftime 3 ,
+using
+.Xr localtime 3 .
+If a
+.Va utc
+value is not provided or is 0, the current time is used.
+.It Cm \&:tA
+Attempt to convert variable to an absolute path using
+.Xr realpath 3 ,
+if that fails, the value is unchanged.
+.It Cm \&:tl
+Converts variable to lower-case letters.
+.It Cm \&:ts Ns Ar c
+Words in the variable are normally separated by a space on expansion.
+This modifier sets the separator to the character
+.Ar c .
+If
+.Ar c
+is omitted, then no separator is used.
+The common escapes (including octal numeric codes), work as expected.
+.It Cm \&:tu
+Converts variable to upper-case letters.
+.It Cm \&:tW
+Causes the value to be treated as a single word
+(possibly containing embedded white space).
+See also
+.Ql Cm \&:[*] .
+.It Cm \&:tw
+Causes the value to be treated as a sequence of
+words delimited by white space.
+See also
+.Ql Cm \&:[@] .
+.Sm off
+.It Cm \&:S No \&/ Ar old_string No \&/ Ar new_string No \&/ Op Cm 1gW
+.Sm on
+Modify the first occurrence of
+.Ar old_string
+in the variable's value, replacing it with
+.Ar new_string .
+If a
+.Ql g
+is appended to the last slash of the pattern, all occurrences
+in each word are replaced.
+If a
+.Ql 1
+is appended to the last slash of the pattern, only the first word
+is affected.
+If a
+.Ql W
+is appended to the last slash of the pattern,
+then the value is treated as a single word
+(possibly containing embedded white space).
+If
+.Ar old_string
+begins with a caret
+.Pq Ql ^ ,
+.Ar old_string
+is anchored at the beginning of each word.
+If
+.Ar old_string
+ends with a dollar sign
+.Pq Ql \&$ ,
+it is anchored at the end of each word.
+Inside
+.Ar new_string ,
+an ampersand
+.Pq Ql &
+is replaced by
+.Ar old_string
+(without any
+.Ql ^
+or
+.Ql \&$ ) .
+Any character may be used as a delimiter for the parts of the modifier
+string.
+The anchoring, ampersand and delimiter characters may be escaped with a
+backslash
+.Pq Ql \e .
+.Pp
+Variable expansion occurs in the normal fashion inside both
+.Ar old_string
+and
+.Ar new_string
+with the single exception that a backslash is used to prevent the expansion
+of a dollar sign
+.Pq Ql \&$ ,
+not a preceding dollar sign as is usual.
+.Sm off
+.It Cm \&:C No \&/ Ar pattern No \&/ Ar replacement No \&/ Op Cm 1gW
+.Sm on
+The
+.Cm \&:C
+modifier is just like the
+.Cm \&:S
+modifier except that the old and new strings, instead of being
+simple strings, are an extended regular expression (see
+.Xr regex 3 )
+string
+.Ar pattern
+and an
+.Xr ed 1 Ns \-style
+string
+.Ar replacement .
+Normally, the first occurrence of the pattern
+.Ar pattern
+in each word of the value is substituted with
+.Ar replacement .
+The
+.Ql 1
+modifier causes the substitution to apply to at most one word; the
+.Ql g
+modifier causes the substitution to apply to as many instances of the
+search pattern
+.Ar pattern
+as occur in the word or words it is found in; the
+.Ql W
+modifier causes the value to be treated as a single word
+(possibly containing embedded white space).
+Note that
+.Ql 1
+and
+.Ql g
+are orthogonal; the former specifies whether multiple words are
+potentially affected, the latter whether multiple substitutions can
+potentially occur within each affected word.
+.Pp
+As for the
+.Cm \&:S
+modifier, the
+.Ar pattern
+and
+.Ar replacement
+are subjected to variable expansion before being parsed as
+regular expressions.
+.It Cm \&:T
+Replaces each word in the variable with its last component.
+.It Cm \&:u
+Remove adjacent duplicate words (like
+.Xr uniq 1 ) .
+.Sm off
+.It Cm \&:\&? Ar true_string Cm \&: Ar false_string
+.Sm on
+If the variable name (not its value), when parsed as a .if conditional
+expression, evaluates to true, return as its value the
+.Ar true_string ,
+otherwise return the
+.Ar false_string .
+Since the variable name is used as the expression, \&:\&? must be the
+first modifier after the variable name itself - which will, of course,
+usually contain variable expansions.
+A common error is trying to use expressions like
+.Dl ${NUMBERS:M42:?match:no}
+which actually tests defined(NUMBERS),
+to determine is any words match "42" you need to use something like:
+.Dl ${"${NUMBERS:M42}" != \&"\&":?match:no} .
+.It Ar :old_string=new_string
+This is the
+.At V
+style variable substitution.
+It must be the last modifier specified.
+If
+.Ar old_string
+or
+.Ar new_string
+do not contain the pattern matching character
+.Ar %
+then it is assumed that they are
+anchored at the end of each word, so only suffixes or entire
+words may be replaced.
+Otherwise
+.Ar %
+is the substring of
+.Ar old_string
+to be replaced in
+.Ar new_string .
+.Pp
+Variable expansion occurs in the normal fashion inside both
+.Ar old_string
+and
+.Ar new_string
+with the single exception that a backslash is used to prevent the
+expansion of a dollar sign
+.Pq Ql \&$ ,
+not a preceding dollar sign as is usual.
+.Sm off
+.It Cm \&:@ Ar temp Cm @ Ar string Cm @
+.Sm on
+This is the loop expansion mechanism from the OSF Development
+Environment (ODE) make.
+Unlike
+.Cm \&.for
+loops expansion occurs at the time of
+reference.
+Assign
+.Ar temp
+to each word in the variable and evaluate
+.Ar string .
+The ODE convention is that
+.Ar temp
+should start and end with a period.
+For example.
+.Dl ${LINKS:@.LINK.@${LN} ${TARGET} ${.LINK.}@}
+.Pp
+However a single character variable is often more readable:
+.Dl ${MAKE_PRINT_VAR_ON_ERROR:@v@$v='${$v}'${.newline}@}
+.It Cm \&:_[=var]
+Save the current variable value in
+.Ql $_
+or the named
+.Va var
+for later reference.
+Example usage:
+.Bd -literal -offset indent
+M_cmpv.units = 1 1000 1000000
+M_cmpv = S,., ,g:_:range:@i@+ $${_:[-$$i]} \&\\
+\\* $${M_cmpv.units:[$$i]}@:S,^,expr 0 ,1:sh
+
+.Dv .if ${VERSION:${M_cmpv}} < ${3.1.12:L:${M_cmpv}}
+
+.Ed
+Here
+.Ql $_
+is used to save the result of the
+.Ql :S
+modifier which is later referenced using the index values from
+.Ql :range .
+.It Cm \&:U Ns Ar newval
+If the variable is undefined
+.Ar newval
+is the value.
+If the variable is defined, the existing value is returned.
+This is another ODE make feature.
+It is handy for setting per-target CFLAGS for instance:
+.Dl ${_${.TARGET:T}_CFLAGS:U${DEF_CFLAGS}}
+If a value is only required if the variable is undefined, use:
+.Dl ${VAR:D:Unewval}
+.It Cm \&:D Ns Ar newval
+If the variable is defined
+.Ar newval
+is the value.
+.It Cm \&:L
+The name of the variable is the value.
+.It Cm \&:P
+The path of the node which has the same name as the variable
+is the value.
+If no such node exists or its path is null, then the
+name of the variable is used.
+In order for this modifier to work, the name (node) must at least have
+appeared on the rhs of a dependency.
+.Sm off
+.It Cm \&:\&! Ar cmd Cm \&!
+.Sm on
+The output of running
+.Ar cmd
+is the value.
+.It Cm \&:sh
+If the variable is non-empty it is run as a command and the output
+becomes the new value.
+.It Cm \&::= Ns Ar str
+The variable is assigned the value
+.Ar str
+after substitution.
+This modifier and its variations are useful in
+obscure situations such as wanting to set a variable when shell commands
+are being parsed.
+These assignment modifiers always expand to
+nothing, so if appearing in a rule line by themselves should be
+preceded with something to keep
+.Nm
+happy.
+.Pp
+The
+.Ql Cm \&::
+helps avoid false matches with the
+.At V
+style
+.Cm \&:=
+modifier and since substitution always occurs the
+.Cm \&::=
+form is vaguely appropriate.
+.It Cm \&::?= Ns Ar str
+As for
+.Cm \&::=
+but only if the variable does not already have a value.
+.It Cm \&::+= Ns Ar str
+Append
+.Ar str
+to the variable.
+.It Cm \&::!= Ns Ar cmd
+Assign the output of
+.Ar cmd
+to the variable.
+.It Cm \&:\&[ Ns Ar range Ns Cm \&]
+Selects one or more words from the value,
+or performs other operations related to the way in which the
+value is divided into words.
+.Pp
+Ordinarily, a value is treated as a sequence of words
+delimited by white space.
+Some modifiers suppress this behavior,
+causing a value to be treated as a single word
+(possibly containing embedded white space).
+An empty value, or a value that consists entirely of white-space,
+is treated as a single word.
+For the purposes of the
+.Ql Cm \&:[]
+modifier, the words are indexed both forwards using positive integers
+(where index 1 represents the first word),
+and backwards using negative integers
+(where index \-1 represents the last word).
+.Pp
+The
+.Ar range
+is subjected to variable expansion, and the expanded result is
+then interpreted as follows:
+.Bl -tag -width index
+.\" :[n]
+.It Ar index
+Selects a single word from the value.
+.\" :[start..end]
+.It Ar start Ns Cm \&.. Ns Ar end
+Selects all words from
+.Ar start
+to
+.Ar end ,
+inclusive.
+For example,
+.Ql Cm \&:[2..-1]
+selects all words from the second word to the last word.
+If
+.Ar start
+is greater than
+.Ar end ,
+then the words are output in reverse order.
+For example,
+.Ql Cm \&:[-1..1]
+selects all the words from last to first.
+.\" :[*]
+.It Cm \&*
+Causes subsequent modifiers to treat the value as a single word
+(possibly containing embedded white space).
+Analogous to the effect of
+\&"$*\&"
+in Bourne shell.
+.\" :[0]
+.It 0
+Means the same as
+.Ql Cm \&:[*] .
+.\" :[*]
+.It Cm \&@
+Causes subsequent modifiers to treat the value as a sequence of words
+delimited by white space.
+Analogous to the effect of
+\&"$@\&"
+in Bourne shell.
+.\" :[#]
+.It Cm \&#
+Returns the number of words in the value.
+.El \" :[range]
+.El
+.Sh INCLUDE STATEMENTS, CONDITIONALS AND FOR LOOPS
+Makefile inclusion, conditional structures and for loops reminiscent
+of the C programming language are provided in
+.Nm .
+All such structures are identified by a line beginning with a single
+dot
+.Pq Ql \&.
+character.
+Files are included with either
+.Cm \&.include Aq Ar file
+or
+.Cm \&.include Pf \*q Ar file Ns \*q .
+Variables between the angle brackets or double quotes are expanded
+to form the file name.
+If angle brackets are used, the included makefile is expected to be in
+the system makefile directory.
+If double quotes are used, the including makefile's directory and any
+directories specified using the
+.Fl I
+option are searched before the system
+makefile directory.
+For compatibility with other versions of
+.Nm
+.Ql include file ...
+is also accepted.
+.Pp
+If the include statement is written as
+.Cm .-include
+or as
+.Cm .sinclude
+then errors locating and/or opening include files are ignored.
+.Pp
+If the include statement is written as
+.Cm .dinclude
+not only are errors locating and/or opening include files ignored,
+but stale dependencies within the included file will be ignored
+just like
+.Va .MAKE.DEPENDFILE .
+.Pp
+Conditional expressions are also preceded by a single dot as the first
+character of a line.
+The possible conditionals are as follows:
+.Bl -tag -width Ds
+.It Ic .error Ar message
+The message is printed along with the name of the makefile and line number,
+then
+.Nm
+will exit.
+.It Ic .export Ar variable ...
+Export the specified global variable.
+If no variable list is provided, all globals are exported
+except for internal variables (those that start with
+.Ql \&. ) .
+This is not affected by the
+.Fl X
+flag, so should be used with caution.
+For compatibility with other
+.Nm
+programs
+.Ql export variable=value
+is also accepted.
+.Pp
+Appending a variable name to
+.Va .MAKE.EXPORTED
+is equivalent to exporting a variable.
+.It Ic .export-env Ar variable ...
+The same as
+.Ql .export ,
+except that the variable is not appended to
+.Va .MAKE.EXPORTED .
+This allows exporting a value to the environment which is different from that
+used by
+.Nm
+internally.
+.It Ic .export-literal Ar variable ...
+The same as
+.Ql .export-env ,
+except that variables in the value are not expanded.
+.It Ic .info Ar message
+The message is printed along with the name of the makefile and line number.
+.It Ic .undef Ar variable
+Un-define the specified global variable.
+Only global variables may be un-defined.
+.It Ic .unexport Ar variable ...
+The opposite of
+.Ql .export .
+The specified global
+.Va variable
+will be removed from
+.Va .MAKE.EXPORTED .
+If no variable list is provided, all globals are unexported,
+and
+.Va .MAKE.EXPORTED
+deleted.
+.It Ic .unexport-env
+Unexport all globals previously exported and
+clear the environment inherited from the parent.
+This operation will cause a memory leak of the original environment,
+so should be used sparingly.
+Testing for
+.Va .MAKE.LEVEL
+being 0, would make sense.
+Also note that any variables which originated in the parent environment
+should be explicitly preserved if desired.
+For example:
+.Bd -literal -offset indent
+.Li .if ${.MAKE.LEVEL} == 0
+PATH := ${PATH}
+.Li .unexport-env
+.Li .export PATH
+.Li .endif
+.Pp
+.Ed
+Would result in an environment containing only
+.Ql Ev PATH ,
+which is the minimal useful environment.
+Actually
+.Ql Ev .MAKE.LEVEL
+will also be pushed into the new environment.
+.It Ic .warning Ar message
+The message prefixed by
+.Ql Pa warning:
+is printed along with the name of the makefile and line number.
+.It Ic \&.if Oo \&! Oc Ns Ar expression Op Ar operator expression ...
+Test the value of an expression.
+.It Ic .ifdef Oo \&! Oc Ns Ar variable Op Ar operator variable ...
+Test the value of a variable.
+.It Ic .ifndef Oo \&! Oc Ns Ar variable Op Ar operator variable ...
+Test the value of a variable.
+.It Ic .ifmake Oo \&! Oc Ns Ar target Op Ar operator target ...
+Test the target being built.
+.It Ic .ifnmake Oo \&! Ns Oc Ar target Op Ar operator target ...
+Test the target being built.
+.It Ic .else
+Reverse the sense of the last conditional.
+.It Ic .elif Oo \&! Ns Oc Ar expression Op Ar operator expression ...
+A combination of
+.Ql Ic .else
+followed by
+.Ql Ic .if .
+.It Ic .elifdef Oo \&! Oc Ns Ar variable Op Ar operator variable ...
+A combination of
+.Ql Ic .else
+followed by
+.Ql Ic .ifdef .
+.It Ic .elifndef Oo \&! Oc Ns Ar variable Op Ar operator variable ...
+A combination of
+.Ql Ic .else
+followed by
+.Ql Ic .ifndef .
+.It Ic .elifmake Oo \&! Oc Ns Ar target Op Ar operator target ...
+A combination of
+.Ql Ic .else
+followed by
+.Ql Ic .ifmake .
+.It Ic .elifnmake Oo \&! Oc Ns Ar target Op Ar operator target ...
+A combination of
+.Ql Ic .else
+followed by
+.Ql Ic .ifnmake .
+.It Ic .endif
+End the body of the conditional.
+.El
+.Pp
+The
+.Ar operator
+may be any one of the following:
+.Bl -tag -width "Cm XX"
+.It Cm \&|\&|
+Logical OR.
+.It Cm \&&&
+Logical
+.Tn AND ;
+of higher precedence than
+.Dq \&|\&| .
+.El
+.Pp
+As in C,
+.Nm
+will only evaluate a conditional as far as is necessary to determine
+its value.
+Parentheses may be used to change the order of evaluation.
+The boolean operator
+.Ql Ic \&!
+may be used to logically negate an entire
+conditional.
+It is of higher precedence than
+.Ql Ic \&&& .
+.Pp
+The value of
+.Ar expression
+may be any of the following:
+.Bl -tag -width defined
+.It Ic defined
+Takes a variable name as an argument and evaluates to true if the variable
+has been defined.
+.It Ic make
+Takes a target name as an argument and evaluates to true if the target
+was specified as part of
+.Nm Ns 's
+command line or was declared the default target (either implicitly or
+explicitly, see
+.Va .MAIN )
+before the line containing the conditional.
+.It Ic empty
+Takes a variable, with possible modifiers, and evaluates to true if
+the expansion of the variable would result in an empty string.
+.It Ic exists
+Takes a file name as an argument and evaluates to true if the file exists.
+The file is searched for on the system search path (see
+.Va .PATH ) .
+.It Ic target
+Takes a target name as an argument and evaluates to true if the target
+has been defined.
+.It Ic commands
+Takes a target name as an argument and evaluates to true if the target
+has been defined and has commands associated with it.
+.El
+.Pp
+.Ar Expression
+may also be an arithmetic or string comparison.
+Variable expansion is
+performed on both sides of the comparison, after which the integral
+values are compared.
+A value is interpreted as hexadecimal if it is
+preceded by 0x, otherwise it is decimal; octal numbers are not supported.
+The standard C relational operators are all supported.
+If after
+variable expansion, either the left or right hand side of a
+.Ql Ic ==
+or
+.Ql Ic "!="
+operator is not an integral value, then
+string comparison is performed between the expanded
+variables.
+If no relational operator is given, it is assumed that the expanded
+variable is being compared against 0 or an empty string in the case
+of a string comparison.
+.Pp
+When
+.Nm
+is evaluating one of these conditional expressions, and it encounters
+a (white-space separated) word it doesn't recognize, either the
+.Dq make
+or
+.Dq defined
+expression is applied to it, depending on the form of the conditional.
+If the form is
+.Ql Ic .ifdef ,
+.Ql Ic .ifndef ,
+or
+.Ql Ic .if
+the
+.Dq defined
+expression is applied.
+Similarly, if the form is
+.Ql Ic .ifmake
+or
+.Ql Ic .ifnmake ,
+the
+.Dq make
+expression is applied.
+.Pp
+If the conditional evaluates to true the parsing of the makefile continues
+as before.
+If it evaluates to false, the following lines are skipped.
+In both cases this continues until a
+.Ql Ic .else
+or
+.Ql Ic .endif
+is found.
+.Pp
+For loops are typically used to apply a set of rules to a list of files.
+The syntax of a for loop is:
+.Pp
+.Bl -tag -compact -width Ds
+.It Ic \&.for Ar variable Oo Ar variable ... Oc Ic in Ar expression
+.It Aq make-rules
+.It Ic \&.endfor
+.El
+.Pp
+After the for
+.Ic expression
+is evaluated, it is split into words.
+On each iteration of the loop, one word is taken and assigned to each
+.Ic variable ,
+in order, and these
+.Ic variables
+are substituted into the
+.Ic make-rules
+inside the body of the for loop.
+The number of words must come out even; that is, if there are three
+iteration variables, the number of words provided must be a multiple
+of three.
+.Sh COMMENTS
+Comments begin with a hash
+.Pq Ql \&#
+character, anywhere but in a shell
+command line, and continue to the end of an unescaped new line.
+.Sh SPECIAL SOURCES (ATTRIBUTES)
+.Bl -tag -width .IGNOREx
+.It Ic .EXEC
+Target is never out of date, but always execute commands anyway.
+.It Ic .IGNORE
+Ignore any errors from the commands associated with this target, exactly
+as if they all were preceded by a dash
+.Pq Ql \- .
+.\" .It Ic .INVISIBLE
+.\" XXX
+.\" .It Ic .JOIN
+.\" XXX
+.It Ic .MADE
+Mark all sources of this target as being up-to-date.
+.It Ic .MAKE
+Execute the commands associated with this target even if the
+.Fl n
+or
+.Fl t
+options were specified.
+Normally used to mark recursive
+.Nm Ns s .
+.It Ic .META
+Create a meta file for the target, even if it is flagged as
+.Ic .PHONY ,
+.Ic .MAKE ,
+or
+.Ic .SPECIAL .
+Usage in conjunction with
+.Ic .MAKE
+is the most likely case.
+In "meta" mode, the target is out-of-date if the meta file is missing.
+.It Ic .NOMETA
+Do not create a meta file for the target.
+Meta files are also not created for
+.Ic .PHONY ,
+.Ic .MAKE ,
+or
+.Ic .SPECIAL
+targets.
+.It Ic .NOMETA_CMP
+Ignore differences in commands when deciding if target is out of date.
+This is useful if the command contains a value which always changes.
+If the number of commands change, though, the target will still be out of date.
+The same effect applies to any command line that uses the variable
+.Va .OODATE ,
+which can be used for that purpose even when not otherwise needed or desired:
+.Bd -literal -offset indent
+
+skip-compare-for-some:
+ @echo this will be compared
+ @echo this will not ${.OODATE:M.NOMETA_CMP}
+ @echo this will also be compared
+
+.Ed
+The
+.Cm \&:M
+pattern suppresses any expansion of the unwanted variable.
+.It Ic .NOPATH
+Do not search for the target in the directories specified by
+.Ic .PATH .
+.It Ic .NOTMAIN
+Normally
+.Nm
+selects the first target it encounters as the default target to be built
+if no target was specified.
+This source prevents this target from being selected.
+.It Ic .OPTIONAL
+If a target is marked with this attribute and
+.Nm
+can't figure out how to create it, it will ignore this fact and assume
+the file isn't needed or already exists.
+.It Ic .PHONY
+The target does not
+correspond to an actual file; it is always considered to be out of date,
+and will not be created with the
+.Fl t
+option.
+Suffix-transformation rules are not applied to
+.Ic .PHONY
+targets.
+.It Ic .PRECIOUS
+When
+.Nm
+is interrupted, it normally removes any partially made targets.
+This source prevents the target from being removed.
+.It Ic .RECURSIVE
+Synonym for
+.Ic .MAKE .
+.It Ic .SILENT
+Do not echo any of the commands associated with this target, exactly
+as if they all were preceded by an at sign
+.Pq Ql @ .
+.It Ic .USE
+Turn the target into
+.Nm Ns 's
+version of a macro.
+When the target is used as a source for another target, the other target
+acquires the commands, sources, and attributes (except for
+.Ic .USE )
+of the
+source.
+If the target already has commands, the
+.Ic .USE
+target's commands are appended
+to them.
+.It Ic .USEBEFORE
+Exactly like
+.Ic .USE ,
+but prepend the
+.Ic .USEBEFORE
+target commands to the target.
+.It Ic .WAIT
+If
+.Ic .WAIT
+appears in a dependency line, the sources that precede it are
+made before the sources that succeed it in the line.
+Since the dependents of files are not made until the file itself
+could be made, this also stops the dependents being built unless they
+are needed for another branch of the dependency tree.
+So given:
+.Bd -literal
+x: a .WAIT b
+ echo x
+a:
+ echo a
+b: b1
+ echo b
+b1:
+ echo b1
+
+.Ed
+the output is always
+.Ql a ,
+.Ql b1 ,
+.Ql b ,
+.Ql x .
+.br
+The ordering imposed by
+.Ic .WAIT
+is only relevant for parallel makes.
+.El
+.Sh SPECIAL TARGETS
+Special targets may not be included with other targets, i.e. they must be
+the only target specified.
+.Bl -tag -width .BEGINx
+.It Ic .BEGIN
+Any command lines attached to this target are executed before anything
+else is done.
+.It Ic .DEFAULT
+This is sort of a
+.Ic .USE
+rule for any target (that was used only as a
+source) that
+.Nm
+can't figure out any other way to create.
+Only the shell script is used.
+The
+.Ic .IMPSRC
+variable of a target that inherits
+.Ic .DEFAULT Ns 's
+commands is set
+to the target's own name.
+.It Ic .DELETE_ON_ERROR
+If this target is present in the makefile, it globally causes make to
+delete targets whose commands fail.
+(By default, only targets whose commands are interrupted during
+execution are deleted.
+This is the historical behavior.)
+This setting can be used to help prevent half-finished or malformed
+targets from being left around and corrupting future rebuilds.
+.It Ic .END
+Any command lines attached to this target are executed after everything
+else is done.
+.It Ic .ERROR
+Any command lines attached to this target are executed when another target fails.
+The
+.Ic .ERROR_TARGET
+variable is set to the target that failed.
+See also
+.Ic MAKE_PRINT_VAR_ON_ERROR .
+.It Ic .IGNORE
+Mark each of the sources with the
+.Ic .IGNORE
+attribute.
+If no sources are specified, this is the equivalent of specifying the
+.Fl i
+option.
+.It Ic .INTERRUPT
+If
+.Nm
+is interrupted, the commands for this target will be executed.
+.It Ic .MAIN
+If no target is specified when
+.Nm
+is invoked, this target will be built.
+.It Ic .MAKEFLAGS
+This target provides a way to specify flags for
+.Nm
+when the makefile is used.
+The flags are as if typed to the shell, though the
+.Fl f
+option will have
+no effect.
+.\" XXX: NOT YET!!!!
+.\" .It Ic .NOTPARALLEL
+.\" The named targets are executed in non parallel mode.
+.\" If no targets are
+.\" specified, then all targets are executed in non parallel mode.
+.It Ic .NOPATH
+Apply the
+.Ic .NOPATH
+attribute to any specified sources.
+.It Ic .NOTPARALLEL
+Disable parallel mode.
+.It Ic .NO_PARALLEL
+Synonym for
+.Ic .NOTPARALLEL ,
+for compatibility with other pmake variants.
+.It Ic .OBJDIR
+The source is a new value for
+.Ql Va .OBJDIR .
+If it exists,
+.Nm
+will
+.Xr chdir 2
+to it and update the value of
+.Ql Va .OBJDIR .
+.It Ic .ORDER
+The named targets are made in sequence.
+This ordering does not add targets to the list of targets to be made.
+Since the dependents of a target do not get built until the target itself
+could be built, unless
+.Ql a
+is built by another part of the dependency graph,
+the following is a dependency loop:
+.Bd -literal
+\&.ORDER: b a
+b: a
+.Ed
+.Pp
+The ordering imposed by
+.Ic .ORDER
+is only relevant for parallel makes.
+.\" XXX: NOT YET!!!!
+.\" .It Ic .PARALLEL
+.\" The named targets are executed in parallel mode.
+.\" If no targets are
+.\" specified, then all targets are executed in parallel mode.
+.It Ic .PATH
+The sources are directories which are to be searched for files not
+found in the current directory.
+If no sources are specified, any previously specified directories are
+deleted.
+If the source is the special
+.Ic .DOTLAST
+target, then the current working
+directory is searched last.
+.It Ic .PATH. Ns Va suffix
+Like
+.Ic .PATH
+but applies only to files with a particular suffix.
+The suffix must have been previously declared with
+.Ic .SUFFIXES .
+.It Ic .PHONY
+Apply the
+.Ic .PHONY
+attribute to any specified sources.
+.It Ic .PRECIOUS
+Apply the
+.Ic .PRECIOUS
+attribute to any specified sources.
+If no sources are specified, the
+.Ic .PRECIOUS
+attribute is applied to every
+target in the file.
+.It Ic .SHELL
+Sets the shell that
+.Nm
+will use to execute commands.
+The sources are a set of
+.Ar field=value
+pairs.
+.Bl -tag -width hasErrCtls
+.It Ar name
+This is the minimal specification, used to select one of the built-in
+shell specs;
+.Ar sh ,
+.Ar ksh ,
+and
+.Ar csh .
+.It Ar path
+Specifies the path to the shell.
+.It Ar hasErrCtl
+Indicates whether the shell supports exit on error.
+.It Ar check
+The command to turn on error checking.
+.It Ar ignore
+The command to disable error checking.
+.It Ar echo
+The command to turn on echoing of commands executed.
+.It Ar quiet
+The command to turn off echoing of commands executed.
+.It Ar filter
+The output to filter after issuing the
+.Ar quiet
+command.
+It is typically identical to
+.Ar quiet .
+.It Ar errFlag
+The flag to pass the shell to enable error checking.
+.It Ar echoFlag
+The flag to pass the shell to enable command echoing.
+.It Ar newline
+The string literal to pass the shell that results in a single newline
+character when used outside of any quoting characters.
+.El
+Example:
+.Bd -literal
+\&.SHELL: name=ksh path=/bin/ksh hasErrCtl=true \e
+ check="set \-e" ignore="set +e" \e
+ echo="set \-v" quiet="set +v" filter="set +v" \e
+ echoFlag=v errFlag=e newline="'\en'"
+.Ed
+.It Ic .SILENT
+Apply the
+.Ic .SILENT
+attribute to any specified sources.
+If no sources are specified, the
+.Ic .SILENT
+attribute is applied to every
+command in the file.
+.It Ic .STALE
+This target gets run when a dependency file contains stale entries, having
+.Va .ALLSRC
+set to the name of that dependency file.
+.It Ic .SUFFIXES
+Each source specifies a suffix to
+.Nm .
+If no sources are specified, any previously specified suffixes are deleted.
+It allows the creation of suffix-transformation rules.
+.Pp
+Example:
+.Bd -literal
+\&.SUFFIXES: .o
+\&.c.o:
+ cc \-o ${.TARGET} \-c ${.IMPSRC}
+.Ed
+.El
+.Sh ENVIRONMENT
+.Nm
+uses the following environment variables, if they exist:
+.Ev MACHINE ,
+.Ev MACHINE_ARCH ,
+.Ev MAKE ,
+.Ev MAKEFLAGS ,
+.Ev MAKEOBJDIR ,
+.Ev MAKEOBJDIRPREFIX ,
+.Ev MAKESYSPATH ,
+.Ev PWD ,
+and
+.Ev TMPDIR .
+.Pp
+.Ev MAKEOBJDIRPREFIX
+and
+.Ev MAKEOBJDIR
+may only be set in the environment or on the command line to
+.Nm
+and not as makefile variables;
+see the description of
+.Ql Va .OBJDIR
+for more details.
+.Sh FILES
+.Bl -tag -width /usr/share/mk -compact
+.It .depend
+list of dependencies
+.It Makefile
+list of dependencies
+.It makefile
+list of dependencies
+.It sys.mk
+system makefile
+.It /usr/share/mk
+system makefile directory
+.El
+.Sh COMPATIBILITY
+The basic make syntax is compatible between different versions of make;
+however the special variables, variable modifiers and conditionals are not.
+.Ss Older versions
+An incomplete list of changes in older versions of
+.Nm :
+.Pp
+The way that .for loop variables are substituted changed after
+.Nx 5.0
+so that they still appear to be variable expansions.
+In particular this stops them being treated as syntax, and removes some
+obscure problems using them in .if statements.
+.Pp
+The way that parallel makes are scheduled changed in
+.Nx 4.0
+so that .ORDER and .WAIT apply recursively to the dependent nodes.
+The algorithms used may change again in the future.
+.Ss Other make dialects
+Other make dialects (GNU make, SVR4 make, POSIX make, etc.) do not
+support most of the features of
+.Nm
+as described in this manual.
+Most notably:
+.Bl -bullet -offset indent
+.It
+The
+.Ic .WAIT
+and
+.Ic .ORDER
+declarations and most functionality pertaining to parallelization.
+(GNU make supports parallelization but lacks these features needed to
+control it effectively.)
+.It
+Directives, including for loops and conditionals and most of the
+forms of include files.
+(GNU make has its own incompatible and less powerful syntax for
+conditionals.)
+.It
+All built-in variables that begin with a dot.
+.It
+Most of the special sources and targets that begin with a dot,
+with the notable exception of
+.Ic .PHONY ,
+.Ic .PRECIOUS ,
+and
+.Ic .SUFFIXES .
+.It
+Variable modifiers, except for the
+.Dl :old=new
+string substitution, which does not portably support globbing with
+.Ql %
+and historically only works on declared suffixes.
+.It
+The
+.Ic $>
+variable even in its short form; most makes support this functionality
+but its name varies.
+.El
+.Pp
+Some features are somewhat more portable, such as assignment with
+.Ic += ,
+.Ic ?= ,
+and
+.Ic != .
+The
+.Ic .PATH
+functionality is based on an older feature
+.Ic VPATH
+found in GNU make and many versions of SVR4 make; however,
+historically its behavior is too ill-defined (and too buggy) to rely
+upon.
+.Pp
+The
+.Ic $@
+and
+.Ic $<
+variables are more or less universally portable, as is the
+.Ic $(MAKE)
+variable.
+Basic use of suffix rules (for files only in the current directory,
+not trying to chain transformations together, etc.) is also reasonably
+portable.
+.Sh SEE ALSO
+.Xr mkdep 1
+.Sh HISTORY
+A
+.Nm
+command appeared in
+.At v7 .
+This
+.Nm
+implementation is based on Adam De Boor's pmake program which was written
+for Sprite at Berkeley.
+It was designed to be a parallel distributed make running jobs on different
+machines using a daemon called
+.Dq customs .
+.Pp
+Historically the target/dependency
+.Dq FRC
+has been used to FoRCe rebuilding (since the target/dependency
+does not exist... unless someone creates an
+.Dq FRC
+file).
+.Sh BUGS
+The
+.Nm
+syntax is difficult to parse without actually acting of the data.
+For instance finding the end of a variable use should involve scanning each
+the modifiers using the correct terminator for each field.
+In many places
+.Nm
+just counts {} and () in order to find the end of a variable expansion.
+.Pp
+There is no way of escaping a space character in a filename.
diff --git a/usr.bin/make/make.c b/usr.bin/make/make.c
new file mode 100644
index 0000000..8947582
--- /dev/null
+++ b/usr.bin/make/make.c
@@ -0,0 +1,1555 @@
+/* $NetBSD: make.c,v 1.96 2016/11/10 23:41:58 sjg Exp $ */
+
+/*
+ * Copyright (c) 1988, 1989, 1990, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Adam de Boor.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+/*
+ * Copyright (c) 1989 by Berkeley Softworks
+ * All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Adam de Boor.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. All advertising materials mentioning features or use of this software
+ * must display the following acknowledgement:
+ * This product includes software developed by the University of
+ * California, Berkeley and its contributors.
+ * 4. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#ifndef MAKE_NATIVE
+static char rcsid[] = "$NetBSD: make.c,v 1.96 2016/11/10 23:41:58 sjg Exp $";
+#else
+#include <sys/cdefs.h>
+#ifndef lint
+#if 0
+static char sccsid[] = "@(#)make.c 8.1 (Berkeley) 6/6/93";
+#else
+__RCSID("$NetBSD: make.c,v 1.96 2016/11/10 23:41:58 sjg Exp $");
+#endif
+#endif /* not lint */
+#endif
+
+/*-
+ * make.c --
+ * The functions which perform the examination of targets and
+ * their suitability for creation
+ *
+ * Interface:
+ * Make_Run Initialize things for the module and recreate
+ * whatever needs recreating. Returns TRUE if
+ * work was (or would have been) done and FALSE
+ * otherwise.
+ *
+ * Make_Update Update all parents of a given child. Performs
+ * various bookkeeping chores like the updating
+ * of the cmgn field of the parent, filling
+ * of the IMPSRC context variable, etc. It will
+ * place the parent on the toBeMade queue if it
+ * should be.
+ *
+ * Make_TimeStamp Function to set the parent's cmgn field
+ * based on a child's modification time.
+ *
+ * Make_DoAllVar Set up the various local variables for a
+ * target, including the .ALLSRC variable, making
+ * sure that any variable that needs to exist
+ * at the very least has the empty value.
+ *
+ * Make_OODate Determine if a target is out-of-date.
+ *
+ * Make_HandleUse See if a child is a .USE node for a parent
+ * and perform the .USE actions if so.
+ *
+ * Make_ExpandUse Expand .USE nodes
+ */
+
+#include "make.h"
+#include "hash.h"
+#include "dir.h"
+#include "job.h"
+
+static unsigned int checked = 1;/* Sequence # to detect recursion */
+static Lst toBeMade; /* The current fringe of the graph. These
+ * are nodes which await examination by
+ * MakeOODate. It is added to by
+ * Make_Update and subtracted from by
+ * MakeStartJobs */
+
+static int MakeAddChild(void *, void *);
+static int MakeFindChild(void *, void *);
+static int MakeUnmark(void *, void *);
+static int MakeAddAllSrc(void *, void *);
+static int MakeTimeStamp(void *, void *);
+static int MakeHandleUse(void *, void *);
+static Boolean MakeStartJobs(void);
+static int MakePrintStatus(void *, void *);
+static int MakeCheckOrder(void *, void *);
+static int MakeBuildChild(void *, void *);
+static int MakeBuildParent(void *, void *);
+
+MAKE_ATTR_DEAD static void
+make_abort(GNode *gn, int line)
+{
+ static int two = 2;
+
+ fprintf(debug_file, "make_abort from line %d\n", line);
+ Targ_PrintNode(gn, &two);
+ Lst_ForEach(toBeMade, Targ_PrintNode, &two);
+ Targ_PrintGraph(3);
+ abort();
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * Make_TimeStamp --
+ * Set the cmgn field of a parent node based on the mtime stamp in its
+ * child. Called from MakeOODate via Lst_ForEach.
+ *
+ * Input:
+ * pgn the current parent
+ * cgn the child we've just examined
+ *
+ * Results:
+ * Always returns 0.
+ *
+ * Side Effects:
+ * The cmgn of the parent node will be changed if the mtime
+ * field of the child is greater than it.
+ *-----------------------------------------------------------------------
+ */
+int
+Make_TimeStamp(GNode *pgn, GNode *cgn)
+{
+ if (pgn->cmgn == NULL || cgn->mtime > pgn->cmgn->mtime) {
+ pgn->cmgn = cgn;
+ }
+ return (0);
+}
+
+/*
+ * Input:
+ * pgn the current parent
+ * cgn the child we've just examined
+ *
+ */
+static int
+MakeTimeStamp(void *pgn, void *cgn)
+{
+ return Make_TimeStamp((GNode *)pgn, (GNode *)cgn);
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * Make_OODate --
+ * See if a given node is out of date with respect to its sources.
+ * Used by Make_Run when deciding which nodes to place on the
+ * toBeMade queue initially and by Make_Update to screen out USE and
+ * EXEC nodes. In the latter case, however, any other sort of node
+ * must be considered out-of-date since at least one of its children
+ * will have been recreated.
+ *
+ * Input:
+ * gn the node to check
+ *
+ * Results:
+ * TRUE if the node is out of date. FALSE otherwise.
+ *
+ * Side Effects:
+ * The mtime field of the node and the cmgn field of its parents
+ * will/may be changed.
+ *-----------------------------------------------------------------------
+ */
+Boolean
+Make_OODate(GNode *gn)
+{
+ Boolean oodate;
+
+ /*
+ * Certain types of targets needn't even be sought as their datedness
+ * doesn't depend on their modification time...
+ */
+ if ((gn->type & (OP_JOIN|OP_USE|OP_USEBEFORE|OP_EXEC)) == 0) {
+ (void)Dir_MTime(gn, 1);
+ if (DEBUG(MAKE)) {
+ if (gn->mtime != 0) {
+ fprintf(debug_file, "modified %s...", Targ_FmtTime(gn->mtime));
+ } else {
+ fprintf(debug_file, "non-existent...");
+ }
+ }
+ }
+
+ /*
+ * A target is remade in one of the following circumstances:
+ * its modification time is smaller than that of its youngest child
+ * and it would actually be run (has commands or type OP_NOP)
+ * it's the object of a force operator
+ * it has no children, was on the lhs of an operator and doesn't exist
+ * already.
+ *
+ * Libraries are only considered out-of-date if the archive module says
+ * they are.
+ *
+ * These weird rules are brought to you by Backward-Compatibility and
+ * the strange people who wrote 'Make'.
+ */
+ if (gn->type & (OP_USE|OP_USEBEFORE)) {
+ /*
+ * If the node is a USE node it is *never* out of date
+ * no matter *what*.
+ */
+ if (DEBUG(MAKE)) {
+ fprintf(debug_file, ".USE node...");
+ }
+ oodate = FALSE;
+ } else if ((gn->type & OP_LIB) &&
+ ((gn->mtime==0) || Arch_IsLib(gn))) {
+ if (DEBUG(MAKE)) {
+ fprintf(debug_file, "library...");
+ }
+
+ /*
+ * always out of date if no children and :: target
+ * or non-existent.
+ */
+ oodate = (gn->mtime == 0 || Arch_LibOODate(gn) ||
+ (gn->cmgn == NULL && (gn->type & OP_DOUBLEDEP)));
+ } else if (gn->type & OP_JOIN) {
+ /*
+ * A target with the .JOIN attribute is only considered
+ * out-of-date if any of its children was out-of-date.
+ */
+ if (DEBUG(MAKE)) {
+ fprintf(debug_file, ".JOIN node...");
+ }
+ if (DEBUG(MAKE)) {
+ fprintf(debug_file, "source %smade...", gn->flags & CHILDMADE ? "" : "not ");
+ }
+ oodate = (gn->flags & CHILDMADE) ? TRUE : FALSE;
+ } else if (gn->type & (OP_FORCE|OP_EXEC|OP_PHONY)) {
+ /*
+ * A node which is the object of the force (!) operator or which has
+ * the .EXEC attribute is always considered out-of-date.
+ */
+ if (DEBUG(MAKE)) {
+ if (gn->type & OP_FORCE) {
+ fprintf(debug_file, "! operator...");
+ } else if (gn->type & OP_PHONY) {
+ fprintf(debug_file, ".PHONY node...");
+ } else {
+ fprintf(debug_file, ".EXEC node...");
+ }
+ }
+ oodate = TRUE;
+ } else if ((gn->cmgn != NULL && gn->mtime < gn->cmgn->mtime) ||
+ (gn->cmgn == NULL &&
+ ((gn->mtime == 0 && !(gn->type & OP_OPTIONAL))
+ || gn->type & OP_DOUBLEDEP)))
+ {
+ /*
+ * A node whose modification time is less than that of its
+ * youngest child or that has no children (cmgn == NULL) and
+ * either doesn't exist (mtime == 0) and it isn't optional
+ * or was the object of a * :: operator is out-of-date.
+ * Why? Because that's the way Make does it.
+ */
+ if (DEBUG(MAKE)) {
+ if (gn->cmgn != NULL && gn->mtime < gn->cmgn->mtime) {
+ fprintf(debug_file, "modified before source %s...",
+ gn->cmgn->path ? gn->cmgn->path : gn->cmgn->name);
+ } else if (gn->mtime == 0) {
+ fprintf(debug_file, "non-existent and no sources...");
+ } else {
+ fprintf(debug_file, ":: operator and no sources...");
+ }
+ }
+ oodate = TRUE;
+ } else {
+ /*
+ * When a non-existing child with no sources
+ * (such as a typically used FORCE source) has been made and
+ * the target of the child (usually a directory) has the same
+ * timestamp as the timestamp just given to the non-existing child
+ * after it was considered made.
+ */
+ if (DEBUG(MAKE)) {
+ if (gn->flags & FORCE)
+ fprintf(debug_file, "non existing child...");
+ }
+ oodate = (gn->flags & FORCE) ? TRUE : FALSE;
+ }
+
+#ifdef USE_META
+ if (useMeta) {
+ oodate = meta_oodate(gn, oodate);
+ }
+#endif
+
+ /*
+ * If the target isn't out-of-date, the parents need to know its
+ * modification time. Note that targets that appear to be out-of-date
+ * but aren't, because they have no commands and aren't of type OP_NOP,
+ * have their mtime stay below their children's mtime to keep parents from
+ * thinking they're out-of-date.
+ */
+ if (!oodate) {
+ Lst_ForEach(gn->parents, MakeTimeStamp, gn);
+ }
+
+ return (oodate);
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * MakeAddChild --
+ * Function used by Make_Run to add a child to the list l.
+ * It will only add the child if its make field is FALSE.
+ *
+ * Input:
+ * gnp the node to add
+ * lp the list to which to add it
+ *
+ * Results:
+ * Always returns 0
+ *
+ * Side Effects:
+ * The given list is extended
+ *-----------------------------------------------------------------------
+ */
+static int
+MakeAddChild(void *gnp, void *lp)
+{
+ GNode *gn = (GNode *)gnp;
+ Lst l = (Lst) lp;
+
+ if ((gn->flags & REMAKE) == 0 && !(gn->type & (OP_USE|OP_USEBEFORE))) {
+ if (DEBUG(MAKE))
+ fprintf(debug_file, "MakeAddChild: need to examine %s%s\n",
+ gn->name, gn->cohort_num);
+ (void)Lst_EnQueue(l, gn);
+ }
+ return (0);
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * MakeFindChild --
+ * Function used by Make_Run to find the pathname of a child
+ * that was already made.
+ *
+ * Input:
+ * gnp the node to find
+ *
+ * Results:
+ * Always returns 0
+ *
+ * Side Effects:
+ * The path and mtime of the node and the cmgn of the parent are
+ * updated; the unmade children count of the parent is decremented.
+ *-----------------------------------------------------------------------
+ */
+static int
+MakeFindChild(void *gnp, void *pgnp)
+{
+ GNode *gn = (GNode *)gnp;
+ GNode *pgn = (GNode *)pgnp;
+
+ (void)Dir_MTime(gn, 0);
+ Make_TimeStamp(pgn, gn);
+ pgn->unmade--;
+
+ return (0);
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * Make_HandleUse --
+ * Function called by Make_Run and SuffApplyTransform on the downward
+ * pass to handle .USE and transformation nodes. It implements the
+ * .USE and transformation functionality by copying the node's commands,
+ * type flags and children to the parent node.
+ *
+ * A .USE node is much like an explicit transformation rule, except
+ * its commands are always added to the target node, even if the
+ * target already has commands.
+ *
+ * Input:
+ * cgn The .USE node
+ * pgn The target of the .USE node
+ *
+ * Results:
+ * none
+ *
+ * Side Effects:
+ * Children and commands may be added to the parent and the parent's
+ * type may be changed.
+ *
+ *-----------------------------------------------------------------------
+ */
+void
+Make_HandleUse(GNode *cgn, GNode *pgn)
+{
+ LstNode ln; /* An element in the children list */
+
+#ifdef DEBUG_SRC
+ if ((cgn->type & (OP_USE|OP_USEBEFORE|OP_TRANSFORM)) == 0) {
+ fprintf(debug_file, "Make_HandleUse: called for plain node %s\n", cgn->name);
+ return;
+ }
+#endif
+
+ if ((cgn->type & (OP_USE|OP_USEBEFORE)) || Lst_IsEmpty(pgn->commands)) {
+ if (cgn->type & OP_USEBEFORE) {
+ /*
+ * .USEBEFORE --
+ * prepend the child's commands to the parent.
+ */
+ Lst cmds = pgn->commands;
+ pgn->commands = Lst_Duplicate(cgn->commands, NULL);
+ (void)Lst_Concat(pgn->commands, cmds, LST_CONCNEW);
+ Lst_Destroy(cmds, NULL);
+ } else {
+ /*
+ * .USE or target has no commands --
+ * append the child's commands to the parent.
+ */
+ (void)Lst_Concat(pgn->commands, cgn->commands, LST_CONCNEW);
+ }
+ }
+
+ if (Lst_Open(cgn->children) == SUCCESS) {
+ while ((ln = Lst_Next(cgn->children)) != NULL) {
+ GNode *tgn, *gn = (GNode *)Lst_Datum(ln);
+
+ /*
+ * Expand variables in the .USE node's name
+ * and save the unexpanded form.
+ * We don't need to do this for commands.
+ * They get expanded properly when we execute.
+ */
+ if (gn->uname == NULL) {
+ gn->uname = gn->name;
+ } else {
+ free(gn->name);
+ }
+ gn->name = Var_Subst(NULL, gn->uname, pgn, VARF_WANTRES);
+ if (gn->name && gn->uname && strcmp(gn->name, gn->uname) != 0) {
+ /* See if we have a target for this node. */
+ tgn = Targ_FindNode(gn->name, TARG_NOCREATE);
+ if (tgn != NULL)
+ gn = tgn;
+ }
+
+ (void)Lst_AtEnd(pgn->children, gn);
+ (void)Lst_AtEnd(gn->parents, pgn);
+ pgn->unmade += 1;
+ }
+ Lst_Close(cgn->children);
+ }
+
+ pgn->type |= cgn->type & ~(OP_OPMASK|OP_USE|OP_USEBEFORE|OP_TRANSFORM);
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * MakeHandleUse --
+ * Callback function for Lst_ForEach, used by Make_Run on the downward
+ * pass to handle .USE nodes. Should be called before the children
+ * are enqueued to be looked at by MakeAddChild.
+ * This function calls Make_HandleUse to copy the .USE node's commands,
+ * type flags and children to the parent node.
+ *
+ * Input:
+ * cgnp the child we've just examined
+ * pgnp the current parent
+ *
+ * Results:
+ * returns 0.
+ *
+ * Side Effects:
+ * After expansion, .USE child nodes are removed from the parent
+ *
+ *-----------------------------------------------------------------------
+ */
+static int
+MakeHandleUse(void *cgnp, void *pgnp)
+{
+ GNode *cgn = (GNode *)cgnp;
+ GNode *pgn = (GNode *)pgnp;
+ LstNode ln; /* An element in the children list */
+ int unmarked;
+
+ unmarked = ((cgn->type & OP_MARK) == 0);
+ cgn->type |= OP_MARK;
+
+ if ((cgn->type & (OP_USE|OP_USEBEFORE)) == 0)
+ return (0);
+
+ if (unmarked)
+ Make_HandleUse(cgn, pgn);
+
+ /*
+ * This child node is now "made", so we decrement the count of
+ * unmade children in the parent... We also remove the child
+ * from the parent's list to accurately reflect the number of decent
+ * children the parent has. This is used by Make_Run to decide
+ * whether to queue the parent or examine its children...
+ */
+ if ((ln = Lst_Member(pgn->children, cgn)) != NULL) {
+ Lst_Remove(pgn->children, ln);
+ pgn->unmade--;
+ }
+ return (0);
+}
+
+
+/*-
+ *-----------------------------------------------------------------------
+ * Make_Recheck --
+ * Check the modification time of a gnode, and update it as described
+ * in the comments below.
+ *
+ * Results:
+ * returns 0 if the gnode does not exist, or its filesystem
+ * time if it does.
+ *
+ * Side Effects:
+ * the gnode's modification time and path name are affected.
+ *
+ *-----------------------------------------------------------------------
+ */
+time_t
+Make_Recheck(GNode *gn)
+{
+ time_t mtime = Dir_MTime(gn, 1);
+
+#ifndef RECHECK
+ /*
+ * We can't re-stat the thing, but we can at least take care of rules
+ * where a target depends on a source that actually creates the
+ * target, but only if it has changed, e.g.
+ *
+ * parse.h : parse.o
+ *
+ * parse.o : parse.y
+ * yacc -d parse.y
+ * cc -c y.tab.c
+ * mv y.tab.o parse.o
+ * cmp -s y.tab.h parse.h || mv y.tab.h parse.h
+ *
+ * In this case, if the definitions produced by yacc haven't changed
+ * from before, parse.h won't have been updated and gn->mtime will
+ * reflect the current modification time for parse.h. This is
+ * something of a kludge, I admit, but it's a useful one..
+ * XXX: People like to use a rule like
+ *
+ * FRC:
+ *
+ * To force things that depend on FRC to be made, so we have to
+ * check for gn->children being empty as well...
+ */
+ if (!Lst_IsEmpty(gn->commands) || Lst_IsEmpty(gn->children)) {
+ gn->mtime = now;
+ }
+#else
+ /*
+ * This is what Make does and it's actually a good thing, as it
+ * allows rules like
+ *
+ * cmp -s y.tab.h parse.h || cp y.tab.h parse.h
+ *
+ * to function as intended. Unfortunately, thanks to the stateless
+ * nature of NFS (by which I mean the loose coupling of two clients
+ * using the same file from a common server), there are times
+ * when the modification time of a file created on a remote
+ * machine will not be modified before the local stat() implied by
+ * the Dir_MTime occurs, thus leading us to believe that the file
+ * is unchanged, wreaking havoc with files that depend on this one.
+ *
+ * I have decided it is better to make too much than to make too
+ * little, so this stuff is commented out unless you're sure it's ok.
+ * -- ardeb 1/12/88
+ */
+ /*
+ * Christos, 4/9/92: If we are saving commands pretend that
+ * the target is made now. Otherwise archives with ... rules
+ * don't work!
+ */
+ if (NoExecute(gn) || (gn->type & OP_SAVE_CMDS) ||
+ (mtime == 0 && !(gn->type & OP_WAIT))) {
+ if (DEBUG(MAKE)) {
+ fprintf(debug_file, " recheck(%s): update time from %s to now\n",
+ gn->name, Targ_FmtTime(gn->mtime));
+ }
+ gn->mtime = now;
+ }
+ else {
+ if (DEBUG(MAKE)) {
+ fprintf(debug_file, " recheck(%s): current update time: %s\n",
+ gn->name, Targ_FmtTime(gn->mtime));
+ }
+ }
+#endif
+ return mtime;
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * Make_Update --
+ * Perform update on the parents of a node. Used by JobFinish once
+ * a node has been dealt with and by MakeStartJobs if it finds an
+ * up-to-date node.
+ *
+ * Input:
+ * cgn the child node
+ *
+ * Results:
+ * Always returns 0
+ *
+ * Side Effects:
+ * The unmade field of pgn is decremented and pgn may be placed on
+ * the toBeMade queue if this field becomes 0.
+ *
+ * If the child was made, the parent's flag CHILDMADE field will be
+ * set true.
+ *
+ * If the child is not up-to-date and still does not exist,
+ * set the FORCE flag on the parents.
+ *
+ * If the child wasn't made, the cmgn field of the parent will be
+ * altered if the child's mtime is big enough.
+ *
+ * Finally, if the child is the implied source for the parent, the
+ * parent's IMPSRC variable is set appropriately.
+ *
+ *-----------------------------------------------------------------------
+ */
+void
+Make_Update(GNode *cgn)
+{
+ GNode *pgn; /* the parent node */
+ char *cname; /* the child's name */
+ LstNode ln; /* Element in parents and iParents lists */
+ time_t mtime = -1;
+ char *p1;
+ Lst parents;
+ GNode *centurion;
+
+ /* It is save to re-examine any nodes again */
+ checked++;
+
+ cname = Var_Value(TARGET, cgn, &p1);
+ free(p1);
+
+ if (DEBUG(MAKE))
+ fprintf(debug_file, "Make_Update: %s%s\n", cgn->name, cgn->cohort_num);
+
+ /*
+ * If the child was actually made, see what its modification time is
+ * now -- some rules won't actually update the file. If the file still
+ * doesn't exist, make its mtime now.
+ */
+ if (cgn->made != UPTODATE) {
+ mtime = Make_Recheck(cgn);
+ }
+
+ /*
+ * If this is a `::' node, we must consult its first instance
+ * which is where all parents are linked.
+ */
+ if ((centurion = cgn->centurion) != NULL) {
+ if (!Lst_IsEmpty(cgn->parents))
+ Punt("%s%s: cohort has parents", cgn->name, cgn->cohort_num);
+ centurion->unmade_cohorts -= 1;
+ if (centurion->unmade_cohorts < 0)
+ Error("Graph cycles through centurion %s", centurion->name);
+ } else {
+ centurion = cgn;
+ }
+ parents = centurion->parents;
+
+ /* If this was a .ORDER node, schedule the RHS */
+ Lst_ForEach(centurion->order_succ, MakeBuildParent, Lst_First(toBeMade));
+
+ /* Now mark all the parents as having one less unmade child */
+ if (Lst_Open(parents) == SUCCESS) {
+ while ((ln = Lst_Next(parents)) != NULL) {
+ pgn = (GNode *)Lst_Datum(ln);
+ if (DEBUG(MAKE))
+ fprintf(debug_file, "inspect parent %s%s: flags %x, "
+ "type %x, made %d, unmade %d ",
+ pgn->name, pgn->cohort_num, pgn->flags,
+ pgn->type, pgn->made, pgn->unmade-1);
+
+ if (!(pgn->flags & REMAKE)) {
+ /* This parent isn't needed */
+ if (DEBUG(MAKE))
+ fprintf(debug_file, "- not needed\n");
+ continue;
+ }
+ if (mtime == 0 && !(cgn->type & OP_WAIT))
+ pgn->flags |= FORCE;
+
+ /*
+ * If the parent has the .MADE attribute, its timestamp got
+ * updated to that of its newest child, and its unmake
+ * child count got set to zero in Make_ExpandUse().
+ * However other things might cause us to build one of its
+ * children - and so we mustn't do any processing here when
+ * the child build finishes.
+ */
+ if (pgn->type & OP_MADE) {
+ if (DEBUG(MAKE))
+ fprintf(debug_file, "- .MADE\n");
+ continue;
+ }
+
+ if ( ! (cgn->type & (OP_EXEC|OP_USE|OP_USEBEFORE))) {
+ if (cgn->made == MADE)
+ pgn->flags |= CHILDMADE;
+ (void)Make_TimeStamp(pgn, cgn);
+ }
+
+ /*
+ * A parent must wait for the completion of all instances
+ * of a `::' dependency.
+ */
+ if (centurion->unmade_cohorts != 0 || centurion->made < MADE) {
+ if (DEBUG(MAKE))
+ fprintf(debug_file,
+ "- centurion made %d, %d unmade cohorts\n",
+ centurion->made, centurion->unmade_cohorts);
+ continue;
+ }
+
+ /* One more child of this parent is now made */
+ pgn->unmade -= 1;
+ if (pgn->unmade < 0) {
+ if (DEBUG(MAKE)) {
+ fprintf(debug_file, "Graph cycles through %s%s\n",
+ pgn->name, pgn->cohort_num);
+ Targ_PrintGraph(2);
+ }
+ Error("Graph cycles through %s%s", pgn->name, pgn->cohort_num);
+ }
+
+ /* We must always rescan the parents of .WAIT and .ORDER nodes. */
+ if (pgn->unmade != 0 && !(centurion->type & OP_WAIT)
+ && !(centurion->flags & DONE_ORDER)) {
+ if (DEBUG(MAKE))
+ fprintf(debug_file, "- unmade children\n");
+ continue;
+ }
+ if (pgn->made != DEFERRED) {
+ /*
+ * Either this parent is on a different branch of the tree,
+ * or it on the RHS of a .WAIT directive
+ * or it is already on the toBeMade list.
+ */
+ if (DEBUG(MAKE))
+ fprintf(debug_file, "- not deferred\n");
+ continue;
+ }
+ if (pgn->order_pred
+ && Lst_ForEach(pgn->order_pred, MakeCheckOrder, 0)) {
+ /* A .ORDER rule stops us building this */
+ continue;
+ }
+ if (DEBUG(MAKE)) {
+ static int two = 2;
+ fprintf(debug_file, "- %s%s made, schedule %s%s (made %d)\n",
+ cgn->name, cgn->cohort_num,
+ pgn->name, pgn->cohort_num, pgn->made);
+ Targ_PrintNode(pgn, &two);
+ }
+ /* Ok, we can schedule the parent again */
+ pgn->made = REQUESTED;
+ (void)Lst_EnQueue(toBeMade, pgn);
+ }
+ Lst_Close(parents);
+ }
+
+ /*
+ * Set the .PREFIX and .IMPSRC variables for all the implied parents
+ * of this node.
+ */
+ if (Lst_Open(cgn->iParents) == SUCCESS) {
+ char *cpref = Var_Value(PREFIX, cgn, &p1);
+
+ while ((ln = Lst_Next(cgn->iParents)) != NULL) {
+ pgn = (GNode *)Lst_Datum(ln);
+ if (pgn->flags & REMAKE) {
+ Var_Set(IMPSRC, cname, pgn, 0);
+ if (cpref != NULL)
+ Var_Set(PREFIX, cpref, pgn, 0);
+ }
+ }
+ free(p1);
+ Lst_Close(cgn->iParents);
+ }
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * MakeAddAllSrc --
+ * Add a child's name to the ALLSRC and OODATE variables of the given
+ * node. Called from Make_DoAllVar via Lst_ForEach. A child is added only
+ * if it has not been given the .EXEC, .USE or .INVISIBLE attributes.
+ * .EXEC and .USE children are very rarely going to be files, so...
+ * If the child is a .JOIN node, its ALLSRC is propagated to the parent.
+ *
+ * A child is added to the OODATE variable if its modification time is
+ * later than that of its parent, as defined by Make, except if the
+ * parent is a .JOIN node. In that case, it is only added to the OODATE
+ * variable if it was actually made (since .JOIN nodes don't have
+ * modification times, the comparison is rather unfair...)..
+ *
+ * Results:
+ * Always returns 0
+ *
+ * Side Effects:
+ * The ALLSRC variable for the given node is extended.
+ *-----------------------------------------------------------------------
+ */
+static int
+MakeUnmark(void *cgnp, void *pgnp MAKE_ATTR_UNUSED)
+{
+ GNode *cgn = (GNode *)cgnp;
+
+ cgn->type &= ~OP_MARK;
+ return (0);
+}
+
+/*
+ * Input:
+ * cgnp The child to add
+ * pgnp The parent to whose ALLSRC variable it should
+ * be added
+ *
+ */
+static int
+MakeAddAllSrc(void *cgnp, void *pgnp)
+{
+ GNode *cgn = (GNode *)cgnp;
+ GNode *pgn = (GNode *)pgnp;
+
+ if (cgn->type & OP_MARK)
+ return (0);
+ cgn->type |= OP_MARK;
+
+ if ((cgn->type & (OP_EXEC|OP_USE|OP_USEBEFORE|OP_INVISIBLE)) == 0) {
+ char *child, *allsrc;
+ char *p1 = NULL, *p2 = NULL;
+
+ if (cgn->type & OP_ARCHV)
+ child = Var_Value(MEMBER, cgn, &p1);
+ else
+ child = cgn->path ? cgn->path : cgn->name;
+ if (cgn->type & OP_JOIN) {
+ allsrc = Var_Value(ALLSRC, cgn, &p2);
+ } else {
+ allsrc = child;
+ }
+ if (allsrc != NULL)
+ Var_Append(ALLSRC, allsrc, pgn);
+ free(p2);
+ if (pgn->type & OP_JOIN) {
+ if (cgn->made == MADE) {
+ Var_Append(OODATE, child, pgn);
+ }
+ } else if ((pgn->mtime < cgn->mtime) ||
+ (cgn->mtime >= now && cgn->made == MADE))
+ {
+ /*
+ * It goes in the OODATE variable if the parent is younger than the
+ * child or if the child has been modified more recently than
+ * the start of the make. This is to keep pmake from getting
+ * confused if something else updates the parent after the
+ * make starts (shouldn't happen, I know, but sometimes it
+ * does). In such a case, if we've updated the kid, the parent
+ * is likely to have a modification time later than that of
+ * the kid and anything that relies on the OODATE variable will
+ * be hosed.
+ *
+ * XXX: This will cause all made children to go in the OODATE
+ * variable, even if they're not touched, if RECHECK isn't defined,
+ * since cgn->mtime is set to now in Make_Update. According to
+ * some people, this is good...
+ */
+ Var_Append(OODATE, child, pgn);
+ }
+ free(p1);
+ }
+ return (0);
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * Make_DoAllVar --
+ * Set up the ALLSRC and OODATE variables. Sad to say, it must be
+ * done separately, rather than while traversing the graph. This is
+ * because Make defined OODATE to contain all sources whose modification
+ * times were later than that of the target, *not* those sources that
+ * were out-of-date. Since in both compatibility and native modes,
+ * the modification time of the parent isn't found until the child
+ * has been dealt with, we have to wait until now to fill in the
+ * variable. As for ALLSRC, the ordering is important and not
+ * guaranteed when in native mode, so it must be set here, too.
+ *
+ * Results:
+ * None
+ *
+ * Side Effects:
+ * The ALLSRC and OODATE variables of the given node is filled in.
+ * If the node is a .JOIN node, its TARGET variable will be set to
+ * match its ALLSRC variable.
+ *-----------------------------------------------------------------------
+ */
+void
+Make_DoAllVar(GNode *gn)
+{
+ if (gn->flags & DONE_ALLSRC)
+ return;
+
+ Lst_ForEach(gn->children, MakeUnmark, gn);
+ Lst_ForEach(gn->children, MakeAddAllSrc, gn);
+
+ if (!Var_Exists (OODATE, gn)) {
+ Var_Set(OODATE, "", gn, 0);
+ }
+ if (!Var_Exists (ALLSRC, gn)) {
+ Var_Set(ALLSRC, "", gn, 0);
+ }
+
+ if (gn->type & OP_JOIN) {
+ char *p1;
+ Var_Set(TARGET, Var_Value(ALLSRC, gn, &p1), gn, 0);
+ free(p1);
+ }
+ gn->flags |= DONE_ALLSRC;
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * MakeStartJobs --
+ * Start as many jobs as possible.
+ *
+ * Results:
+ * If the query flag was given to pmake, no job will be started,
+ * but as soon as an out-of-date target is found, this function
+ * returns TRUE. At all other times, this function returns FALSE.
+ *
+ * Side Effects:
+ * Nodes are removed from the toBeMade queue and job table slots
+ * are filled.
+ *
+ *-----------------------------------------------------------------------
+ */
+
+static int
+MakeCheckOrder(void *v_bn, void *ignore MAKE_ATTR_UNUSED)
+{
+ GNode *bn = v_bn;
+
+ if (bn->made >= MADE || !(bn->flags & REMAKE))
+ return 0;
+ if (DEBUG(MAKE))
+ fprintf(debug_file, "MakeCheckOrder: Waiting for .ORDER node %s%s\n",
+ bn->name, bn->cohort_num);
+ return 1;
+}
+
+static int
+MakeBuildChild(void *v_cn, void *toBeMade_next)
+{
+ GNode *cn = v_cn;
+
+ if (DEBUG(MAKE))
+ fprintf(debug_file, "MakeBuildChild: inspect %s%s, made %d, type %x\n",
+ cn->name, cn->cohort_num, cn->made, cn->type);
+ if (cn->made > DEFERRED)
+ return 0;
+
+ /* If this node is on the RHS of a .ORDER, check LHSs. */
+ if (cn->order_pred && Lst_ForEach(cn->order_pred, MakeCheckOrder, 0)) {
+ /* Can't build this (or anything else in this child list) yet */
+ cn->made = DEFERRED;
+ return 0; /* but keep looking */
+ }
+
+ if (DEBUG(MAKE))
+ fprintf(debug_file, "MakeBuildChild: schedule %s%s\n",
+ cn->name, cn->cohort_num);
+
+ cn->made = REQUESTED;
+ if (toBeMade_next == NULL)
+ Lst_AtEnd(toBeMade, cn);
+ else
+ Lst_InsertBefore(toBeMade, toBeMade_next, cn);
+
+ if (cn->unmade_cohorts != 0)
+ Lst_ForEach(cn->cohorts, MakeBuildChild, toBeMade_next);
+
+ /*
+ * If this node is a .WAIT node with unmade chlidren
+ * then don't add the next sibling.
+ */
+ return cn->type & OP_WAIT && cn->unmade > 0;
+}
+
+/* When a .ORDER LHS node completes we do this on each RHS */
+static int
+MakeBuildParent(void *v_pn, void *toBeMade_next)
+{
+ GNode *pn = v_pn;
+
+ if (pn->made != DEFERRED)
+ return 0;
+
+ if (MakeBuildChild(pn, toBeMade_next) == 0) {
+ /* Mark so that when this node is built we reschedule its parents */
+ pn->flags |= DONE_ORDER;
+ }
+
+ return 0;
+}
+
+static Boolean
+MakeStartJobs(void)
+{
+ GNode *gn;
+ int have_token = 0;
+
+ while (!Lst_IsEmpty (toBeMade)) {
+ /* Get token now to avoid cycling job-list when we only have 1 token */
+ if (!have_token && !Job_TokenWithdraw())
+ break;
+ have_token = 1;
+
+ gn = (GNode *)Lst_DeQueue(toBeMade);
+ if (DEBUG(MAKE))
+ fprintf(debug_file, "Examining %s%s...\n",
+ gn->name, gn->cohort_num);
+
+ if (gn->made != REQUESTED) {
+ if (DEBUG(MAKE))
+ fprintf(debug_file, "state %d\n", gn->made);
+
+ make_abort(gn, __LINE__);
+ }
+
+ if (gn->checked == checked) {
+ /* We've already looked at this node since a job finished... */
+ if (DEBUG(MAKE))
+ fprintf(debug_file, "already checked %s%s\n",
+ gn->name, gn->cohort_num);
+ gn->made = DEFERRED;
+ continue;
+ }
+ gn->checked = checked;
+
+ if (gn->unmade != 0) {
+ /*
+ * We can't build this yet, add all unmade children to toBeMade,
+ * just before the current first element.
+ */
+ gn->made = DEFERRED;
+ Lst_ForEach(gn->children, MakeBuildChild, Lst_First(toBeMade));
+ /* and drop this node on the floor */
+ if (DEBUG(MAKE))
+ fprintf(debug_file, "dropped %s%s\n", gn->name, gn->cohort_num);
+ continue;
+ }
+
+ gn->made = BEINGMADE;
+ if (Make_OODate(gn)) {
+ if (DEBUG(MAKE)) {
+ fprintf(debug_file, "out-of-date\n");
+ }
+ if (queryFlag) {
+ return (TRUE);
+ }
+ Make_DoAllVar(gn);
+ Job_Make(gn);
+ have_token = 0;
+ } else {
+ if (DEBUG(MAKE)) {
+ fprintf(debug_file, "up-to-date\n");
+ }
+ gn->made = UPTODATE;
+ if (gn->type & OP_JOIN) {
+ /*
+ * Even for an up-to-date .JOIN node, we need it to have its
+ * context variables so references to it get the correct
+ * value for .TARGET when building up the context variables
+ * of its parent(s)...
+ */
+ Make_DoAllVar(gn);
+ }
+ Make_Update(gn);
+ }
+ }
+
+ if (have_token)
+ Job_TokenReturn();
+
+ return (FALSE);
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * MakePrintStatus --
+ * Print the status of a top-level node, viz. it being up-to-date
+ * already or not created due to an error in a lower level.
+ * Callback function for Make_Run via Lst_ForEach.
+ *
+ * Input:
+ * gnp Node to examine
+ * cyclep True if gn->unmade being non-zero implies a
+ * cycle in the graph, not an error in an
+ * inferior.
+ *
+ * Results:
+ * Always returns 0.
+ *
+ * Side Effects:
+ * A message may be printed.
+ *
+ *-----------------------------------------------------------------------
+ */
+static int
+MakePrintStatusOrder(void *ognp, void *gnp)
+{
+ GNode *ogn = ognp;
+ GNode *gn = gnp;
+
+ if (!(ogn->flags & REMAKE) || ogn->made > REQUESTED)
+ /* not waiting for this one */
+ return 0;
+
+ printf(" `%s%s' has .ORDER dependency against %s%s "
+ "(made %d, flags %x, type %x)\n",
+ gn->name, gn->cohort_num,
+ ogn->name, ogn->cohort_num, ogn->made, ogn->flags, ogn->type);
+ if (DEBUG(MAKE) && debug_file != stdout)
+ fprintf(debug_file, " `%s%s' has .ORDER dependency against %s%s "
+ "(made %d, flags %x, type %x)\n",
+ gn->name, gn->cohort_num,
+ ogn->name, ogn->cohort_num, ogn->made, ogn->flags, ogn->type);
+ return 0;
+}
+
+static int
+MakePrintStatus(void *gnp, void *v_errors)
+{
+ GNode *gn = (GNode *)gnp;
+ int *errors = v_errors;
+
+ if (gn->flags & DONECYCLE)
+ /* We've completely processed this node before, don't do it again. */
+ return 0;
+
+ if (gn->unmade == 0) {
+ gn->flags |= DONECYCLE;
+ switch (gn->made) {
+ case UPTODATE:
+ printf("`%s%s' is up to date.\n", gn->name, gn->cohort_num);
+ break;
+ case MADE:
+ break;
+ case UNMADE:
+ case DEFERRED:
+ case REQUESTED:
+ case BEINGMADE:
+ (*errors)++;
+ printf("`%s%s' was not built (made %d, flags %x, type %x)!\n",
+ gn->name, gn->cohort_num, gn->made, gn->flags, gn->type);
+ if (DEBUG(MAKE) && debug_file != stdout)
+ fprintf(debug_file,
+ "`%s%s' was not built (made %d, flags %x, type %x)!\n",
+ gn->name, gn->cohort_num, gn->made, gn->flags, gn->type);
+ /* Most likely problem is actually caused by .ORDER */
+ Lst_ForEach(gn->order_pred, MakePrintStatusOrder, gn);
+ break;
+ default:
+ /* Errors - already counted */
+ printf("`%s%s' not remade because of errors.\n",
+ gn->name, gn->cohort_num);
+ if (DEBUG(MAKE) && debug_file != stdout)
+ fprintf(debug_file, "`%s%s' not remade because of errors.\n",
+ gn->name, gn->cohort_num);
+ break;
+ }
+ return 0;
+ }
+
+ if (DEBUG(MAKE))
+ fprintf(debug_file, "MakePrintStatus: %s%s has %d unmade children\n",
+ gn->name, gn->cohort_num, gn->unmade);
+ /*
+ * If printing cycles and came to one that has unmade children,
+ * print out the cycle by recursing on its children.
+ */
+ if (!(gn->flags & CYCLE)) {
+ /* Fist time we've seen this node, check all children */
+ gn->flags |= CYCLE;
+ Lst_ForEach(gn->children, MakePrintStatus, errors);
+ /* Mark that this node needn't be processed again */
+ gn->flags |= DONECYCLE;
+ return 0;
+ }
+
+ /* Only output the error once per node */
+ gn->flags |= DONECYCLE;
+ Error("Graph cycles through `%s%s'", gn->name, gn->cohort_num);
+ if ((*errors)++ > 100)
+ /* Abandon the whole error report */
+ return 1;
+
+ /* Reporting for our children will give the rest of the loop */
+ Lst_ForEach(gn->children, MakePrintStatus, errors);
+ return 0;
+}
+
+
+/*-
+ *-----------------------------------------------------------------------
+ * Make_ExpandUse --
+ * Expand .USE nodes and create a new targets list
+ *
+ * Input:
+ * targs the initial list of targets
+ *
+ * Side Effects:
+ *-----------------------------------------------------------------------
+ */
+void
+Make_ExpandUse(Lst targs)
+{
+ GNode *gn; /* a temporary pointer */
+ Lst examine; /* List of targets to examine */
+
+ examine = Lst_Duplicate(targs, NULL);
+
+ /*
+ * Make an initial downward pass over the graph, marking nodes to be made
+ * as we go down. We call Suff_FindDeps to find where a node is and
+ * to get some children for it if it has none and also has no commands.
+ * If the node is a leaf, we stick it on the toBeMade queue to
+ * be looked at in a minute, otherwise we add its children to our queue
+ * and go on about our business.
+ */
+ while (!Lst_IsEmpty (examine)) {
+ gn = (GNode *)Lst_DeQueue(examine);
+
+ if (gn->flags & REMAKE)
+ /* We've looked at this one already */
+ continue;
+ gn->flags |= REMAKE;
+ if (DEBUG(MAKE))
+ fprintf(debug_file, "Make_ExpandUse: examine %s%s\n",
+ gn->name, gn->cohort_num);
+
+ if ((gn->type & OP_DOUBLEDEP) && !Lst_IsEmpty (gn->cohorts)) {
+ /* Append all the 'cohorts' to the list of things to examine */
+ Lst new;
+ new = Lst_Duplicate(gn->cohorts, NULL);
+ Lst_Concat(new, examine, LST_CONCLINK);
+ examine = new;
+ }
+
+ /*
+ * Apply any .USE rules before looking for implicit dependencies
+ * to make sure everything has commands that should...
+ * Make sure that the TARGET is set, so that we can make
+ * expansions.
+ */
+ if (gn->type & OP_ARCHV) {
+ char *eoa, *eon;
+ eoa = strchr(gn->name, '(');
+ eon = strchr(gn->name, ')');
+ if (eoa == NULL || eon == NULL)
+ continue;
+ *eoa = '\0';
+ *eon = '\0';
+ Var_Set(MEMBER, eoa + 1, gn, 0);
+ Var_Set(ARCHIVE, gn->name, gn, 0);
+ *eoa = '(';
+ *eon = ')';
+ }
+
+ (void)Dir_MTime(gn, 0);
+ Var_Set(TARGET, gn->path ? gn->path : gn->name, gn, 0);
+ Lst_ForEach(gn->children, MakeUnmark, gn);
+ Lst_ForEach(gn->children, MakeHandleUse, gn);
+
+ if ((gn->type & OP_MADE) == 0)
+ Suff_FindDeps(gn);
+ else {
+ /* Pretend we made all this node's children */
+ Lst_ForEach(gn->children, MakeFindChild, gn);
+ if (gn->unmade != 0)
+ printf("Warning: %s%s still has %d unmade children\n",
+ gn->name, gn->cohort_num, gn->unmade);
+ }
+
+ if (gn->unmade != 0)
+ Lst_ForEach(gn->children, MakeAddChild, examine);
+ }
+
+ Lst_Destroy(examine, NULL);
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * Make_ProcessWait --
+ * Convert .WAIT nodes into dependencies
+ *
+ * Input:
+ * targs the initial list of targets
+ *
+ *-----------------------------------------------------------------------
+ */
+
+static int
+link_parent(void *cnp, void *pnp)
+{
+ GNode *cn = cnp;
+ GNode *pn = pnp;
+
+ Lst_AtEnd(pn->children, cn);
+ Lst_AtEnd(cn->parents, pn);
+ pn->unmade++;
+ return 0;
+}
+
+static int
+add_wait_dep(void *v_cn, void *v_wn)
+{
+ GNode *cn = v_cn;
+ GNode *wn = v_wn;
+
+ if (cn == wn)
+ return 1;
+
+ if (cn == NULL || wn == NULL) {
+ printf("bad wait dep %p %p\n", cn, wn);
+ exit(4);
+ }
+ if (DEBUG(MAKE))
+ fprintf(debug_file, ".WAIT: add dependency %s%s -> %s\n",
+ cn->name, cn->cohort_num, wn->name);
+
+ Lst_AtEnd(wn->children, cn);
+ wn->unmade++;
+ Lst_AtEnd(cn->parents, wn);
+ return 0;
+}
+
+static void
+Make_ProcessWait(Lst targs)
+{
+ GNode *pgn; /* 'parent' node we are examining */
+ GNode *cgn; /* Each child in turn */
+ LstNode owln; /* Previous .WAIT node */
+ Lst examine; /* List of targets to examine */
+ LstNode ln;
+
+ /*
+ * We need all the nodes to have a common parent in order for the
+ * .WAIT and .ORDER scheduling to work.
+ * Perhaps this should be done earlier...
+ */
+
+ pgn = Targ_NewGN(".MAIN");
+ pgn->flags = REMAKE;
+ pgn->type = OP_PHONY | OP_DEPENDS;
+ /* Get it displayed in the diag dumps */
+ Lst_AtFront(Targ_List(), pgn);
+
+ Lst_ForEach(targs, link_parent, pgn);
+
+ /* Start building with the 'dummy' .MAIN' node */
+ MakeBuildChild(pgn, NULL);
+
+ examine = Lst_Init(FALSE);
+ Lst_AtEnd(examine, pgn);
+
+ while (!Lst_IsEmpty (examine)) {
+ pgn = Lst_DeQueue(examine);
+
+ /* We only want to process each child-list once */
+ if (pgn->flags & DONE_WAIT)
+ continue;
+ pgn->flags |= DONE_WAIT;
+ if (DEBUG(MAKE))
+ fprintf(debug_file, "Make_ProcessWait: examine %s\n", pgn->name);
+
+ if ((pgn->type & OP_DOUBLEDEP) && !Lst_IsEmpty (pgn->cohorts)) {
+ /* Append all the 'cohorts' to the list of things to examine */
+ Lst new;
+ new = Lst_Duplicate(pgn->cohorts, NULL);
+ Lst_Concat(new, examine, LST_CONCLINK);
+ examine = new;
+ }
+
+ owln = Lst_First(pgn->children);
+ Lst_Open(pgn->children);
+ for (; (ln = Lst_Next(pgn->children)) != NULL; ) {
+ cgn = Lst_Datum(ln);
+ if (cgn->type & OP_WAIT) {
+ /* Make the .WAIT node depend on the previous children */
+ Lst_ForEachFrom(pgn->children, owln, add_wait_dep, cgn);
+ owln = ln;
+ } else {
+ Lst_AtEnd(examine, cgn);
+ }
+ }
+ Lst_Close(pgn->children);
+ }
+
+ Lst_Destroy(examine, NULL);
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * Make_Run --
+ * Initialize the nodes to remake and the list of nodes which are
+ * ready to be made by doing a breadth-first traversal of the graph
+ * starting from the nodes in the given list. Once this traversal
+ * is finished, all the 'leaves' of the graph are in the toBeMade
+ * queue.
+ * Using this queue and the Job module, work back up the graph,
+ * calling on MakeStartJobs to keep the job table as full as
+ * possible.
+ *
+ * Input:
+ * targs the initial list of targets
+ *
+ * Results:
+ * TRUE if work was done. FALSE otherwise.
+ *
+ * Side Effects:
+ * The make field of all nodes involved in the creation of the given
+ * targets is set to 1. The toBeMade list is set to contain all the
+ * 'leaves' of these subgraphs.
+ *-----------------------------------------------------------------------
+ */
+Boolean
+Make_Run(Lst targs)
+{
+ int errors; /* Number of errors the Job module reports */
+
+ /* Start trying to make the current targets... */
+ toBeMade = Lst_Init(FALSE);
+
+ Make_ExpandUse(targs);
+ Make_ProcessWait(targs);
+
+ if (DEBUG(MAKE)) {
+ fprintf(debug_file, "#***# full graph\n");
+ Targ_PrintGraph(1);
+ }
+
+ if (queryFlag) {
+ /*
+ * We wouldn't do any work unless we could start some jobs in the
+ * next loop... (we won't actually start any, of course, this is just
+ * to see if any of the targets was out of date)
+ */
+ return (MakeStartJobs());
+ }
+ /*
+ * Initialization. At the moment, no jobs are running and until some
+ * get started, nothing will happen since the remaining upward
+ * traversal of the graph is performed by the routines in job.c upon
+ * the finishing of a job. So we fill the Job table as much as we can
+ * before going into our loop.
+ */
+ (void)MakeStartJobs();
+
+ /*
+ * Main Loop: The idea here is that the ending of jobs will take
+ * care of the maintenance of data structures and the waiting for output
+ * will cause us to be idle most of the time while our children run as
+ * much as possible. Because the job table is kept as full as possible,
+ * the only time when it will be empty is when all the jobs which need
+ * running have been run, so that is the end condition of this loop.
+ * Note that the Job module will exit if there were any errors unless the
+ * keepgoing flag was given.
+ */
+ while (!Lst_IsEmpty(toBeMade) || jobTokensRunning > 0) {
+ Job_CatchOutput();
+ (void)MakeStartJobs();
+ }
+
+ errors = Job_Finish();
+
+ /*
+ * Print the final status of each target. E.g. if it wasn't made
+ * because some inferior reported an error.
+ */
+ if (DEBUG(MAKE))
+ fprintf(debug_file, "done: errors %d\n", errors);
+ if (errors == 0) {
+ Lst_ForEach(targs, MakePrintStatus, &errors);
+ if (DEBUG(MAKE)) {
+ fprintf(debug_file, "done: errors %d\n", errors);
+ if (errors)
+ Targ_PrintGraph(4);
+ }
+ }
+ return errors != 0;
+}
diff --git a/usr.bin/make/make.h b/usr.bin/make/make.h
new file mode 100644
index 0000000..53e1895
--- /dev/null
+++ b/usr.bin/make/make.h
@@ -0,0 +1,535 @@
+/* $NetBSD: make.h,v 1.104 2018/02/12 21:38:09 sjg Exp $ */
+
+/*
+ * Copyright (c) 1988, 1989, 1990, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Adam de Boor.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * from: @(#)make.h 8.3 (Berkeley) 6/13/95
+ */
+
+/*
+ * Copyright (c) 1989 by Berkeley Softworks
+ * All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Adam de Boor.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. All advertising materials mentioning features or use of this software
+ * must display the following acknowledgement:
+ * This product includes software developed by the University of
+ * California, Berkeley and its contributors.
+ * 4. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * from: @(#)make.h 8.3 (Berkeley) 6/13/95
+ */
+
+/*-
+ * make.h --
+ * The global definitions for pmake
+ */
+
+#ifndef _MAKE_H_
+#define _MAKE_H_
+
+#include <sys/types.h>
+#include <sys/param.h>
+
+#include <ctype.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#ifdef BSD4_4
+# include <sys/cdefs.h>
+#endif
+
+#ifndef FD_CLOEXEC
+#define FD_CLOEXEC 1
+#endif
+
+#if defined(__GNUC__)
+#define MAKE_GNUC_PREREQ(x, y) \
+ ((__GNUC__ == (x) && __GNUC_MINOR__ >= (y)) || \
+ (__GNUC__ > (x)))
+#else /* defined(__GNUC__) */
+#define MAKE_GNUC_PREREQ(x, y) 0
+#endif /* defined(__GNUC__) */
+
+#if MAKE_GNUC_PREREQ(2, 7)
+#define MAKE_ATTR_UNUSED __attribute__((__unused__))
+#else
+#define MAKE_ATTR_UNUSED /* delete */
+#endif
+
+#if MAKE_GNUC_PREREQ(2, 5)
+#define MAKE_ATTR_DEAD __attribute__((__noreturn__))
+#elif defined(__GNUC__)
+#define MAKE_ATTR_DEAD __volatile
+#else
+#define MAKE_ATTR_DEAD /* delete */
+#endif
+
+#if MAKE_GNUC_PREREQ(2, 7)
+#define MAKE_ATTR_PRINTFLIKE(fmtarg, firstvararg) \
+ __attribute__((__format__ (__printf__, fmtarg, firstvararg)))
+#else
+#define MAKE_ATTR_PRINTFLIKE(fmtarg, firstvararg) /* delete */
+#endif
+
+#include "sprite.h"
+#include "lst.h"
+#include "hash.h"
+#include "config.h"
+#include "buf.h"
+#include "make_malloc.h"
+
+/*-
+ * The structure for an individual graph node. Each node has several
+ * pieces of data associated with it.
+ * 1) the name of the target it describes
+ * 2) the location of the target file in the file system.
+ * 3) the type of operator used to define its sources (qv. parse.c)
+ * 4) whether it is involved in this invocation of make
+ * 5) whether the target has been remade
+ * 6) whether any of its children has been remade
+ * 7) the number of its children that are, as yet, unmade
+ * 8) its modification time
+ * 9) the modification time of its youngest child (qv. make.c)
+ * 10) a list of nodes for which this is a source (parents)
+ * 11) a list of nodes on which this depends (children)
+ * 12) a list of nodes that depend on this, as gleaned from the
+ * transformation rules (iParents)
+ * 13) a list of ancestor nodes, which includes parents, iParents,
+ * and recursive parents of parents
+ * 14) a list of nodes of the same name created by the :: operator
+ * 15) a list of nodes that must be made (if they're made) before
+ * this node can be, but that do not enter into the datedness of
+ * this node.
+ * 16) a list of nodes that must be made (if they're made) before
+ * this node or any child of this node can be, but that do not
+ * enter into the datedness of this node.
+ * 17) a list of nodes that must be made (if they're made) after
+ * this node is, but that do not depend on this node, in the
+ * normal sense.
+ * 18) a Lst of ``local'' variables that are specific to this target
+ * and this target only (qv. var.c [$@ $< $?, etc.])
+ * 19) a Lst of strings that are commands to be given to a shell
+ * to create this target.
+ */
+typedef struct GNode {
+ char *name; /* The target's name */
+ char *uname; /* The unexpanded name of a .USE node */
+ char *path; /* The full pathname of the file */
+ int type; /* Its type (see the OP flags, below) */
+
+ int flags;
+#define REMAKE 0x1 /* this target needs to be (re)made */
+#define CHILDMADE 0x2 /* children of this target were made */
+#define FORCE 0x4 /* children don't exist, and we pretend made */
+#define DONE_WAIT 0x8 /* Set by Make_ProcessWait() */
+#define DONE_ORDER 0x10 /* Build requested by .ORDER processing */
+#define FROM_DEPEND 0x20 /* Node created from .depend */
+#define DONE_ALLSRC 0x40 /* We do it once only */
+#define CYCLE 0x1000 /* Used by MakePrintStatus */
+#define DONECYCLE 0x2000 /* Used by MakePrintStatus */
+#define INTERNAL 0x4000 /* Internal use only */
+ enum enum_made {
+ UNMADE, DEFERRED, REQUESTED, BEINGMADE,
+ MADE, UPTODATE, ERROR, ABORTED
+ } made; /* Set to reflect the state of processing
+ * on this node:
+ * UNMADE - Not examined yet
+ * DEFERRED - Examined once (building child)
+ * REQUESTED - on toBeMade list
+ * BEINGMADE - Target is already being made.
+ * Indicates a cycle in the graph.
+ * MADE - Was out-of-date and has been made
+ * UPTODATE - Was already up-to-date
+ * ERROR - An error occurred while it was being
+ * made (used only in compat mode)
+ * ABORTED - The target was aborted due to
+ * an error making an inferior (compat).
+ */
+ int unmade; /* The number of unmade children */
+
+ time_t mtime; /* Its modification time */
+ struct GNode *cmgn; /* The youngest child */
+
+ Lst iParents; /* Links to parents for which this is an
+ * implied source, if any */
+ Lst cohorts; /* Other nodes for the :: operator */
+ Lst parents; /* Nodes that depend on this one */
+ Lst children; /* Nodes on which this one depends */
+ Lst order_pred; /* .ORDER nodes we need made */
+ Lst order_succ; /* .ORDER nodes who need us */
+
+ char cohort_num[8]; /* #n for this cohort */
+ int unmade_cohorts;/* # of unmade instances on the
+ cohorts list */
+ struct GNode *centurion; /* Pointer to the first instance of a ::
+ node; only set when on a cohorts list */
+ unsigned int checked; /* Last time we tried to makle this node */
+
+ Hash_Table context; /* The local variables */
+ Lst commands; /* Creation commands */
+
+ struct _Suff *suffix; /* Suffix for the node (determined by
+ * Suff_FindDeps and opaque to everyone
+ * but the Suff module) */
+ const char *fname; /* filename where the GNode got defined */
+ int lineno; /* line number where the GNode got defined */
+} GNode;
+
+/*
+ * The OP_ constants are used when parsing a dependency line as a way of
+ * communicating to other parts of the program the way in which a target
+ * should be made. These constants are bitwise-OR'ed together and
+ * placed in the 'type' field of each node. Any node that has
+ * a 'type' field which satisfies the OP_NOP function was never never on
+ * the lefthand side of an operator, though it may have been on the
+ * righthand side...
+ */
+#define OP_DEPENDS 0x00000001 /* Execution of commands depends on
+ * kids (:) */
+#define OP_FORCE 0x00000002 /* Always execute commands (!) */
+#define OP_DOUBLEDEP 0x00000004 /* Execution of commands depends on kids
+ * per line (::) */
+#define OP_OPMASK (OP_DEPENDS|OP_FORCE|OP_DOUBLEDEP)
+
+#define OP_OPTIONAL 0x00000008 /* Don't care if the target doesn't
+ * exist and can't be created */
+#define OP_USE 0x00000010 /* Use associated commands for parents */
+#define OP_EXEC 0x00000020 /* Target is never out of date, but always
+ * execute commands anyway. Its time
+ * doesn't matter, so it has none...sort
+ * of */
+#define OP_IGNORE 0x00000040 /* Ignore errors when creating the node */
+#define OP_PRECIOUS 0x00000080 /* Don't remove the target when
+ * interrupted */
+#define OP_SILENT 0x00000100 /* Don't echo commands when executed */
+#define OP_MAKE 0x00000200 /* Target is a recursive make so its
+ * commands should always be executed when
+ * it is out of date, regardless of the
+ * state of the -n or -t flags */
+#define OP_JOIN 0x00000400 /* Target is out-of-date only if any of its
+ * children was out-of-date */
+#define OP_MADE 0x00000800 /* Assume the children of the node have
+ * been already made */
+#define OP_SPECIAL 0x00001000 /* Special .BEGIN, .END, .INTERRUPT */
+#define OP_USEBEFORE 0x00002000 /* Like .USE, only prepend commands */
+#define OP_INVISIBLE 0x00004000 /* The node is invisible to its parents.
+ * I.e. it doesn't show up in the parents's
+ * local variables. */
+#define OP_NOTMAIN 0x00008000 /* The node is exempt from normal 'main
+ * target' processing in parse.c */
+#define OP_PHONY 0x00010000 /* Not a file target; run always */
+#define OP_NOPATH 0x00020000 /* Don't search for file in the path */
+#define OP_WAIT 0x00040000 /* .WAIT phony node */
+#define OP_NOMETA 0x00080000 /* .NOMETA do not create a .meta file */
+#define OP_META 0x00100000 /* .META we _do_ want a .meta file */
+#define OP_NOMETA_CMP 0x00200000 /* Do not compare commands in .meta file */
+#define OP_SUBMAKE 0x00400000 /* Possibly a submake node */
+/* Attributes applied by PMake */
+#define OP_TRANSFORM 0x80000000 /* The node is a transformation rule */
+#define OP_MEMBER 0x40000000 /* Target is a member of an archive */
+#define OP_LIB 0x20000000 /* Target is a library */
+#define OP_ARCHV 0x10000000 /* Target is an archive construct */
+#define OP_HAS_COMMANDS 0x08000000 /* Target has all the commands it should.
+ * Used when parsing to catch multiple
+ * commands for a target */
+#define OP_SAVE_CMDS 0x04000000 /* Saving commands on .END (Compat) */
+#define OP_DEPS_FOUND 0x02000000 /* Already processed by Suff_FindDeps */
+#define OP_MARK 0x01000000 /* Node found while expanding .ALLSRC */
+
+#define NoExecute(gn) ((gn->type & OP_MAKE) ? noRecursiveExecute : noExecute)
+/*
+ * OP_NOP will return TRUE if the node with the given type was not the
+ * object of a dependency operator
+ */
+#define OP_NOP(t) (((t) & OP_OPMASK) == 0x00000000)
+
+#define OP_NOTARGET (OP_NOTMAIN|OP_USE|OP_EXEC|OP_TRANSFORM)
+
+/*
+ * The TARG_ constants are used when calling the Targ_FindNode and
+ * Targ_FindList functions in targ.c. They simply tell the functions what to
+ * do if the desired node(s) is (are) not found. If the TARG_CREATE constant
+ * is given, a new, empty node will be created for the target, placed in the
+ * table of all targets and its address returned. If TARG_NOCREATE is given,
+ * a NULL pointer will be returned.
+ */
+#define TARG_NOCREATE 0x00 /* don't create it */
+#define TARG_CREATE 0x01 /* create node if not found */
+#define TARG_NOHASH 0x02 /* don't look in/add to hash table */
+
+/*
+ * These constants are all used by the Str_Concat function to decide how the
+ * final string should look. If STR_ADDSPACE is given, a space will be
+ * placed between the two strings. If STR_ADDSLASH is given, a '/' will
+ * be used instead of a space. If neither is given, no intervening characters
+ * will be placed between the two strings in the final output. If the
+ * STR_DOFREE bit is set, the two input strings will be freed before
+ * Str_Concat returns.
+ */
+#define STR_ADDSPACE 0x01 /* add a space when Str_Concat'ing */
+#define STR_ADDSLASH 0x02 /* add a slash when Str_Concat'ing */
+
+/*
+ * Error levels for parsing. PARSE_FATAL means the process cannot continue
+ * once the makefile has been parsed. PARSE_WARNING means it can. Passed
+ * as the first argument to Parse_Error.
+ */
+#define PARSE_INFO 3
+#define PARSE_WARNING 2
+#define PARSE_FATAL 1
+
+/*
+ * Values returned by Cond_Eval.
+ */
+#define COND_PARSE 0 /* Parse the next lines */
+#define COND_SKIP 1 /* Skip the next lines */
+#define COND_INVALID 2 /* Not a conditional statement */
+
+/*
+ * Definitions for the "local" variables. Used only for clarity.
+ */
+#define TARGET "@" /* Target of dependency */
+#define OODATE "?" /* All out-of-date sources */
+#define ALLSRC ">" /* All sources */
+#define IMPSRC "<" /* Source implied by transformation */
+#define PREFIX "*" /* Common prefix */
+#define ARCHIVE "!" /* Archive in "archive(member)" syntax */
+#define MEMBER "%" /* Member in "archive(member)" syntax */
+
+#define FTARGET "@F" /* file part of TARGET */
+#define DTARGET "@D" /* directory part of TARGET */
+#define FIMPSRC "<F" /* file part of IMPSRC */
+#define DIMPSRC "<D" /* directory part of IMPSRC */
+#define FPREFIX "*F" /* file part of PREFIX */
+#define DPREFIX "*D" /* directory part of PREFIX */
+
+/*
+ * Global Variables
+ */
+extern Lst create; /* The list of target names specified on the
+ * command line. used to resolve #if
+ * make(...) statements */
+extern Lst dirSearchPath; /* The list of directories to search when
+ * looking for targets */
+
+extern Boolean compatMake; /* True if we are make compatible */
+extern Boolean ignoreErrors; /* True if should ignore all errors */
+extern Boolean beSilent; /* True if should print no commands */
+extern Boolean noExecute; /* True if should execute nothing */
+extern Boolean noRecursiveExecute; /* True if should execute nothing */
+extern Boolean allPrecious; /* True if every target is precious */
+extern Boolean deleteOnError; /* True if failed targets should be deleted */
+extern Boolean keepgoing; /* True if should continue on unaffected
+ * portions of the graph when have an error
+ * in one portion */
+extern Boolean touchFlag; /* TRUE if targets should just be 'touched'
+ * if out of date. Set by the -t flag */
+extern Boolean queryFlag; /* TRUE if we aren't supposed to really make
+ * anything, just see if the targets are out-
+ * of-date */
+extern Boolean doing_depend; /* TRUE if processing .depend */
+
+extern Boolean checkEnvFirst; /* TRUE if environment should be searched for
+ * variables before the global context */
+extern Boolean jobServer; /* a jobServer already exists */
+
+extern Boolean parseWarnFatal; /* TRUE if makefile parsing warnings are
+ * treated as errors */
+
+extern Boolean varNoExportEnv; /* TRUE if we should not export variables
+ * set on the command line to the env. */
+
+extern GNode *DEFAULT; /* .DEFAULT rule */
+
+extern GNode *VAR_INTERNAL; /* Variables defined internally by make
+ * which should not override those set by
+ * makefiles.
+ */
+extern GNode *VAR_GLOBAL; /* Variables defined in a global context, e.g
+ * in the Makefile itself */
+extern GNode *VAR_CMD; /* Variables defined on the command line */
+extern GNode *VAR_FOR; /* Iteration variables */
+extern char var_Error[]; /* Value returned by Var_Parse when an error
+ * is encountered. It actually points to
+ * an empty string, so naive callers needn't
+ * worry about it. */
+
+extern time_t now; /* The time at the start of this whole
+ * process */
+
+extern Boolean oldVars; /* Do old-style variable substitution */
+
+extern Lst sysIncPath; /* The system include path. */
+extern Lst defIncPath; /* The default include path. */
+
+extern char curdir[]; /* Startup directory */
+extern char *progname; /* The program name */
+extern char *makeDependfile; /* .depend */
+extern char **savedEnv; /* if we replaced environ this will be non-NULL */
+
+/*
+ * We cannot vfork() in a child of vfork().
+ * Most systems do not enforce this but some do.
+ */
+#define vFork() ((getpid() == myPid) ? vfork() : fork())
+extern pid_t myPid;
+
+#define MAKEFLAGS ".MAKEFLAGS"
+#define MAKEOVERRIDES ".MAKEOVERRIDES"
+#define MAKE_JOB_PREFIX ".MAKE.JOB.PREFIX" /* prefix for job target output */
+#define MAKE_EXPORTED ".MAKE.EXPORTED" /* variables we export */
+#define MAKE_MAKEFILES ".MAKE.MAKEFILES" /* all the makefiles we read */
+#define MAKE_LEVEL ".MAKE.LEVEL" /* recursion level */
+#define MAKEFILE_PREFERENCE ".MAKE.MAKEFILE_PREFERENCE"
+#define MAKE_DEPENDFILE ".MAKE.DEPENDFILE" /* .depend */
+#define MAKE_MODE ".MAKE.MODE"
+#ifndef MAKE_LEVEL_ENV
+# define MAKE_LEVEL_ENV "MAKELEVEL"
+#endif
+
+/*
+ * debug control:
+ * There is one bit per module. It is up to the module what debug
+ * information to print.
+ */
+FILE *debug_file; /* Output written here - default stdout */
+extern int debug;
+#define DEBUG_ARCH 0x00001
+#define DEBUG_COND 0x00002
+#define DEBUG_DIR 0x00004
+#define DEBUG_GRAPH1 0x00008
+#define DEBUG_GRAPH2 0x00010
+#define DEBUG_JOB 0x00020
+#define DEBUG_MAKE 0x00040
+#define DEBUG_SUFF 0x00080
+#define DEBUG_TARG 0x00100
+#define DEBUG_VAR 0x00200
+#define DEBUG_FOR 0x00400
+#define DEBUG_SHELL 0x00800
+#define DEBUG_ERROR 0x01000
+#define DEBUG_LOUD 0x02000
+#define DEBUG_META 0x04000
+
+#define DEBUG_GRAPH3 0x10000
+#define DEBUG_SCRIPT 0x20000
+#define DEBUG_PARSE 0x40000
+#define DEBUG_CWD 0x80000
+
+#define CONCAT(a,b) a##b
+
+#define DEBUG(module) (debug & CONCAT(DEBUG_,module))
+
+#include "nonints.h"
+
+int Make_TimeStamp(GNode *, GNode *);
+Boolean Make_OODate(GNode *);
+void Make_ExpandUse(Lst);
+time_t Make_Recheck(GNode *);
+void Make_HandleUse(GNode *, GNode *);
+void Make_Update(GNode *);
+void Make_DoAllVar(GNode *);
+Boolean Make_Run(Lst);
+char * Check_Cwd_Cmd(const char *);
+void Check_Cwd(const char **);
+void PrintOnError(GNode *, const char *);
+void Main_ExportMAKEFLAGS(Boolean);
+Boolean Main_SetObjdir(const char *, ...) MAKE_ATTR_PRINTFLIKE(1, 2);
+int mkTempFile(const char *, char **);
+int str2Lst_Append(Lst, char *, const char *);
+int cached_lstat(const char *, void *);
+int cached_stat(const char *, void *);
+
+#define VARF_UNDEFERR 1
+#define VARF_WANTRES 2
+#define VARF_ASSIGN 4
+
+#ifdef __GNUC__
+#define UNCONST(ptr) ({ \
+ union __unconst { \
+ const void *__cp; \
+ void *__p; \
+ } __d; \
+ __d.__cp = ptr, __d.__p; })
+#else
+#define UNCONST(ptr) (void *)(ptr)
+#endif
+
+#ifndef MIN
+#define MIN(a, b) ((a < b) ? a : b)
+#endif
+#ifndef MAX
+#define MAX(a, b) ((a > b) ? a : b)
+#endif
+
+/* At least GNU/Hurd systems lack hardcoded MAXPATHLEN/PATH_MAX */
+#include <limits.h>
+#ifndef MAXPATHLEN
+#define MAXPATHLEN 4096
+#endif
+#ifndef PATH_MAX
+#define PATH_MAX MAXPATHLEN
+#endif
+
+#if defined(SYSV)
+#define KILLPG(pid, sig) kill(-(pid), (sig))
+#else
+#define KILLPG(pid, sig) killpg((pid), (sig))
+#endif
+
+#endif /* _MAKE_H_ */
diff --git a/usr.bin/make/make_malloc.c b/usr.bin/make/make_malloc.c
new file mode 100644
index 0000000..035d519
--- /dev/null
+++ b/usr.bin/make/make_malloc.c
@@ -0,0 +1,119 @@
+/* $NetBSD: make_malloc.c,v 1.11 2017/04/16 20:20:24 dholland Exp $ */
+
+/*-
+ * Copyright (c) 2009 The NetBSD Foundation, Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
+ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifdef MAKE_NATIVE
+#include <sys/cdefs.h>
+__RCSID("$NetBSD: make_malloc.c,v 1.11 2017/04/16 20:20:24 dholland Exp $");
+#endif
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+
+#include "make.h"
+
+#ifndef USE_EMALLOC
+static MAKE_ATTR_DEAD void enomem(void);
+
+/*
+ * enomem --
+ * die when out of memory.
+ */
+static MAKE_ATTR_DEAD void
+enomem(void)
+{
+ (void)fprintf(stderr, "%s: %s.\n", progname, strerror(ENOMEM));
+ exit(2);
+}
+
+/*
+ * bmake_malloc --
+ * malloc, but die on error.
+ */
+void *
+bmake_malloc(size_t len)
+{
+ void *p;
+
+ if ((p = malloc(len)) == NULL)
+ enomem();
+ return(p);
+}
+
+/*
+ * bmake_strdup --
+ * strdup, but die on error.
+ */
+char *
+bmake_strdup(const char *str)
+{
+ size_t len;
+ char *p;
+
+ len = strlen(str) + 1;
+ if ((p = malloc(len)) == NULL)
+ enomem();
+ return memcpy(p, str, len);
+}
+
+/*
+ * bmake_strndup --
+ * strndup, but die on error.
+ */
+char *
+bmake_strndup(const char *str, size_t max_len)
+{
+ size_t len;
+ char *p;
+
+ if (str == NULL)
+ return NULL;
+
+ len = strlen(str);
+ if (len > max_len)
+ len = max_len;
+ p = bmake_malloc(len + 1);
+ memcpy(p, str, len);
+ p[len] = '\0';
+
+ return(p);
+}
+
+/*
+ * bmake_realloc --
+ * realloc, but die on error.
+ */
+void *
+bmake_realloc(void *ptr, size_t size)
+{
+ if ((ptr = realloc(ptr, size)) == NULL)
+ enomem();
+ return(ptr);
+}
+#endif
diff --git a/usr.bin/make/make_malloc.h b/usr.bin/make/make_malloc.h
new file mode 100644
index 0000000..36d3eff
--- /dev/null
+++ b/usr.bin/make/make_malloc.h
@@ -0,0 +1,41 @@
+/* $NetBSD: make_malloc.h,v 1.4 2009/01/24 14:43:29 dsl Exp $ */
+
+/*-
+ * Copyright (c) 2009 The NetBSD Foundation, Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
+ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef USE_EMALLOC
+void *bmake_malloc(size_t);
+void *bmake_realloc(void *, size_t);
+char *bmake_strdup(const char *);
+char *bmake_strndup(const char *, size_t);
+#else
+#include <util.h>
+#define bmake_malloc(x) emalloc(x)
+#define bmake_realloc(x,y) erealloc(x,y)
+#define bmake_strdup(x) estrdup(x)
+#define bmake_strndup(x,y) estrndup(x,y)
+#endif
+
diff --git a/usr.bin/make/meta.c b/usr.bin/make/meta.c
new file mode 100644
index 0000000..4a3f41d
--- /dev/null
+++ b/usr.bin/make/meta.c
@@ -0,0 +1,1641 @@
+/* $NetBSD: meta.c,v 1.70 2018/02/13 19:37:30 sjg Exp $ */
+
+/*
+ * Implement 'meta' mode.
+ * Adapted from John Birrell's patches to FreeBSD make.
+ * --sjg
+ */
+/*
+ * Copyright (c) 2009-2016, Juniper Networks, Inc.
+ * Portions Copyright (c) 2009, John Birrell.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+#if defined(USE_META)
+
+#ifdef HAVE_CONFIG_H
+# include "config.h"
+#endif
+#include <sys/stat.h>
+#include <sys/ioctl.h>
+#include <libgen.h>
+#include <errno.h>
+#if !defined(HAVE_CONFIG_H) || defined(HAVE_ERR_H)
+#include <err.h>
+#endif
+
+#include "make.h"
+#include "job.h"
+
+#ifdef HAVE_FILEMON_H
+# include <filemon.h>
+#endif
+#if !defined(USE_FILEMON) && defined(FILEMON_SET_FD)
+# define USE_FILEMON
+#endif
+
+static BuildMon Mybm; /* for compat */
+static Lst metaBailiwick; /* our scope of control */
+static char *metaBailiwickStr; /* string storage for the list */
+static Lst metaIgnorePaths; /* paths we deliberately ignore */
+static char *metaIgnorePathsStr; /* string storage for the list */
+
+#ifndef MAKE_META_IGNORE_PATHS
+#define MAKE_META_IGNORE_PATHS ".MAKE.META.IGNORE_PATHS"
+#endif
+#ifndef MAKE_META_IGNORE_PATTERNS
+#define MAKE_META_IGNORE_PATTERNS ".MAKE.META.IGNORE_PATTERNS"
+#endif
+#ifndef MAKE_META_IGNORE_FILTER
+#define MAKE_META_IGNORE_FILTER ".MAKE.META.IGNORE_FILTER"
+#endif
+
+Boolean useMeta = FALSE;
+static Boolean useFilemon = FALSE;
+static Boolean writeMeta = FALSE;
+static Boolean metaMissing = FALSE; /* oodate if missing */
+static Boolean filemonMissing = FALSE; /* oodate if missing */
+static Boolean metaEnv = FALSE; /* don't save env unless asked */
+static Boolean metaVerbose = FALSE;
+static Boolean metaIgnoreCMDs = FALSE; /* ignore CMDs in .meta files */
+static Boolean metaIgnorePatterns = FALSE; /* do we need to do pattern matches */
+static Boolean metaIgnoreFilter = FALSE; /* do we have more complex filtering? */
+static Boolean metaCurdirOk = FALSE; /* write .meta in .CURDIR Ok? */
+static Boolean metaSilent = FALSE; /* if we have a .meta be SILENT */
+
+extern Boolean forceJobs;
+extern Boolean comatMake;
+extern char **environ;
+
+#define MAKE_META_PREFIX ".MAKE.META.PREFIX"
+
+#ifndef N2U
+# define N2U(n, u) (((n) + ((u) - 1)) / (u))
+#endif
+#ifndef ROUNDUP
+# define ROUNDUP(n, u) (N2U((n), (u)) * (u))
+#endif
+
+#if !defined(HAVE_STRSEP)
+# define strsep(s, d) stresep((s), (d), 0)
+#endif
+
+/*
+ * Filemon is a kernel module which snoops certain syscalls.
+ *
+ * C chdir
+ * E exec
+ * F [v]fork
+ * L [sym]link
+ * M rename
+ * R read
+ * W write
+ * S stat
+ *
+ * See meta_oodate below - we mainly care about 'E' and 'R'.
+ *
+ * We can still use meta mode without filemon, but
+ * the benefits are more limited.
+ */
+#ifdef USE_FILEMON
+# ifndef _PATH_FILEMON
+# define _PATH_FILEMON "/dev/filemon"
+# endif
+
+/*
+ * Open the filemon device.
+ */
+static void
+filemon_open(BuildMon *pbm)
+{
+ int retry;
+
+ pbm->mon_fd = pbm->filemon_fd = -1;
+ if (!useFilemon)
+ return;
+
+ for (retry = 5; retry >= 0; retry--) {
+ if ((pbm->filemon_fd = open(_PATH_FILEMON, O_RDWR)) >= 0)
+ break;
+ }
+
+ if (pbm->filemon_fd < 0) {
+ useFilemon = FALSE;
+ warn("Could not open %s", _PATH_FILEMON);
+ return;
+ }
+
+ /*
+ * We use a file outside of '.'
+ * to avoid a FreeBSD kernel bug where unlink invalidates
+ * cwd causing getcwd to do a lot more work.
+ * We only care about the descriptor.
+ */
+ pbm->mon_fd = mkTempFile("filemon.XXXXXX", NULL);
+ if (ioctl(pbm->filemon_fd, FILEMON_SET_FD, &pbm->mon_fd) < 0) {
+ err(1, "Could not set filemon file descriptor!");
+ }
+ /* we don't need these once we exec */
+ (void)fcntl(pbm->mon_fd, F_SETFD, FD_CLOEXEC);
+ (void)fcntl(pbm->filemon_fd, F_SETFD, FD_CLOEXEC);
+}
+
+/*
+ * Read the build monitor output file and write records to the target's
+ * metadata file.
+ */
+static int
+filemon_read(FILE *mfp, int fd)
+{
+ char buf[BUFSIZ];
+ int n;
+ int error;
+
+ /* Check if we're not writing to a meta data file.*/
+ if (mfp == NULL) {
+ if (fd >= 0)
+ close(fd); /* not interested */
+ return 0;
+ }
+ /* rewind */
+ (void)lseek(fd, (off_t)0, SEEK_SET);
+
+ error = 0;
+ fprintf(mfp, "\n-- filemon acquired metadata --\n");
+
+ while ((n = read(fd, buf, sizeof(buf))) > 0) {
+ if ((int)fwrite(buf, 1, n, mfp) < n)
+ error = EIO;
+ }
+ fflush(mfp);
+ if (close(fd) < 0)
+ error = errno;
+ return error;
+}
+#endif
+
+/*
+ * when realpath() fails,
+ * we use this, to clean up ./ and ../
+ */
+static void
+eat_dots(char *buf, size_t bufsz, int dots)
+{
+ char *cp;
+ char *cp2;
+ const char *eat;
+ size_t eatlen;
+
+ switch (dots) {
+ case 1:
+ eat = "/./";
+ eatlen = 2;
+ break;
+ case 2:
+ eat = "/../";
+ eatlen = 3;
+ break;
+ default:
+ return;
+ }
+
+ do {
+ cp = strstr(buf, eat);
+ if (cp) {
+ cp2 = cp + eatlen;
+ if (dots == 2 && cp > buf) {
+ do {
+ cp--;
+ } while (cp > buf && *cp != '/');
+ }
+ if (*cp == '/') {
+ strlcpy(cp, cp2, bufsz - (cp - buf));
+ } else {
+ return; /* can't happen? */
+ }
+ }
+ } while (cp);
+}
+
+static char *
+meta_name(struct GNode *gn, char *mname, size_t mnamelen,
+ const char *dname,
+ const char *tname,
+ const char *cwd)
+{
+ char buf[MAXPATHLEN];
+ char *rp;
+ char *cp;
+ char *tp;
+ char *dtp;
+ size_t ldname;
+
+ /*
+ * Weed out relative paths from the target file name.
+ * We have to be careful though since if target is a
+ * symlink, the result will be unstable.
+ * So we use realpath() just to get the dirname, and leave the
+ * basename as given to us.
+ */
+ if ((cp = strrchr(tname, '/'))) {
+ if (cached_realpath(tname, buf)) {
+ if ((rp = strrchr(buf, '/'))) {
+ rp++;
+ cp++;
+ if (strcmp(cp, rp) != 0)
+ strlcpy(rp, cp, sizeof(buf) - (rp - buf));
+ }
+ tname = buf;
+ } else {
+ /*
+ * We likely have a directory which is about to be made.
+ * We pretend realpath() succeeded, to have a chance
+ * of generating the same meta file name that we will
+ * next time through.
+ */
+ if (tname[0] == '/') {
+ strlcpy(buf, tname, sizeof(buf));
+ } else {
+ snprintf(buf, sizeof(buf), "%s/%s", cwd, tname);
+ }
+ eat_dots(buf, sizeof(buf), 1); /* ./ */
+ eat_dots(buf, sizeof(buf), 2); /* ../ */
+ tname = buf;
+ }
+ }
+ /* on some systems dirname may modify its arg */
+ tp = bmake_strdup(tname);
+ dtp = dirname(tp);
+ if (strcmp(dname, dtp) == 0)
+ snprintf(mname, mnamelen, "%s.meta", tname);
+ else {
+ ldname = strlen(dname);
+ if (strncmp(dname, dtp, ldname) == 0 && dtp[ldname] == '/')
+ snprintf(mname, mnamelen, "%s/%s.meta", dname, &tname[ldname+1]);
+ else
+ snprintf(mname, mnamelen, "%s/%s.meta", dname, tname);
+
+ /*
+ * Replace path separators in the file name after the
+ * current object directory path.
+ */
+ cp = mname + strlen(dname) + 1;
+
+ while (*cp != '\0') {
+ if (*cp == '/')
+ *cp = '_';
+ cp++;
+ }
+ }
+ free(tp);
+ return (mname);
+}
+
+/*
+ * Return true if running ${.MAKE}
+ * Bypassed if target is flagged .MAKE
+ */
+static int
+is_submake(void *cmdp, void *gnp)
+{
+ static char *p_make = NULL;
+ static int p_len;
+ char *cmd = cmdp;
+ GNode *gn = gnp;
+ char *mp = NULL;
+ char *cp;
+ char *cp2;
+ int rc = 0; /* keep looking */
+
+ if (!p_make) {
+ p_make = Var_Value(".MAKE", gn, &cp);
+ p_len = strlen(p_make);
+ }
+ cp = strchr(cmd, '$');
+ if ((cp)) {
+ mp = Var_Subst(NULL, cmd, gn, VARF_WANTRES);
+ cmd = mp;
+ }
+ cp2 = strstr(cmd, p_make);
+ if ((cp2)) {
+ switch (cp2[p_len]) {
+ case '\0':
+ case ' ':
+ case '\t':
+ case '\n':
+ rc = 1;
+ break;
+ }
+ if (cp2 > cmd && rc > 0) {
+ switch (cp2[-1]) {
+ case ' ':
+ case '\t':
+ case '\n':
+ break;
+ default:
+ rc = 0; /* no match */
+ break;
+ }
+ }
+ }
+ free(mp);
+ return (rc);
+}
+
+typedef struct meta_file_s {
+ FILE *fp;
+ GNode *gn;
+} meta_file_t;
+
+static int
+printCMD(void *cmdp, void *mfpp)
+{
+ meta_file_t *mfp = mfpp;
+ char *cmd = cmdp;
+ char *cp = NULL;
+
+ if (strchr(cmd, '$')) {
+ cmd = cp = Var_Subst(NULL, cmd, mfp->gn, VARF_WANTRES);
+ }
+ fprintf(mfp->fp, "CMD %s\n", cmd);
+ free(cp);
+ return 0;
+}
+
+/*
+ * Certain node types never get a .meta file
+ */
+#define SKIP_META_TYPE(_type) do { \
+ if ((gn->type & __CONCAT(OP_, _type))) { \
+ if (verbose) { \
+ fprintf(debug_file, "Skipping meta for %s: .%s\n", \
+ gn->name, __STRING(_type)); \
+ } \
+ return FALSE; \
+ } \
+} while (0)
+
+
+/*
+ * Do we need/want a .meta file ?
+ */
+static Boolean
+meta_needed(GNode *gn, const char *dname, const char *tname,
+ char *objdir, int verbose)
+{
+ struct stat fs;
+
+ if (verbose)
+ verbose = DEBUG(META);
+
+ /* This may be a phony node which we don't want meta data for... */
+ /* Skip .meta for .BEGIN, .END, .ERROR etc as well. */
+ /* Or it may be explicitly flagged as .NOMETA */
+ SKIP_META_TYPE(NOMETA);
+ /* Unless it is explicitly flagged as .META */
+ if (!(gn->type & OP_META)) {
+ SKIP_META_TYPE(PHONY);
+ SKIP_META_TYPE(SPECIAL);
+ SKIP_META_TYPE(MAKE);
+ }
+
+ /* Check if there are no commands to execute. */
+ if (Lst_IsEmpty(gn->commands)) {
+ if (verbose)
+ fprintf(debug_file, "Skipping meta for %s: no commands\n",
+ gn->name);
+ return FALSE;
+ }
+ if ((gn->type & (OP_META|OP_SUBMAKE)) == OP_SUBMAKE) {
+ /* OP_SUBMAKE is a bit too aggressive */
+ if (Lst_ForEach(gn->commands, is_submake, gn)) {
+ if (DEBUG(META))
+ fprintf(debug_file, "Skipping meta for %s: .SUBMAKE\n",
+ gn->name);
+ return FALSE;
+ }
+ }
+
+ /* The object directory may not exist. Check it.. */
+ if (cached_stat(dname, &fs) != 0) {
+ if (verbose)
+ fprintf(debug_file, "Skipping meta for %s: no .OBJDIR\n",
+ gn->name);
+ return FALSE;
+ }
+
+ /* make sure these are canonical */
+ if (cached_realpath(dname, objdir))
+ dname = objdir;
+
+ /* If we aren't in the object directory, don't create a meta file. */
+ if (!metaCurdirOk && strcmp(curdir, dname) == 0) {
+ if (verbose)
+ fprintf(debug_file, "Skipping meta for %s: .OBJDIR == .CURDIR\n",
+ gn->name);
+ return FALSE;
+ }
+ return TRUE;
+}
+
+
+static FILE *
+meta_create(BuildMon *pbm, GNode *gn)
+{
+ meta_file_t mf;
+ char buf[MAXPATHLEN];
+ char objdir[MAXPATHLEN];
+ char **ptr;
+ const char *dname;
+ const char *tname;
+ char *fname;
+ const char *cp;
+ char *p[4]; /* >= possible uses */
+ int i;
+
+ mf.fp = NULL;
+ i = 0;
+
+ dname = Var_Value(".OBJDIR", gn, &p[i++]);
+ tname = Var_Value(TARGET, gn, &p[i++]);
+
+ /* if this succeeds objdir is realpath of dname */
+ if (!meta_needed(gn, dname, tname, objdir, TRUE))
+ goto out;
+ dname = objdir;
+
+ if (metaVerbose) {
+ char *mp;
+
+ /* Describe the target we are building */
+ mp = Var_Subst(NULL, "${" MAKE_META_PREFIX "}", gn, VARF_WANTRES);
+ if (*mp)
+ fprintf(stdout, "%s\n", mp);
+ free(mp);
+ }
+ /* Get the basename of the target */
+ if ((cp = strrchr(tname, '/')) == NULL) {
+ cp = tname;
+ } else {
+ cp++;
+ }
+
+ fflush(stdout);
+
+ if (!writeMeta)
+ /* Don't create meta data. */
+ goto out;
+
+ fname = meta_name(gn, pbm->meta_fname, sizeof(pbm->meta_fname),
+ dname, tname, objdir);
+
+#ifdef DEBUG_META_MODE
+ if (DEBUG(META))
+ fprintf(debug_file, "meta_create: %s\n", fname);
+#endif
+
+ if ((mf.fp = fopen(fname, "w")) == NULL)
+ err(1, "Could not open meta file '%s'", fname);
+
+ fprintf(mf.fp, "# Meta data file %s\n", fname);
+
+ mf.gn = gn;
+
+ Lst_ForEach(gn->commands, printCMD, &mf);
+
+ fprintf(mf.fp, "CWD %s\n", getcwd(buf, sizeof(buf)));
+ fprintf(mf.fp, "TARGET %s\n", tname);
+
+ if (metaEnv) {
+ for (ptr = environ; *ptr != NULL; ptr++)
+ fprintf(mf.fp, "ENV %s\n", *ptr);
+ }
+
+ fprintf(mf.fp, "-- command output --\n");
+ fflush(mf.fp);
+
+ Var_Append(".MAKE.META.FILES", fname, VAR_GLOBAL);
+ Var_Append(".MAKE.META.CREATED", fname, VAR_GLOBAL);
+
+ gn->type |= OP_META; /* in case anyone wants to know */
+ if (metaSilent) {
+ gn->type |= OP_SILENT;
+ }
+ out:
+ for (i--; i >= 0; i--) {
+ free(p[i]);
+ }
+
+ return (mf.fp);
+}
+
+static Boolean
+boolValue(char *s)
+{
+ switch(*s) {
+ case '0':
+ case 'N':
+ case 'n':
+ case 'F':
+ case 'f':
+ return FALSE;
+ }
+ return TRUE;
+}
+
+/*
+ * Initialization we need before reading makefiles.
+ */
+void
+meta_init(void)
+{
+#ifdef USE_FILEMON
+ /* this allows makefiles to test if we have filemon support */
+ Var_Set(".MAKE.PATH_FILEMON", _PATH_FILEMON, VAR_GLOBAL, 0);
+#endif
+}
+
+
+#define get_mode_bf(bf, token) \
+ if ((cp = strstr(make_mode, token))) \
+ bf = boolValue(&cp[sizeof(token) - 1])
+
+/*
+ * Initialization we need after reading makefiles.
+ */
+void
+meta_mode_init(const char *make_mode)
+{
+ static int once = 0;
+ char *cp;
+
+ useMeta = TRUE;
+ useFilemon = TRUE;
+ writeMeta = TRUE;
+
+ if (make_mode) {
+ if (strstr(make_mode, "env"))
+ metaEnv = TRUE;
+ if (strstr(make_mode, "verb"))
+ metaVerbose = TRUE;
+ if (strstr(make_mode, "read"))
+ writeMeta = FALSE;
+ if (strstr(make_mode, "nofilemon"))
+ useFilemon = FALSE;
+ if (strstr(make_mode, "ignore-cmd"))
+ metaIgnoreCMDs = TRUE;
+ if (useFilemon)
+ get_mode_bf(filemonMissing, "missing-filemon=");
+ get_mode_bf(metaCurdirOk, "curdirok=");
+ get_mode_bf(metaMissing, "missing-meta=");
+ get_mode_bf(metaSilent, "silent=");
+ }
+ if (metaVerbose && !Var_Exists(MAKE_META_PREFIX, VAR_GLOBAL)) {
+ /*
+ * The default value for MAKE_META_PREFIX
+ * prints the absolute path of the target.
+ * This works be cause :H will generate '.' if there is no /
+ * and :tA will resolve that to cwd.
+ */
+ Var_Set(MAKE_META_PREFIX, "Building ${.TARGET:H:tA}/${.TARGET:T}", VAR_GLOBAL, 0);
+ }
+ if (once)
+ return;
+ once = 1;
+ memset(&Mybm, 0, sizeof(Mybm));
+ /*
+ * We consider ourselves master of all within ${.MAKE.META.BAILIWICK}
+ */
+ metaBailiwick = Lst_Init(FALSE);
+ metaBailiwickStr = Var_Subst(NULL, "${.MAKE.META.BAILIWICK:O:u:tA}",
+ VAR_GLOBAL, VARF_WANTRES);
+ if (metaBailiwickStr) {
+ str2Lst_Append(metaBailiwick, metaBailiwickStr, NULL);
+ }
+ /*
+ * We ignore any paths that start with ${.MAKE.META.IGNORE_PATHS}
+ */
+ metaIgnorePaths = Lst_Init(FALSE);
+ Var_Append(MAKE_META_IGNORE_PATHS,
+ "/dev /etc /proc /tmp /var/run /var/tmp ${TMPDIR}", VAR_GLOBAL);
+ metaIgnorePathsStr = Var_Subst(NULL,
+ "${" MAKE_META_IGNORE_PATHS ":O:u:tA}", VAR_GLOBAL,
+ VARF_WANTRES);
+ if (metaIgnorePathsStr) {
+ str2Lst_Append(metaIgnorePaths, metaIgnorePathsStr, NULL);
+ }
+
+ /*
+ * We ignore any paths that match ${.MAKE.META.IGNORE_PATTERNS}
+ */
+ cp = NULL;
+ if (Var_Value(MAKE_META_IGNORE_PATTERNS, VAR_GLOBAL, &cp)) {
+ metaIgnorePatterns = TRUE;
+ free(cp);
+ }
+ cp = NULL;
+ if (Var_Value(MAKE_META_IGNORE_FILTER, VAR_GLOBAL, &cp)) {
+ metaIgnoreFilter = TRUE;
+ free(cp);
+ }
+}
+
+/*
+ * In each case below we allow for job==NULL
+ */
+void
+meta_job_start(Job *job, GNode *gn)
+{
+ BuildMon *pbm;
+
+ if (job != NULL) {
+ pbm = &job->bm;
+ } else {
+ pbm = &Mybm;
+ }
+ pbm->mfp = meta_create(pbm, gn);
+#ifdef USE_FILEMON_ONCE
+ /* compat mode we open the filemon dev once per command */
+ if (job == NULL)
+ return;
+#endif
+#ifdef USE_FILEMON
+ if (pbm->mfp != NULL && useFilemon) {
+ filemon_open(pbm);
+ } else {
+ pbm->mon_fd = pbm->filemon_fd = -1;
+ }
+#endif
+}
+
+/*
+ * The child calls this before doing anything.
+ * It does not disturb our state.
+ */
+void
+meta_job_child(Job *job)
+{
+#ifdef USE_FILEMON
+ BuildMon *pbm;
+
+ if (job != NULL) {
+ pbm = &job->bm;
+ } else {
+ pbm = &Mybm;
+ }
+ if (pbm->mfp != NULL) {
+ close(fileno(pbm->mfp));
+ if (useFilemon) {
+ pid_t pid;
+
+ pid = getpid();
+ if (ioctl(pbm->filemon_fd, FILEMON_SET_PID, &pid) < 0) {
+ err(1, "Could not set filemon pid!");
+ }
+ }
+ }
+#endif
+}
+
+void
+meta_job_error(Job *job, GNode *gn, int flags, int status)
+{
+ char cwd[MAXPATHLEN];
+ BuildMon *pbm;
+
+ if (job != NULL) {
+ pbm = &job->bm;
+ if (!gn)
+ gn = job->node;
+ } else {
+ pbm = &Mybm;
+ }
+ if (pbm->mfp != NULL) {
+ fprintf(pbm->mfp, "\n*** Error code %d%s\n",
+ status,
+ (flags & JOB_IGNERR) ?
+ "(ignored)" : "");
+ }
+ if (gn) {
+ Var_Set(".ERROR_TARGET", gn->path ? gn->path : gn->name, VAR_GLOBAL, 0);
+ }
+ getcwd(cwd, sizeof(cwd));
+ Var_Set(".ERROR_CWD", cwd, VAR_GLOBAL, 0);
+ if (pbm->meta_fname[0]) {
+ Var_Set(".ERROR_META_FILE", pbm->meta_fname, VAR_GLOBAL, 0);
+ }
+ meta_job_finish(job);
+}
+
+void
+meta_job_output(Job *job, char *cp, const char *nl)
+{
+ BuildMon *pbm;
+
+ if (job != NULL) {
+ pbm = &job->bm;
+ } else {
+ pbm = &Mybm;
+ }
+ if (pbm->mfp != NULL) {
+ if (metaVerbose) {
+ static char *meta_prefix = NULL;
+ static int meta_prefix_len;
+
+ if (!meta_prefix) {
+ char *cp2;
+
+ meta_prefix = Var_Subst(NULL, "${" MAKE_META_PREFIX "}",
+ VAR_GLOBAL, VARF_WANTRES);
+ if ((cp2 = strchr(meta_prefix, '$')))
+ meta_prefix_len = cp2 - meta_prefix;
+ else
+ meta_prefix_len = strlen(meta_prefix);
+ }
+ if (strncmp(cp, meta_prefix, meta_prefix_len) == 0) {
+ cp = strchr(cp+1, '\n');
+ if (!cp++)
+ return;
+ }
+ }
+ fprintf(pbm->mfp, "%s%s", cp, nl);
+ }
+}
+
+int
+meta_cmd_finish(void *pbmp)
+{
+ int error = 0;
+ BuildMon *pbm = pbmp;
+#ifdef USE_FILEMON
+ int x;
+#endif
+
+ if (!pbm)
+ pbm = &Mybm;
+
+#ifdef USE_FILEMON
+ if (pbm->filemon_fd >= 0) {
+ if (close(pbm->filemon_fd) < 0)
+ error = errno;
+ x = filemon_read(pbm->mfp, pbm->mon_fd);
+ if (error == 0 && x != 0)
+ error = x;
+ pbm->filemon_fd = pbm->mon_fd = -1;
+ } else
+#endif
+ fprintf(pbm->mfp, "\n"); /* ensure end with newline */
+ return error;
+}
+
+int
+meta_job_finish(Job *job)
+{
+ BuildMon *pbm;
+ int error = 0;
+ int x;
+
+ if (job != NULL) {
+ pbm = &job->bm;
+ } else {
+ pbm = &Mybm;
+ }
+ if (pbm->mfp != NULL) {
+ error = meta_cmd_finish(pbm);
+ x = fclose(pbm->mfp);
+ if (error == 0 && x != 0)
+ error = errno;
+ pbm->mfp = NULL;
+ pbm->meta_fname[0] = '\0';
+ }
+ return error;
+}
+
+void
+meta_finish(void)
+{
+ Lst_Destroy(metaBailiwick, NULL);
+ free(metaBailiwickStr);
+ Lst_Destroy(metaIgnorePaths, NULL);
+ free(metaIgnorePathsStr);
+}
+
+/*
+ * Fetch a full line from fp - growing bufp if needed
+ * Return length in bufp.
+ */
+static int
+fgetLine(char **bufp, size_t *szp, int o, FILE *fp)
+{
+ char *buf = *bufp;
+ size_t bufsz = *szp;
+ struct stat fs;
+ int x;
+
+ if (fgets(&buf[o], bufsz - o, fp) != NULL) {
+ check_newline:
+ x = o + strlen(&buf[o]);
+ if (buf[x - 1] == '\n')
+ return x;
+ /*
+ * We need to grow the buffer.
+ * The meta file can give us a clue.
+ */
+ if (fstat(fileno(fp), &fs) == 0) {
+ size_t newsz;
+ char *p;
+
+ newsz = ROUNDUP((fs.st_size / 2), BUFSIZ);
+ if (newsz <= bufsz)
+ newsz = ROUNDUP(fs.st_size, BUFSIZ);
+ if (newsz <= bufsz)
+ return x; /* truncated */
+ if (DEBUG(META))
+ fprintf(debug_file, "growing buffer %zu -> %zu\n",
+ bufsz, newsz);
+ p = bmake_realloc(buf, newsz);
+ if (p) {
+ *bufp = buf = p;
+ *szp = bufsz = newsz;
+ /* fetch the rest */
+ if (!fgets(&buf[x], bufsz - x, fp))
+ return x; /* truncated! */
+ goto check_newline;
+ }
+ }
+ }
+ return 0;
+}
+
+/* Lst_ForEach wants 1 to stop search */
+static int
+prefix_match(void *p, void *q)
+{
+ const char *prefix = p;
+ const char *path = q;
+ size_t n = strlen(prefix);
+
+ return (0 == strncmp(path, prefix, n));
+}
+
+/*
+ * looking for exact or prefix/ match to
+ * Lst_Find wants 0 to stop search
+ */
+static int
+path_match(const void *p, const void *q)
+{
+ const char *prefix = q;
+ const char *path = p;
+ size_t n = strlen(prefix);
+ int rc;
+
+ if ((rc = strncmp(path, prefix, n)) == 0) {
+ switch (path[n]) {
+ case '\0':
+ case '/':
+ break;
+ default:
+ rc = 1;
+ break;
+ }
+ }
+ return rc;
+}
+
+/* Lst_Find wants 0 to stop search */
+static int
+string_match(const void *p, const void *q)
+{
+ const char *p1 = p;
+ const char *p2 = q;
+
+ return strcmp(p1, p2);
+}
+
+
+static int
+meta_ignore(GNode *gn, const char *p)
+{
+ char fname[MAXPATHLEN];
+
+ if (p == NULL)
+ return TRUE;
+
+ if (*p == '/') {
+ cached_realpath(p, fname); /* clean it up */
+ if (Lst_ForEach(metaIgnorePaths, prefix_match, fname)) {
+#ifdef DEBUG_META_MODE
+ if (DEBUG(META))
+ fprintf(debug_file, "meta_oodate: ignoring path: %s\n",
+ p);
+#endif
+ return TRUE;
+ }
+ }
+
+ if (metaIgnorePatterns) {
+ char *pm;
+
+ Var_Set(".p.", p, gn, 0);
+ pm = Var_Subst(NULL,
+ "${" MAKE_META_IGNORE_PATTERNS ":@m@${.p.:M$m}@}",
+ gn, VARF_WANTRES);
+ if (*pm) {
+#ifdef DEBUG_META_MODE
+ if (DEBUG(META))
+ fprintf(debug_file, "meta_oodate: ignoring pattern: %s\n",
+ p);
+#endif
+ free(pm);
+ return TRUE;
+ }
+ free(pm);
+ }
+
+ if (metaIgnoreFilter) {
+ char *fm;
+
+ /* skip if filter result is empty */
+ snprintf(fname, sizeof(fname),
+ "${%s:L:${%s:ts:}}",
+ p, MAKE_META_IGNORE_FILTER);
+ fm = Var_Subst(NULL, fname, gn, VARF_WANTRES);
+ if (*fm == '\0') {
+#ifdef DEBUG_META_MODE
+ if (DEBUG(META))
+ fprintf(debug_file, "meta_oodate: ignoring filtered: %s\n",
+ p);
+#endif
+ free(fm);
+ return TRUE;
+ }
+ free(fm);
+ }
+ return FALSE;
+}
+
+/*
+ * When running with 'meta' functionality, a target can be out-of-date
+ * if any of the references in its meta data file is more recent.
+ * We have to track the latestdir on a per-process basis.
+ */
+#define LCWD_VNAME_FMT ".meta.%d.lcwd"
+#define LDIR_VNAME_FMT ".meta.%d.ldir"
+
+/*
+ * It is possible that a .meta file is corrupted,
+ * if we detect this we want to reproduce it.
+ * Setting oodate TRUE will have that effect.
+ */
+#define CHECK_VALID_META(p) if (!(p && *p)) { \
+ warnx("%s: %d: malformed", fname, lineno); \
+ oodate = TRUE; \
+ continue; \
+ }
+
+#define DEQUOTE(p) if (*p == '\'') { \
+ char *ep; \
+ p++; \
+ if ((ep = strchr(p, '\''))) \
+ *ep = '\0'; \
+ }
+
+Boolean
+meta_oodate(GNode *gn, Boolean oodate)
+{
+ static char *tmpdir = NULL;
+ static char cwd[MAXPATHLEN];
+ char lcwd_vname[64];
+ char ldir_vname[64];
+ char lcwd[MAXPATHLEN];
+ char latestdir[MAXPATHLEN];
+ char fname[MAXPATHLEN];
+ char fname1[MAXPATHLEN];
+ char fname2[MAXPATHLEN];
+ char fname3[MAXPATHLEN];
+ const char *dname;
+ const char *tname;
+ char *p;
+ char *cp;
+ char *link_src;
+ char *move_target;
+ static size_t cwdlen = 0;
+ static size_t tmplen = 0;
+ FILE *fp;
+ Boolean needOODATE = FALSE;
+ Lst missingFiles;
+ char *pa[4]; /* >= possible uses */
+ int i;
+ int have_filemon = FALSE;
+
+ if (oodate)
+ return oodate; /* we're done */
+
+ i = 0;
+
+ dname = Var_Value(".OBJDIR", gn, &pa[i++]);
+ tname = Var_Value(TARGET, gn, &pa[i++]);
+
+ /* if this succeeds fname3 is realpath of dname */
+ if (!meta_needed(gn, dname, tname, fname3, FALSE))
+ goto oodate_out;
+ dname = fname3;
+
+ missingFiles = Lst_Init(FALSE);
+
+ /*
+ * We need to check if the target is out-of-date. This includes
+ * checking if the expanded command has changed. This in turn
+ * requires that all variables are set in the same way that they
+ * would be if the target needs to be re-built.
+ */
+ Make_DoAllVar(gn);
+
+ meta_name(gn, fname, sizeof(fname), dname, tname, dname);
+
+#ifdef DEBUG_META_MODE
+ if (DEBUG(META))
+ fprintf(debug_file, "meta_oodate: %s\n", fname);
+#endif
+
+ if ((fp = fopen(fname, "r")) != NULL) {
+ static char *buf = NULL;
+ static size_t bufsz;
+ int lineno = 0;
+ int lastpid = 0;
+ int pid;
+ int x;
+ LstNode ln;
+ struct stat fs;
+
+ if (!buf) {
+ bufsz = 8 * BUFSIZ;
+ buf = bmake_malloc(bufsz);
+ }
+
+ if (!cwdlen) {
+ if (getcwd(cwd, sizeof(cwd)) == NULL)
+ err(1, "Could not get current working directory");
+ cwdlen = strlen(cwd);
+ }
+ strlcpy(lcwd, cwd, sizeof(lcwd));
+ strlcpy(latestdir, cwd, sizeof(latestdir));
+
+ if (!tmpdir) {
+ tmpdir = getTmpdir();
+ tmplen = strlen(tmpdir);
+ }
+
+ /* we want to track all the .meta we read */
+ Var_Append(".MAKE.META.FILES", fname, VAR_GLOBAL);
+
+ ln = Lst_First(gn->commands);
+ while (!oodate && (x = fgetLine(&buf, &bufsz, 0, fp)) > 0) {
+ lineno++;
+ if (buf[x - 1] == '\n')
+ buf[x - 1] = '\0';
+ else {
+ warnx("%s: %d: line truncated at %u", fname, lineno, x);
+ oodate = TRUE;
+ break;
+ }
+ link_src = NULL;
+ move_target = NULL;
+ /* Find the start of the build monitor section. */
+ if (!have_filemon) {
+ if (strncmp(buf, "-- filemon", 10) == 0) {
+ have_filemon = TRUE;
+ continue;
+ }
+ if (strncmp(buf, "# buildmon", 10) == 0) {
+ have_filemon = TRUE;
+ continue;
+ }
+ }
+
+ /* Delimit the record type. */
+ p = buf;
+#ifdef DEBUG_META_MODE
+ if (DEBUG(META))
+ fprintf(debug_file, "%s: %d: %s\n", fname, lineno, buf);
+#endif
+ strsep(&p, " ");
+ if (have_filemon) {
+ /*
+ * We are in the 'filemon' output section.
+ * Each record from filemon follows the general form:
+ *
+ * <key> <pid> <data>
+ *
+ * Where:
+ * <key> is a single letter, denoting the syscall.
+ * <pid> is the process that made the syscall.
+ * <data> is the arguments (of interest).
+ */
+ switch(buf[0]) {
+ case '#': /* comment */
+ case 'V': /* version */
+ break;
+ default:
+ /*
+ * We need to track pathnames per-process.
+ *
+ * Each process run by make, starts off in the 'CWD'
+ * recorded in the .meta file, if it chdirs ('C')
+ * elsewhere we need to track that - but only for
+ * that process. If it forks ('F'), we initialize
+ * the child to have the same cwd as its parent.
+ *
+ * We also need to track the 'latestdir' of
+ * interest. This is usually the same as cwd, but
+ * not if a process is reading directories.
+ *
+ * Each time we spot a different process ('pid')
+ * we save the current value of 'latestdir' in a
+ * variable qualified by 'lastpid', and
+ * re-initialize 'latestdir' to any pre-saved
+ * value for the current 'pid' and 'CWD' if none.
+ */
+ CHECK_VALID_META(p);
+ pid = atoi(p);
+ if (pid > 0 && pid != lastpid) {
+ char *ldir;
+ char *tp;
+
+ if (lastpid > 0) {
+ /* We need to remember these. */
+ Var_Set(lcwd_vname, lcwd, VAR_GLOBAL, 0);
+ Var_Set(ldir_vname, latestdir, VAR_GLOBAL, 0);
+ }
+ snprintf(lcwd_vname, sizeof(lcwd_vname), LCWD_VNAME_FMT, pid);
+ snprintf(ldir_vname, sizeof(ldir_vname), LDIR_VNAME_FMT, pid);
+ lastpid = pid;
+ ldir = Var_Value(ldir_vname, VAR_GLOBAL, &tp);
+ if (ldir) {
+ strlcpy(latestdir, ldir, sizeof(latestdir));
+ free(tp);
+ }
+ ldir = Var_Value(lcwd_vname, VAR_GLOBAL, &tp);
+ if (ldir) {
+ strlcpy(lcwd, ldir, sizeof(lcwd));
+ free(tp);
+ }
+ }
+ /* Skip past the pid. */
+ if (strsep(&p, " ") == NULL)
+ continue;
+#ifdef DEBUG_META_MODE
+ if (DEBUG(META))
+ fprintf(debug_file, "%s: %d: %d: %c: cwd=%s lcwd=%s ldir=%s\n",
+ fname, lineno,
+ pid, buf[0], cwd, lcwd, latestdir);
+#endif
+ break;
+ }
+
+ CHECK_VALID_META(p);
+
+ /* Process according to record type. */
+ switch (buf[0]) {
+ case 'X': /* eXit */
+ Var_Delete(lcwd_vname, VAR_GLOBAL);
+ Var_Delete(ldir_vname, VAR_GLOBAL);
+ lastpid = 0; /* no need to save ldir_vname */
+ break;
+
+ case 'F': /* [v]Fork */
+ {
+ char cldir[64];
+ int child;
+
+ child = atoi(p);
+ if (child > 0) {
+ snprintf(cldir, sizeof(cldir), LCWD_VNAME_FMT, child);
+ Var_Set(cldir, lcwd, VAR_GLOBAL, 0);
+ snprintf(cldir, sizeof(cldir), LDIR_VNAME_FMT, child);
+ Var_Set(cldir, latestdir, VAR_GLOBAL, 0);
+#ifdef DEBUG_META_MODE
+ if (DEBUG(META))
+ fprintf(debug_file, "%s: %d: %d: cwd=%s lcwd=%s ldir=%s\n",
+ fname, lineno,
+ child, cwd, lcwd, latestdir);
+#endif
+ }
+ }
+ break;
+
+ case 'C': /* Chdir */
+ /* Update lcwd and latest directory. */
+ strlcpy(latestdir, p, sizeof(latestdir));
+ strlcpy(lcwd, p, sizeof(lcwd));
+ Var_Set(lcwd_vname, lcwd, VAR_GLOBAL, 0);
+ Var_Set(ldir_vname, lcwd, VAR_GLOBAL, 0);
+#ifdef DEBUG_META_MODE
+ if (DEBUG(META))
+ fprintf(debug_file, "%s: %d: cwd=%s ldir=%s\n", fname, lineno, cwd, lcwd);
+#endif
+ break;
+
+ case 'M': /* renaMe */
+ /*
+ * For 'M'oves we want to check
+ * the src as for 'R'ead
+ * and the target as for 'W'rite.
+ */
+ cp = p; /* save this for a second */
+ /* now get target */
+ if (strsep(&p, " ") == NULL)
+ continue;
+ CHECK_VALID_META(p);
+ move_target = p;
+ p = cp;
+ /* 'L' and 'M' put single quotes around the args */
+ DEQUOTE(p);
+ DEQUOTE(move_target);
+ /* FALLTHROUGH */
+ case 'D': /* unlink */
+ if (*p == '/' && !Lst_IsEmpty(missingFiles)) {
+ /* remove any missingFiles entries that match p */
+ if ((ln = Lst_Find(missingFiles, p,
+ path_match)) != NULL) {
+ LstNode nln;
+ char *tp;
+
+ do {
+ nln = Lst_FindFrom(missingFiles, Lst_Succ(ln),
+ p, path_match);
+ tp = Lst_Datum(ln);
+ Lst_Remove(missingFiles, ln);
+ free(tp);
+ } while ((ln = nln) != NULL);
+ }
+ }
+ if (buf[0] == 'M') {
+ /* the target of the mv is a file 'W'ritten */
+#ifdef DEBUG_META_MODE
+ if (DEBUG(META))
+ fprintf(debug_file, "meta_oodate: M %s -> %s\n",
+ p, move_target);
+#endif
+ p = move_target;
+ goto check_write;
+ }
+ break;
+ case 'L': /* Link */
+ /*
+ * For 'L'inks check
+ * the src as for 'R'ead
+ * and the target as for 'W'rite.
+ */
+ link_src = p;
+ /* now get target */
+ if (strsep(&p, " ") == NULL)
+ continue;
+ CHECK_VALID_META(p);
+ /* 'L' and 'M' put single quotes around the args */
+ DEQUOTE(p);
+ DEQUOTE(link_src);
+#ifdef DEBUG_META_MODE
+ if (DEBUG(META))
+ fprintf(debug_file, "meta_oodate: L %s -> %s\n",
+ link_src, p);
+#endif
+ /* FALLTHROUGH */
+ case 'W': /* Write */
+ check_write:
+ /*
+ * If a file we generated within our bailiwick
+ * but outside of .OBJDIR is missing,
+ * we need to do it again.
+ */
+ /* ignore non-absolute paths */
+ if (*p != '/')
+ break;
+
+ if (Lst_IsEmpty(metaBailiwick))
+ break;
+
+ /* ignore cwd - normal dependencies handle those */
+ if (strncmp(p, cwd, cwdlen) == 0)
+ break;
+
+ if (!Lst_ForEach(metaBailiwick, prefix_match, p))
+ break;
+
+ /* tmpdir might be within */
+ if (tmplen > 0 && strncmp(p, tmpdir, tmplen) == 0)
+ break;
+
+ /* ignore anything containing the string "tmp" */
+ if ((strstr("tmp", p)))
+ break;
+
+ if ((link_src != NULL && cached_lstat(p, &fs) < 0) ||
+ (link_src == NULL && cached_stat(p, &fs) < 0)) {
+ if (!meta_ignore(gn, p)) {
+ if (Lst_Find(missingFiles, p, string_match) == NULL)
+ Lst_AtEnd(missingFiles, bmake_strdup(p));
+ }
+ }
+ break;
+ check_link_src:
+ p = link_src;
+ link_src = NULL;
+#ifdef DEBUG_META_MODE
+ if (DEBUG(META))
+ fprintf(debug_file, "meta_oodate: L src %s\n", p);
+#endif
+ /* FALLTHROUGH */
+ case 'R': /* Read */
+ case 'E': /* Exec */
+ /*
+ * Check for runtime files that can't
+ * be part of the dependencies because
+ * they are _expected_ to change.
+ */
+ if (meta_ignore(gn, p))
+ break;
+
+ /*
+ * The rest of the record is the file name.
+ * Check if it's not an absolute path.
+ */
+ {
+ char *sdirs[4];
+ char **sdp;
+ int sdx = 0;
+ int found = 0;
+
+ if (*p == '/') {
+ sdirs[sdx++] = p; /* done */
+ } else {
+ if (strcmp(".", p) == 0)
+ continue; /* no point */
+
+ /* Check vs latestdir */
+ snprintf(fname1, sizeof(fname1), "%s/%s", latestdir, p);
+ sdirs[sdx++] = fname1;
+
+ if (strcmp(latestdir, lcwd) != 0) {
+ /* Check vs lcwd */
+ snprintf(fname2, sizeof(fname2), "%s/%s", lcwd, p);
+ sdirs[sdx++] = fname2;
+ }
+ if (strcmp(lcwd, cwd) != 0) {
+ /* Check vs cwd */
+ snprintf(fname3, sizeof(fname3), "%s/%s", cwd, p);
+ sdirs[sdx++] = fname3;
+ }
+ }
+ sdirs[sdx++] = NULL;
+
+ for (sdp = sdirs; *sdp && !found; sdp++) {
+#ifdef DEBUG_META_MODE
+ if (DEBUG(META))
+ fprintf(debug_file, "%s: %d: looking for: %s\n", fname, lineno, *sdp);
+#endif
+ if (cached_stat(*sdp, &fs) == 0) {
+ found = 1;
+ p = *sdp;
+ }
+ }
+ if (found) {
+#ifdef DEBUG_META_MODE
+ if (DEBUG(META))
+ fprintf(debug_file, "%s: %d: found: %s\n", fname, lineno, p);
+#endif
+ if (!S_ISDIR(fs.st_mode) &&
+ fs.st_mtime > gn->mtime) {
+ if (DEBUG(META))
+ fprintf(debug_file, "%s: %d: file '%s' is newer than the target...\n", fname, lineno, p);
+ oodate = TRUE;
+ } else if (S_ISDIR(fs.st_mode)) {
+ /* Update the latest directory. */
+ cached_realpath(p, latestdir);
+ }
+ } else if (errno == ENOENT && *p == '/' &&
+ strncmp(p, cwd, cwdlen) != 0) {
+ /*
+ * A referenced file outside of CWD is missing.
+ * We cannot catch every eventuality here...
+ */
+ if (Lst_Find(missingFiles, p, string_match) == NULL)
+ Lst_AtEnd(missingFiles, bmake_strdup(p));
+ }
+ }
+ if (buf[0] == 'E') {
+ /* previous latestdir is no longer relevant */
+ strlcpy(latestdir, lcwd, sizeof(latestdir));
+ }
+ break;
+ default:
+ break;
+ }
+ if (!oodate && buf[0] == 'L' && link_src != NULL)
+ goto check_link_src;
+ } else if (strcmp(buf, "CMD") == 0) {
+ /*
+ * Compare the current command with the one in the
+ * meta data file.
+ */
+ if (ln == NULL) {
+ if (DEBUG(META))
+ fprintf(debug_file, "%s: %d: there were more build commands in the meta data file than there are now...\n", fname, lineno);
+ oodate = TRUE;
+ } else {
+ char *cmd = (char *)Lst_Datum(ln);
+ Boolean hasOODATE = FALSE;
+
+ if (strstr(cmd, "$?"))
+ hasOODATE = TRUE;
+ else if ((cp = strstr(cmd, ".OODATE"))) {
+ /* check for $[{(].OODATE[:)}] */
+ if (cp > cmd + 2 && cp[-2] == '$')
+ hasOODATE = TRUE;
+ }
+ if (hasOODATE) {
+ needOODATE = TRUE;
+ if (DEBUG(META))
+ fprintf(debug_file, "%s: %d: cannot compare command using .OODATE\n", fname, lineno);
+ }
+ cmd = Var_Subst(NULL, cmd, gn, VARF_WANTRES|VARF_UNDEFERR);
+
+ if ((cp = strchr(cmd, '\n'))) {
+ int n;
+
+ /*
+ * This command contains newlines, we need to
+ * fetch more from the .meta file before we
+ * attempt a comparison.
+ */
+ /* first put the newline back at buf[x - 1] */
+ buf[x - 1] = '\n';
+ do {
+ /* now fetch the next line */
+ if ((n = fgetLine(&buf, &bufsz, x, fp)) <= 0)
+ break;
+ x = n;
+ lineno++;
+ if (buf[x - 1] != '\n') {
+ warnx("%s: %d: line truncated at %u", fname, lineno, x);
+ break;
+ }
+ cp = strchr(++cp, '\n');
+ } while (cp);
+ if (buf[x - 1] == '\n')
+ buf[x - 1] = '\0';
+ }
+ if (!hasOODATE &&
+ !(gn->type & OP_NOMETA_CMP) &&
+ strcmp(p, cmd) != 0) {
+ if (DEBUG(META))
+ fprintf(debug_file, "%s: %d: a build command has changed\n%s\nvs\n%s\n", fname, lineno, p, cmd);
+ if (!metaIgnoreCMDs)
+ oodate = TRUE;
+ }
+ free(cmd);
+ ln = Lst_Succ(ln);
+ }
+ } else if (strcmp(buf, "CWD") == 0) {
+ /*
+ * Check if there are extra commands now
+ * that weren't in the meta data file.
+ */
+ if (!oodate && ln != NULL) {
+ if (DEBUG(META))
+ fprintf(debug_file, "%s: %d: there are extra build commands now that weren't in the meta data file\n", fname, lineno);
+ oodate = TRUE;
+ }
+ if (strcmp(p, cwd) != 0) {
+ if (DEBUG(META))
+ fprintf(debug_file, "%s: %d: the current working directory has changed from '%s' to '%s'\n", fname, lineno, p, curdir);
+ oodate = TRUE;
+ }
+ }
+ }
+
+ fclose(fp);
+ if (!Lst_IsEmpty(missingFiles)) {
+ if (DEBUG(META))
+ fprintf(debug_file, "%s: missing files: %s...\n",
+ fname, (char *)Lst_Datum(Lst_First(missingFiles)));
+ oodate = TRUE;
+ }
+ if (!oodate && !have_filemon && filemonMissing) {
+ if (DEBUG(META))
+ fprintf(debug_file, "%s: missing filemon data\n", fname);
+ oodate = TRUE;
+ }
+ } else {
+ if (writeMeta && metaMissing) {
+ cp = NULL;
+
+ /* if target is in .CURDIR we do not need a meta file */
+ if (gn->path && (cp = strrchr(gn->path, '/')) && cp > gn->path) {
+ if (strncmp(curdir, gn->path, (cp - gn->path)) != 0) {
+ cp = NULL; /* not in .CURDIR */
+ }
+ }
+ if (!cp) {
+ if (DEBUG(META))
+ fprintf(debug_file, "%s: required but missing\n", fname);
+ oodate = TRUE;
+ needOODATE = TRUE; /* assume the worst */
+ }
+ }
+ }
+
+ Lst_Destroy(missingFiles, (FreeProc *)free);
+
+ if (oodate && needOODATE) {
+ /*
+ * Target uses .OODATE which is empty; or we wouldn't be here.
+ * We have decided it is oodate, so .OODATE needs to be set.
+ * All we can sanely do is set it to .ALLSRC.
+ */
+ Var_Delete(OODATE, gn);
+ Var_Set(OODATE, Var_Value(ALLSRC, gn, &cp), gn, 0);
+ free(cp);
+ }
+
+ oodate_out:
+ for (i--; i >= 0; i--) {
+ free(pa[i]);
+ }
+ return oodate;
+}
+
+/* support for compat mode */
+
+static int childPipe[2];
+
+void
+meta_compat_start(void)
+{
+#ifdef USE_FILEMON_ONCE
+ /*
+ * We need to re-open filemon for each cmd.
+ */
+ BuildMon *pbm = &Mybm;
+
+ if (pbm->mfp != NULL && useFilemon) {
+ filemon_open(pbm);
+ } else {
+ pbm->mon_fd = pbm->filemon_fd = -1;
+ }
+#endif
+ if (pipe(childPipe) < 0)
+ Punt("Cannot create pipe: %s", strerror(errno));
+ /* Set close-on-exec flag for both */
+ (void)fcntl(childPipe[0], F_SETFD, FD_CLOEXEC);
+ (void)fcntl(childPipe[1], F_SETFD, FD_CLOEXEC);
+}
+
+void
+meta_compat_child(void)
+{
+ meta_job_child(NULL);
+ if (dup2(childPipe[1], 1) < 0 ||
+ dup2(1, 2) < 0) {
+ execError("dup2", "pipe");
+ _exit(1);
+ }
+}
+
+void
+meta_compat_parent(void)
+{
+ FILE *fp;
+ char buf[BUFSIZ];
+
+ close(childPipe[1]); /* child side */
+ fp = fdopen(childPipe[0], "r");
+ while (fgets(buf, sizeof(buf), fp)) {
+ meta_job_output(NULL, buf, "");
+ printf("%s", buf);
+ fflush(stdout);
+ }
+ fclose(fp);
+}
+
+#endif /* USE_META */
diff --git a/usr.bin/make/meta.h b/usr.bin/make/meta.h
new file mode 100644
index 0000000..8f1018e
--- /dev/null
+++ b/usr.bin/make/meta.h
@@ -0,0 +1,56 @@
+/* $NetBSD: meta.h,v 1.5 2016/05/12 20:28:34 sjg Exp $ */
+
+/*
+ * Things needed for 'meta' mode.
+ */
+/*
+ * Copyright (c) 2009-2010, Juniper Networks, Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the copyright holders nor the names of its
+ * contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+typedef struct BuildMon {
+ char meta_fname[MAXPATHLEN];
+ int filemon_fd;
+ int mon_fd;
+ FILE *mfp;
+} BuildMon;
+
+extern Boolean useMeta;
+
+struct Job; /* not defined yet */
+void meta_init(void);
+void meta_finish(void);
+void meta_mode_init(const char *);
+void meta_job_start(struct Job *, GNode *);
+void meta_job_child(struct Job *);
+void meta_job_error(struct Job *, GNode *, int, int);
+void meta_job_output(struct Job *, char *, const char *);
+int meta_cmd_finish(void *);
+int meta_job_finish(struct Job *);
+Boolean meta_oodate(GNode *, Boolean);
+void meta_compat_start(void);
+void meta_compat_child(void);
+void meta_compat_parent(void);
diff --git a/usr.bin/make/metachar.c b/usr.bin/make/metachar.c
new file mode 100644
index 0000000..4960338
--- /dev/null
+++ b/usr.bin/make/metachar.c
@@ -0,0 +1,88 @@
+/* $NetBSD: metachar.c,v 1.5 2015/06/19 08:03:35 mlelstv Exp $ */
+
+/*-
+ * Copyright (c) 2015 The NetBSD Foundation, Inc.
+ * All rights reserved.
+ *
+ * This code is derived from software contributed to The NetBSD Foundation
+ * by Christos Zoulas.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
+ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#if HAVE_NBTOOL_CONFIG_H
+#include "nbtool_config.h"
+#endif
+
+#if defined(MAKE_NATIVE) || defined(HAVE_NBTOOL_CONFIG_H)
+#include <sys/cdefs.h>
+#endif
+
+#if defined(__RCSID) && !defined(lint)
+__RCSID("$NetBSD: metachar.c,v 1.5 2015/06/19 08:03:35 mlelstv Exp $");
+#endif
+
+#include "metachar.h"
+/*
+ * The following array is used to make a fast determination of which
+ * characters are interpreted specially by the shell. If a command
+ * contains any of these characters, it is executed by the shell, not
+ * directly by us.
+ *
+ * perhaps move it to ctype?
+ */
+
+unsigned char _metachar[128] = {
+// nul soh stx etx eot enq ack bel
+ 1, 0, 0, 0, 0, 0, 0, 0,
+// bs ht nl vt np cr so si
+ 0, 0, 1, 0, 0, 0, 0, 0,
+// dle dc1 dc2 dc3 dc4 nak syn etb
+ 0, 0, 0, 0, 0, 0, 0, 0,
+// can em sub esc fs gs rs us
+ 0, 0, 0, 0, 0, 0, 0, 0,
+// sp ! " # $ % & '
+ 0, 1, 1, 1, 1, 0, 1, 1,
+// ( ) * + , - . /
+ 1, 1, 1, 0, 0, 0, 0, 0,
+// 0 1 2 3 4 5 6 7
+ 0, 0, 0, 0, 0, 0, 0, 0,
+// 8 9 : ; < = > ?
+ 0, 0, 0, 1, 1, 0, 1, 1,
+// @ A B C D E F G
+ 0, 0, 0, 0, 0, 0, 0, 0,
+// H I J K L M N O
+ 0, 0, 0, 0, 0, 0, 0, 0,
+// P Q R S T U V W
+ 0, 0, 0, 0, 0, 0, 0, 0,
+// X Y Z [ \ ] ^ _
+ 0, 0, 0, 1, 1, 1, 1, 0,
+// ` a b c d e f g
+ 1, 0, 0, 0, 0, 0, 0, 0,
+// h i j k l m n o
+ 0, 0, 0, 0, 0, 0, 0, 0,
+// p q r s t u v w
+ 0, 0, 0, 0, 0, 0, 0, 0,
+// x y z { | } ~ del
+ 0, 0, 0, 1, 1, 1, 1, 0,
+};
+
diff --git a/usr.bin/make/metachar.h b/usr.bin/make/metachar.h
new file mode 100644
index 0000000..db88d67
--- /dev/null
+++ b/usr.bin/make/metachar.h
@@ -0,0 +1,61 @@
+/* $NetBSD: metachar.h,v 1.4 2015/06/21 20:26:02 christos Exp $ */
+
+/*-
+ * Copyright (c) 2015 The NetBSD Foundation, Inc.
+ * All rights reserved.
+ *
+ * This code is derived from software contributed to The NetBSD Foundation
+ * by Christos Zoulas.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
+ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+#ifndef _METACHAR_H
+#define _METACHAR_H
+
+#include <ctype.h>
+
+extern unsigned char _metachar[];
+
+#define ismeta(c) _metachar[(c) & 0x7f]
+
+static inline int
+hasmeta(const char *cmd)
+{
+ while (!ismeta(*cmd))
+ cmd++;
+
+ return *cmd != '\0';
+}
+
+static inline int
+needshell(const char *cmd, int white)
+{
+ while (!ismeta(*cmd) && *cmd != ':' && *cmd != '=') {
+ if (white && isspace((unsigned char)*cmd))
+ break;
+ cmd++;
+ }
+
+ return *cmd != '\0';
+}
+
+#endif /* _METACHAR_H */
diff --git a/usr.bin/make/nonints.h b/usr.bin/make/nonints.h
new file mode 100644
index 0000000..2fe2330
--- /dev/null
+++ b/usr.bin/make/nonints.h
@@ -0,0 +1,198 @@
+/* $NetBSD: nonints.h,v 1.74 2016/09/05 00:40:29 sevan Exp $ */
+
+/*-
+ * Copyright (c) 1988, 1989, 1990, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Adam de Boor.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * from: @(#)nonints.h 8.3 (Berkeley) 3/19/94
+ */
+
+/*-
+ * Copyright (c) 1989 by Berkeley Softworks
+ * All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Adam de Boor.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. All advertising materials mentioning features or use of this software
+ * must display the following acknowledgement:
+ * This product includes software developed by the University of
+ * California, Berkeley and its contributors.
+ * 4. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * from: @(#)nonints.h 8.3 (Berkeley) 3/19/94
+ */
+
+/* arch.c */
+ReturnStatus Arch_ParseArchive(char **, Lst, GNode *);
+void Arch_Touch(GNode *);
+void Arch_TouchLib(GNode *);
+time_t Arch_MTime(GNode *);
+time_t Arch_MemMTime(GNode *);
+void Arch_FindLib(GNode *, Lst);
+Boolean Arch_LibOODate(GNode *);
+void Arch_Init(void);
+void Arch_End(void);
+int Arch_IsLib(GNode *);
+
+/* compat.c */
+int CompatRunCommand(void *, void *);
+void Compat_Run(Lst);
+int Compat_Make(void *, void *);
+
+/* cond.c */
+struct If;
+int Cond_EvalExpression(const struct If *, char *, Boolean *, int, Boolean);
+int Cond_Eval(char *);
+void Cond_restore_depth(unsigned int);
+unsigned int Cond_save_depth(void);
+
+/* for.c */
+int For_Eval(char *);
+int For_Accum(char *);
+void For_Run(int);
+
+/* job.c */
+void JobReapChild(pid_t, int, Boolean);
+
+/* main.c */
+void Main_ParseArgLine(const char *);
+void MakeMode(const char *);
+char *Cmd_Exec(const char *, const char **);
+void Error(const char *, ...) MAKE_ATTR_PRINTFLIKE(1, 2);
+void Fatal(const char *, ...) MAKE_ATTR_PRINTFLIKE(1, 2) MAKE_ATTR_DEAD;
+void Punt(const char *, ...) MAKE_ATTR_PRINTFLIKE(1, 2) MAKE_ATTR_DEAD;
+void DieHorribly(void) MAKE_ATTR_DEAD;
+int PrintAddr(void *, void *);
+void Finish(int) MAKE_ATTR_DEAD;
+int eunlink(const char *);
+void execError(const char *, const char *);
+char *getTmpdir(void);
+Boolean s2Boolean(const char *, Boolean);
+Boolean getBoolean(const char *, Boolean);
+char *cached_realpath(const char *, char *);
+
+/* parse.c */
+void Parse_Error(int, const char *, ...) MAKE_ATTR_PRINTFLIKE(2, 3);
+Boolean Parse_AnyExport(void);
+Boolean Parse_IsVar(char *);
+void Parse_DoVar(char *, GNode *);
+void Parse_AddIncludeDir(char *);
+void Parse_File(const char *, int);
+void Parse_Init(void);
+void Parse_End(void);
+void Parse_SetInput(const char *, int, int, char *(*)(void *, size_t *), void *);
+Lst Parse_MainName(void);
+
+/* str.c */
+char *str_concat(const char *, const char *, int);
+char **brk_string(const char *, int *, Boolean, char **);
+char *Str_FindSubstring(const char *, const char *);
+int Str_Match(const char *, const char *);
+char *Str_SYSVMatch(const char *, const char *, int *len);
+void Str_SYSVSubst(Buffer *, char *, char *, int);
+
+/* suff.c */
+void Suff_ClearSuffixes(void);
+Boolean Suff_IsTransform(char *);
+GNode *Suff_AddTransform(char *);
+int Suff_EndTransform(void *, void *);
+void Suff_AddSuffix(char *, GNode **);
+Lst Suff_GetPath(char *);
+void Suff_DoPaths(void);
+void Suff_AddInclude(char *);
+void Suff_AddLib(char *);
+void Suff_FindDeps(GNode *);
+Lst Suff_FindPath(GNode *);
+void Suff_SetNull(char *);
+void Suff_Init(void);
+void Suff_End(void);
+void Suff_PrintAll(void);
+
+/* targ.c */
+void Targ_Init(void);
+void Targ_End(void);
+Lst Targ_List(void);
+GNode *Targ_NewGN(const char *);
+GNode *Targ_FindNode(const char *, int);
+Lst Targ_FindList(Lst, int);
+Boolean Targ_Ignore(GNode *);
+Boolean Targ_Silent(GNode *);
+Boolean Targ_Precious(GNode *);
+void Targ_SetMain(GNode *);
+int Targ_PrintCmd(void *, void *);
+int Targ_PrintNode(void *, void *);
+char *Targ_FmtTime(time_t);
+void Targ_PrintType(int);
+void Targ_PrintGraph(int);
+void Targ_Propagate(void);
+void Targ_Propagate_Wait(void);
+
+/* var.c */
+void Var_Delete(const char *, GNode *);
+void Var_Set(const char *, const char *, GNode *, int);
+void Var_Append(const char *, const char *, GNode *);
+Boolean Var_Exists(const char *, GNode *);
+char *Var_Value(const char *, GNode *, char **);
+char *Var_Parse(const char *, GNode *, int, int *, void **);
+char *Var_Subst(const char *, const char *, GNode *, int);
+char *Var_GetTail(const char *);
+char *Var_GetHead(const char *);
+void Var_Init(void);
+void Var_End(void);
+void Var_Dump(GNode *);
+void Var_ExportVars(void);
+void Var_Export(char *, int);
+void Var_UnExport(char *);
+
+/* util.c */
+void (*bmake_signal(int, void (*)(int)))(int);
diff --git a/usr.bin/make/parse.c b/usr.bin/make/parse.c
new file mode 100644
index 0000000..d7cecec
--- /dev/null
+++ b/usr.bin/make/parse.c
@@ -0,0 +1,3357 @@
+/* $NetBSD: parse.c,v 1.231 2018/12/22 00:36:32 sjg Exp $ */
+
+/*
+ * Copyright (c) 1988, 1989, 1990, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Adam de Boor.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+/*
+ * Copyright (c) 1989 by Berkeley Softworks
+ * All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Adam de Boor.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. All advertising materials mentioning features or use of this software
+ * must display the following acknowledgement:
+ * This product includes software developed by the University of
+ * California, Berkeley and its contributors.
+ * 4. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#ifndef MAKE_NATIVE
+static char rcsid[] = "$NetBSD: parse.c,v 1.231 2018/12/22 00:36:32 sjg Exp $";
+#else
+#include <sys/cdefs.h>
+#ifndef lint
+#if 0
+static char sccsid[] = "@(#)parse.c 8.3 (Berkeley) 3/19/94";
+#else
+__RCSID("$NetBSD: parse.c,v 1.231 2018/12/22 00:36:32 sjg Exp $");
+#endif
+#endif /* not lint */
+#endif
+
+/*-
+ * parse.c --
+ * Functions to parse a makefile.
+ *
+ * One function, Parse_Init, must be called before any functions
+ * in this module are used. After that, the function Parse_File is the
+ * main entry point and controls most of the other functions in this
+ * module.
+ *
+ * Most important structures are kept in Lsts. Directories for
+ * the .include "..." function are kept in the 'parseIncPath' Lst, while
+ * those for the .include <...> are kept in the 'sysIncPath' Lst. The
+ * targets currently being defined are kept in the 'targets' Lst.
+ *
+ * The variables 'fname' and 'lineno' are used to track the name
+ * of the current file and the line number in that file so that error
+ * messages can be more meaningful.
+ *
+ * Interface:
+ * Parse_Init Initialization function which must be
+ * called before anything else in this module
+ * is used.
+ *
+ * Parse_End Cleanup the module
+ *
+ * Parse_File Function used to parse a makefile. It must
+ * be given the name of the file, which should
+ * already have been opened, and a function
+ * to call to read a character from the file.
+ *
+ * Parse_IsVar Returns TRUE if the given line is a
+ * variable assignment. Used by MainParseArgs
+ * to determine if an argument is a target
+ * or a variable assignment. Used internally
+ * for pretty much the same thing...
+ *
+ * Parse_Error Function called when an error occurs in
+ * parsing. Used by the variable and
+ * conditional modules.
+ * Parse_MainName Returns a Lst of the main target to create.
+ */
+
+#include <sys/types.h>
+#include <sys/mman.h>
+#include <sys/stat.h>
+#include <assert.h>
+#include <ctype.h>
+#include <errno.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdint.h>
+
+#ifndef MAP_FILE
+#define MAP_FILE 0
+#endif
+#ifndef MAP_COPY
+#define MAP_COPY MAP_PRIVATE
+#endif
+
+#include "make.h"
+#include "hash.h"
+#include "dir.h"
+#include "job.h"
+#include "buf.h"
+#include "pathnames.h"
+
+////////////////////////////////////////////////////////////
+// types and constants
+
+/*
+ * Structure for a file being read ("included file")
+ */
+typedef struct IFile {
+ char *fname; /* name of file */
+ int lineno; /* current line number in file */
+ int first_lineno; /* line number of start of text */
+ int cond_depth; /* 'if' nesting when file opened */
+ Boolean depending; /* state of doing_depend on EOF */
+ char *P_str; /* point to base of string buffer */
+ char *P_ptr; /* point to next char of string buffer */
+ char *P_end; /* point to the end of string buffer */
+ char *(*nextbuf)(void *, size_t *); /* Function to get more data */
+ void *nextbuf_arg; /* Opaque arg for nextbuf() */
+ struct loadedfile *lf; /* loadedfile object, if any */
+} IFile;
+
+
+/*
+ * These values are returned by ParseEOF to tell Parse_File whether to
+ * CONTINUE parsing, i.e. it had only reached the end of an include file,
+ * or if it's DONE.
+ */
+#define CONTINUE 1
+#define DONE 0
+
+/*
+ * Tokens for target attributes
+ */
+typedef enum {
+ Begin, /* .BEGIN */
+ Default, /* .DEFAULT */
+ DeleteOnError, /* .DELETE_ON_ERROR */
+ End, /* .END */
+ dotError, /* .ERROR */
+ Ignore, /* .IGNORE */
+ Includes, /* .INCLUDES */
+ Interrupt, /* .INTERRUPT */
+ Libs, /* .LIBS */
+ Meta, /* .META */
+ MFlags, /* .MFLAGS or .MAKEFLAGS */
+ Main, /* .MAIN and we don't have anything user-specified to
+ * make */
+ NoExport, /* .NOEXPORT */
+ NoMeta, /* .NOMETA */
+ NoMetaCmp, /* .NOMETA_CMP */
+ NoPath, /* .NOPATH */
+ Not, /* Not special */
+ NotParallel, /* .NOTPARALLEL */
+ Null, /* .NULL */
+ ExObjdir, /* .OBJDIR */
+ Order, /* .ORDER */
+ Parallel, /* .PARALLEL */
+ ExPath, /* .PATH */
+ Phony, /* .PHONY */
+#ifdef POSIX
+ Posix, /* .POSIX */
+#endif
+ Precious, /* .PRECIOUS */
+ ExShell, /* .SHELL */
+ Silent, /* .SILENT */
+ SingleShell, /* .SINGLESHELL */
+ Stale, /* .STALE */
+ Suffixes, /* .SUFFIXES */
+ Wait, /* .WAIT */
+ Attribute /* Generic attribute */
+} ParseSpecial;
+
+/*
+ * Other tokens
+ */
+#define LPAREN '('
+#define RPAREN ')'
+
+
+////////////////////////////////////////////////////////////
+// result data
+
+/*
+ * The main target to create. This is the first target on the first
+ * dependency line in the first makefile.
+ */
+static GNode *mainNode;
+
+////////////////////////////////////////////////////////////
+// eval state
+
+/* targets we're working on */
+static Lst targets;
+
+#ifdef CLEANUP
+/* command lines for targets */
+static Lst targCmds;
+#endif
+
+/*
+ * specType contains the SPECial TYPE of the current target. It is
+ * Not if the target is unspecial. If it *is* special, however, the children
+ * are linked as children of the parent but not vice versa. This variable is
+ * set in ParseDoDependency
+ */
+static ParseSpecial specType;
+
+/*
+ * Predecessor node for handling .ORDER. Initialized to NULL when .ORDER
+ * seen, then set to each successive source on the line.
+ */
+static GNode *predecessor;
+
+////////////////////////////////////////////////////////////
+// parser state
+
+/* true if currently in a dependency line or its commands */
+static Boolean inLine;
+
+/* number of fatal errors */
+static int fatals = 0;
+
+/*
+ * Variables for doing includes
+ */
+
+/* current file being read */
+static IFile *curFile;
+
+/* stack of IFiles generated by .includes */
+static Lst includes;
+
+/* include paths (lists of directories) */
+Lst parseIncPath; /* dirs for "..." includes */
+Lst sysIncPath; /* dirs for <...> includes */
+Lst defIncPath; /* default for sysIncPath */
+
+////////////////////////////////////////////////////////////
+// parser tables
+
+/*
+ * The parseKeywords table is searched using binary search when deciding
+ * if a target or source is special. The 'spec' field is the ParseSpecial
+ * type of the keyword ("Not" if the keyword isn't special as a target) while
+ * the 'op' field is the operator to apply to the list of targets if the
+ * keyword is used as a source ("0" if the keyword isn't special as a source)
+ */
+static const struct {
+ const char *name; /* Name of keyword */
+ ParseSpecial spec; /* Type when used as a target */
+ int op; /* Operator when used as a source */
+} parseKeywords[] = {
+{ ".BEGIN", Begin, 0 },
+{ ".DEFAULT", Default, 0 },
+{ ".DELETE_ON_ERROR", DeleteOnError, 0 },
+{ ".END", End, 0 },
+{ ".ERROR", dotError, 0 },
+{ ".EXEC", Attribute, OP_EXEC },
+{ ".IGNORE", Ignore, OP_IGNORE },
+{ ".INCLUDES", Includes, 0 },
+{ ".INTERRUPT", Interrupt, 0 },
+{ ".INVISIBLE", Attribute, OP_INVISIBLE },
+{ ".JOIN", Attribute, OP_JOIN },
+{ ".LIBS", Libs, 0 },
+{ ".MADE", Attribute, OP_MADE },
+{ ".MAIN", Main, 0 },
+{ ".MAKE", Attribute, OP_MAKE },
+{ ".MAKEFLAGS", MFlags, 0 },
+{ ".META", Meta, OP_META },
+{ ".MFLAGS", MFlags, 0 },
+{ ".NOMETA", NoMeta, OP_NOMETA },
+{ ".NOMETA_CMP", NoMetaCmp, OP_NOMETA_CMP },
+{ ".NOPATH", NoPath, OP_NOPATH },
+{ ".NOTMAIN", Attribute, OP_NOTMAIN },
+{ ".NOTPARALLEL", NotParallel, 0 },
+{ ".NO_PARALLEL", NotParallel, 0 },
+{ ".NULL", Null, 0 },
+{ ".OBJDIR", ExObjdir, 0 },
+{ ".OPTIONAL", Attribute, OP_OPTIONAL },
+{ ".ORDER", Order, 0 },
+{ ".PARALLEL", Parallel, 0 },
+{ ".PATH", ExPath, 0 },
+{ ".PHONY", Phony, OP_PHONY },
+#ifdef POSIX
+{ ".POSIX", Posix, 0 },
+#endif
+{ ".PRECIOUS", Precious, OP_PRECIOUS },
+{ ".RECURSIVE", Attribute, OP_MAKE },
+{ ".SHELL", ExShell, 0 },
+{ ".SILENT", Silent, OP_SILENT },
+{ ".SINGLESHELL", SingleShell, 0 },
+{ ".STALE", Stale, 0 },
+{ ".SUFFIXES", Suffixes, 0 },
+{ ".USE", Attribute, OP_USE },
+{ ".USEBEFORE", Attribute, OP_USEBEFORE },
+{ ".WAIT", Wait, 0 },
+};
+
+////////////////////////////////////////////////////////////
+// local functions
+
+static int ParseIsEscaped(const char *, const char *);
+static void ParseErrorInternal(const char *, size_t, int, const char *, ...)
+ MAKE_ATTR_PRINTFLIKE(4,5);
+static void ParseVErrorInternal(FILE *, const char *, size_t, int, const char *, va_list)
+ MAKE_ATTR_PRINTFLIKE(5, 0);
+static int ParseFindKeyword(const char *);
+static int ParseLinkSrc(void *, void *);
+static int ParseDoOp(void *, void *);
+static void ParseDoSrc(int, const char *);
+static int ParseFindMain(void *, void *);
+static int ParseAddDir(void *, void *);
+static int ParseClearPath(void *, void *);
+static void ParseDoDependency(char *);
+static int ParseAddCmd(void *, void *);
+static void ParseHasCommands(void *);
+static void ParseDoInclude(char *);
+static void ParseSetParseFile(const char *);
+static void ParseSetIncludedFile(void);
+#ifdef GMAKEEXPORT
+static void ParseGmakeExport(char *);
+#endif
+static int ParseEOF(void);
+static char *ParseReadLine(void);
+static void ParseFinishLine(void);
+static void ParseMark(GNode *);
+
+////////////////////////////////////////////////////////////
+// file loader
+
+struct loadedfile {
+ const char *path; /* name, for error reports */
+ char *buf; /* contents buffer */
+ size_t len; /* length of contents */
+ size_t maplen; /* length of mmap area, or 0 */
+ Boolean used; /* XXX: have we used the data yet */
+};
+
+/*
+ * Constructor/destructor for loadedfile
+ */
+static struct loadedfile *
+loadedfile_create(const char *path)
+{
+ struct loadedfile *lf;
+
+ lf = bmake_malloc(sizeof(*lf));
+ lf->path = (path == NULL ? "(stdin)" : path);
+ lf->buf = NULL;
+ lf->len = 0;
+ lf->maplen = 0;
+ lf->used = FALSE;
+ return lf;
+}
+
+static void
+loadedfile_destroy(struct loadedfile *lf)
+{
+ if (lf->buf != NULL) {
+ if (lf->maplen > 0) {
+ munmap(lf->buf, lf->maplen);
+ } else {
+ free(lf->buf);
+ }
+ }
+ free(lf);
+}
+
+/*
+ * nextbuf() operation for loadedfile, as needed by the weird and twisted
+ * logic below. Once that's cleaned up, we can get rid of lf->used...
+ */
+static char *
+loadedfile_nextbuf(void *x, size_t *len)
+{
+ struct loadedfile *lf = x;
+
+ if (lf->used) {
+ return NULL;
+ }
+ lf->used = TRUE;
+ *len = lf->len;
+ return lf->buf;
+}
+
+/*
+ * Try to get the size of a file.
+ */
+static ReturnStatus
+load_getsize(int fd, size_t *ret)
+{
+ struct stat st;
+
+ if (fstat(fd, &st) < 0) {
+ return FAILURE;
+ }
+
+ if (!S_ISREG(st.st_mode)) {
+ return FAILURE;
+ }
+
+ /*
+ * st_size is an off_t, which is 64 bits signed; *ret is
+ * size_t, which might be 32 bits unsigned or 64 bits
+ * unsigned. Rather than being elaborate, just punt on
+ * files that are more than 2^31 bytes. We should never
+ * see a makefile that size in practice...
+ *
+ * While we're at it reject negative sizes too, just in case.
+ */
+ if (st.st_size < 0 || st.st_size > 0x7fffffff) {
+ return FAILURE;
+ }
+
+ *ret = (size_t) st.st_size;
+ return SUCCESS;
+}
+
+/*
+ * Read in a file.
+ *
+ * Until the path search logic can be moved under here instead of
+ * being in the caller in another source file, we need to have the fd
+ * passed in already open. Bleh.
+ *
+ * If the path is NULL use stdin and (to insure against fd leaks)
+ * assert that the caller passed in -1.
+ */
+static struct loadedfile *
+loadfile(const char *path, int fd)
+{
+ struct loadedfile *lf;
+ static long pagesize = 0;
+ ssize_t result;
+ size_t bufpos;
+
+ lf = loadedfile_create(path);
+
+ if (path == NULL) {
+ assert(fd == -1);
+ fd = STDIN_FILENO;
+ } else {
+#if 0 /* notyet */
+ fd = open(path, O_RDONLY);
+ if (fd < 0) {
+ ...
+ Error("%s: %s", path, strerror(errno));
+ exit(1);
+ }
+#endif
+ }
+
+ if (load_getsize(fd, &lf->len) == SUCCESS) {
+ /* found a size, try mmap */
+ if (pagesize == 0)
+ pagesize = sysconf(_SC_PAGESIZE);
+ if (pagesize <= 0) {
+ pagesize = 0x1000;
+ }
+ /* round size up to a page */
+ lf->maplen = pagesize * ((lf->len + pagesize - 1)/pagesize);
+
+ /*
+ * XXX hack for dealing with empty files; remove when
+ * we're no longer limited by interfacing to the old
+ * logic elsewhere in this file.
+ */
+ if (lf->maplen == 0) {
+ lf->maplen = pagesize;
+ }
+
+ /*
+ * FUTURE: remove PROT_WRITE when the parser no longer
+ * needs to scribble on the input.
+ */
+ lf->buf = mmap(NULL, lf->maplen, PROT_READ|PROT_WRITE,
+ MAP_FILE|MAP_COPY, fd, 0);
+ if (lf->buf != MAP_FAILED) {
+ /* succeeded */
+ if (lf->len == lf->maplen && lf->buf[lf->len - 1] != '\n') {
+ char *b = bmake_malloc(lf->len + 1);
+ b[lf->len] = '\n';
+ memcpy(b, lf->buf, lf->len++);
+ munmap(lf->buf, lf->maplen);
+ lf->maplen = 0;
+ lf->buf = b;
+ }
+ goto done;
+ }
+ }
+
+ /* cannot mmap; load the traditional way */
+
+ lf->maplen = 0;
+ lf->len = 1024;
+ lf->buf = bmake_malloc(lf->len);
+
+ bufpos = 0;
+ while (1) {
+ assert(bufpos <= lf->len);
+ if (bufpos == lf->len) {
+ if (lf->len > SIZE_MAX/2) {
+ errno = EFBIG;
+ Error("%s: file too large", path);
+ exit(1);
+ }
+ lf->len *= 2;
+ lf->buf = bmake_realloc(lf->buf, lf->len);
+ }
+ assert(bufpos < lf->len);
+ result = read(fd, lf->buf + bufpos, lf->len - bufpos);
+ if (result < 0) {
+ Error("%s: read error: %s", path, strerror(errno));
+ exit(1);
+ }
+ if (result == 0) {
+ break;
+ }
+ bufpos += result;
+ }
+ assert(bufpos <= lf->len);
+ lf->len = bufpos;
+
+ /* truncate malloc region to actual length (maybe not useful) */
+ if (lf->len > 0) {
+ /* as for mmap case, ensure trailing \n */
+ if (lf->buf[lf->len - 1] != '\n')
+ lf->len++;
+ lf->buf = bmake_realloc(lf->buf, lf->len);
+ lf->buf[lf->len - 1] = '\n';
+ }
+
+done:
+ if (path != NULL) {
+ close(fd);
+ }
+ return lf;
+}
+
+////////////////////////////////////////////////////////////
+// old code
+
+/*-
+ *----------------------------------------------------------------------
+ * ParseIsEscaped --
+ * Check if the current character is escaped on the current line
+ *
+ * Results:
+ * 0 if the character is not backslash escaped, 1 otherwise
+ *
+ * Side Effects:
+ * None
+ *----------------------------------------------------------------------
+ */
+static int
+ParseIsEscaped(const char *line, const char *c)
+{
+ int active = 0;
+ for (;;) {
+ if (line == c)
+ return active;
+ if (*--c != '\\')
+ return active;
+ active = !active;
+ }
+}
+
+/*-
+ *----------------------------------------------------------------------
+ * ParseFindKeyword --
+ * Look in the table of keywords for one matching the given string.
+ *
+ * Input:
+ * str String to find
+ *
+ * Results:
+ * The index of the keyword, or -1 if it isn't there.
+ *
+ * Side Effects:
+ * None
+ *----------------------------------------------------------------------
+ */
+static int
+ParseFindKeyword(const char *str)
+{
+ int start, end, cur;
+ int diff;
+
+ start = 0;
+ end = (sizeof(parseKeywords)/sizeof(parseKeywords[0])) - 1;
+
+ do {
+ cur = start + ((end - start) / 2);
+ diff = strcmp(str, parseKeywords[cur].name);
+
+ if (diff == 0) {
+ return (cur);
+ } else if (diff < 0) {
+ end = cur - 1;
+ } else {
+ start = cur + 1;
+ }
+ } while (start <= end);
+ return (-1);
+}
+
+/*-
+ * ParseVErrorInternal --
+ * Error message abort function for parsing. Prints out the context
+ * of the error (line number and file) as well as the message with
+ * two optional arguments.
+ *
+ * Results:
+ * None
+ *
+ * Side Effects:
+ * "fatals" is incremented if the level is PARSE_FATAL.
+ */
+/* VARARGS */
+static void
+ParseVErrorInternal(FILE *f, const char *cfname, size_t clineno, int type,
+ const char *fmt, va_list ap)
+{
+ static Boolean fatal_warning_error_printed = FALSE;
+
+ (void)fprintf(f, "%s: ", progname);
+
+ if (cfname != NULL) {
+ (void)fprintf(f, "\"");
+ if (*cfname != '/' && strcmp(cfname, "(stdin)") != 0) {
+ char *cp, *cp2;
+ const char *dir, *fname;
+
+ /*
+ * Nothing is more annoying than not knowing
+ * which Makefile is the culprit; we try ${.PARSEDIR}
+ * and apply realpath(3) if not absolute.
+ */
+ dir = Var_Value(".PARSEDIR", VAR_GLOBAL, &cp);
+ if (dir == NULL)
+ dir = ".";
+ if (*dir != '/') {
+ dir = cp2 = realpath(dir, NULL);
+ free(cp);
+ cp = cp2; /* cp2 set to NULL by Var_Value */
+ }
+ fname = Var_Value(".PARSEFILE", VAR_GLOBAL, &cp2);
+ if (fname == NULL) {
+ if ((fname = strrchr(cfname, '/')))
+ fname++;
+ else
+ fname = cfname;
+ }
+ (void)fprintf(f, "%s/%s", dir, fname);
+ free(cp2);
+ free(cp);
+ } else
+ (void)fprintf(f, "%s", cfname);
+
+ (void)fprintf(f, "\" line %d: ", (int)clineno);
+ }
+ if (type == PARSE_WARNING)
+ (void)fprintf(f, "warning: ");
+ (void)vfprintf(f, fmt, ap);
+ (void)fprintf(f, "\n");
+ (void)fflush(f);
+ if (type == PARSE_INFO)
+ return;
+ if (type == PARSE_FATAL || parseWarnFatal)
+ fatals += 1;
+ if (parseWarnFatal && !fatal_warning_error_printed) {
+ Error("parsing warnings being treated as errors");
+ fatal_warning_error_printed = TRUE;
+ }
+}
+
+/*-
+ * ParseErrorInternal --
+ * Error function
+ *
+ * Results:
+ * None
+ *
+ * Side Effects:
+ * None
+ */
+/* VARARGS */
+static void
+ParseErrorInternal(const char *cfname, size_t clineno, int type,
+ const char *fmt, ...)
+{
+ va_list ap;
+
+ va_start(ap, fmt);
+ (void)fflush(stdout);
+ ParseVErrorInternal(stderr, cfname, clineno, type, fmt, ap);
+ va_end(ap);
+
+ if (debug_file != stderr && debug_file != stdout) {
+ va_start(ap, fmt);
+ ParseVErrorInternal(debug_file, cfname, clineno, type, fmt, ap);
+ va_end(ap);
+ }
+}
+
+/*-
+ * Parse_Error --
+ * External interface to ParseErrorInternal; uses the default filename
+ * Line number.
+ *
+ * Results:
+ * None
+ *
+ * Side Effects:
+ * None
+ */
+/* VARARGS */
+void
+Parse_Error(int type, const char *fmt, ...)
+{
+ va_list ap;
+ const char *fname;
+ size_t lineno;
+
+ if (curFile == NULL) {
+ fname = NULL;
+ lineno = 0;
+ } else {
+ fname = curFile->fname;
+ lineno = curFile->lineno;
+ }
+
+ va_start(ap, fmt);
+ (void)fflush(stdout);
+ ParseVErrorInternal(stderr, fname, lineno, type, fmt, ap);
+ va_end(ap);
+
+ if (debug_file != stderr && debug_file != stdout) {
+ va_start(ap, fmt);
+ ParseVErrorInternal(debug_file, fname, lineno, type, fmt, ap);
+ va_end(ap);
+ }
+}
+
+
+/*
+ * ParseMessage
+ * Parse a .info .warning or .error directive
+ *
+ * The input is the line minus the ".". We substitute
+ * variables, print the message and exit(1) (for .error) or just print
+ * a warning if the directive is malformed.
+ */
+static Boolean
+ParseMessage(char *line)
+{
+ int mtype;
+
+ switch(*line) {
+ case 'i':
+ mtype = PARSE_INFO;
+ break;
+ case 'w':
+ mtype = PARSE_WARNING;
+ break;
+ case 'e':
+ mtype = PARSE_FATAL;
+ break;
+ default:
+ Parse_Error(PARSE_WARNING, "invalid syntax: \".%s\"", line);
+ return FALSE;
+ }
+
+ while (isalpha((unsigned char)*line))
+ line++;
+ if (!isspace((unsigned char)*line))
+ return FALSE; /* not for us */
+ while (isspace((unsigned char)*line))
+ line++;
+
+ line = Var_Subst(NULL, line, VAR_CMD, VARF_WANTRES);
+ Parse_Error(mtype, "%s", line);
+ free(line);
+
+ if (mtype == PARSE_FATAL) {
+ /* Terminate immediately. */
+ exit(1);
+ }
+ return TRUE;
+}
+
+/*-
+ *---------------------------------------------------------------------
+ * ParseLinkSrc --
+ * Link the parent node to its new child. Used in a Lst_ForEach by
+ * ParseDoDependency. If the specType isn't 'Not', the parent
+ * isn't linked as a parent of the child.
+ *
+ * Input:
+ * pgnp The parent node
+ * cgpn The child node
+ *
+ * Results:
+ * Always = 0
+ *
+ * Side Effects:
+ * New elements are added to the parents list of cgn and the
+ * children list of cgn. the unmade field of pgn is updated
+ * to reflect the additional child.
+ *---------------------------------------------------------------------
+ */
+static int
+ParseLinkSrc(void *pgnp, void *cgnp)
+{
+ GNode *pgn = (GNode *)pgnp;
+ GNode *cgn = (GNode *)cgnp;
+
+ if ((pgn->type & OP_DOUBLEDEP) && !Lst_IsEmpty (pgn->cohorts))
+ pgn = (GNode *)Lst_Datum(Lst_Last(pgn->cohorts));
+ (void)Lst_AtEnd(pgn->children, cgn);
+ if (specType == Not)
+ (void)Lst_AtEnd(cgn->parents, pgn);
+ pgn->unmade += 1;
+ if (DEBUG(PARSE)) {
+ fprintf(debug_file, "# %s: added child %s - %s\n", __func__,
+ pgn->name, cgn->name);
+ Targ_PrintNode(pgn, 0);
+ Targ_PrintNode(cgn, 0);
+ }
+ return (0);
+}
+
+/*-
+ *---------------------------------------------------------------------
+ * ParseDoOp --
+ * Apply the parsed operator to the given target node. Used in a
+ * Lst_ForEach call by ParseDoDependency once all targets have
+ * been found and their operator parsed. If the previous and new
+ * operators are incompatible, a major error is taken.
+ *
+ * Input:
+ * gnp The node to which the operator is to be applied
+ * opp The operator to apply
+ *
+ * Results:
+ * Always 0
+ *
+ * Side Effects:
+ * The type field of the node is altered to reflect any new bits in
+ * the op.
+ *---------------------------------------------------------------------
+ */
+static int
+ParseDoOp(void *gnp, void *opp)
+{
+ GNode *gn = (GNode *)gnp;
+ int op = *(int *)opp;
+ /*
+ * If the dependency mask of the operator and the node don't match and
+ * the node has actually had an operator applied to it before, and
+ * the operator actually has some dependency information in it, complain.
+ */
+ if (((op & OP_OPMASK) != (gn->type & OP_OPMASK)) &&
+ !OP_NOP(gn->type) && !OP_NOP(op))
+ {
+ Parse_Error(PARSE_FATAL, "Inconsistent operator for %s", gn->name);
+ return (1);
+ }
+
+ if ((op == OP_DOUBLEDEP) && ((gn->type & OP_OPMASK) == OP_DOUBLEDEP)) {
+ /*
+ * If the node was the object of a :: operator, we need to create a
+ * new instance of it for the children and commands on this dependency
+ * line. The new instance is placed on the 'cohorts' list of the
+ * initial one (note the initial one is not on its own cohorts list)
+ * and the new instance is linked to all parents of the initial
+ * instance.
+ */
+ GNode *cohort;
+
+ /*
+ * Propagate copied bits to the initial node. They'll be propagated
+ * back to the rest of the cohorts later.
+ */
+ gn->type |= op & ~OP_OPMASK;
+
+ cohort = Targ_FindNode(gn->name, TARG_NOHASH);
+ if (doing_depend)
+ ParseMark(cohort);
+ /*
+ * Make the cohort invisible as well to avoid duplicating it into
+ * other variables. True, parents of this target won't tend to do
+ * anything with their local variables, but better safe than
+ * sorry. (I think this is pointless now, since the relevant list
+ * traversals will no longer see this node anyway. -mycroft)
+ */
+ cohort->type = op | OP_INVISIBLE;
+ (void)Lst_AtEnd(gn->cohorts, cohort);
+ cohort->centurion = gn;
+ gn->unmade_cohorts += 1;
+ snprintf(cohort->cohort_num, sizeof cohort->cohort_num, "#%d",
+ gn->unmade_cohorts);
+ } else {
+ /*
+ * We don't want to nuke any previous flags (whatever they were) so we
+ * just OR the new operator into the old
+ */
+ gn->type |= op;
+ }
+
+ return (0);
+}
+
+/*-
+ *---------------------------------------------------------------------
+ * ParseDoSrc --
+ * Given the name of a source, figure out if it is an attribute
+ * and apply it to the targets if it is. Else decide if there is
+ * some attribute which should be applied *to* the source because
+ * of some special target and apply it if so. Otherwise, make the
+ * source be a child of the targets in the list 'targets'
+ *
+ * Input:
+ * tOp operator (if any) from special targets
+ * src name of the source to handle
+ *
+ * Results:
+ * None
+ *
+ * Side Effects:
+ * Operator bits may be added to the list of targets or to the source.
+ * The targets may have a new source added to their lists of children.
+ *---------------------------------------------------------------------
+ */
+static void
+ParseDoSrc(int tOp, const char *src)
+{
+ GNode *gn = NULL;
+ static int wait_number = 0;
+ char wait_src[16];
+
+ if (*src == '.' && isupper ((unsigned char)src[1])) {
+ int keywd = ParseFindKeyword(src);
+ if (keywd != -1) {
+ int op = parseKeywords[keywd].op;
+ if (op != 0) {
+ Lst_ForEach(targets, ParseDoOp, &op);
+ return;
+ }
+ if (parseKeywords[keywd].spec == Wait) {
+ /*
+ * We add a .WAIT node in the dependency list.
+ * After any dynamic dependencies (and filename globbing)
+ * have happened, it is given a dependency on the each
+ * previous child back to and previous .WAIT node.
+ * The next child won't be scheduled until the .WAIT node
+ * is built.
+ * We give each .WAIT node a unique name (mainly for diag).
+ */
+ snprintf(wait_src, sizeof wait_src, ".WAIT_%u", ++wait_number);
+ gn = Targ_FindNode(wait_src, TARG_NOHASH);
+ if (doing_depend)
+ ParseMark(gn);
+ gn->type = OP_WAIT | OP_PHONY | OP_DEPENDS | OP_NOTMAIN;
+ Lst_ForEach(targets, ParseLinkSrc, gn);
+ return;
+ }
+ }
+ }
+
+ switch (specType) {
+ case Main:
+ /*
+ * If we have noted the existence of a .MAIN, it means we need
+ * to add the sources of said target to the list of things
+ * to create. The string 'src' is likely to be free, so we
+ * must make a new copy of it. Note that this will only be
+ * invoked if the user didn't specify a target on the command
+ * line. This is to allow #ifmake's to succeed, or something...
+ */
+ (void)Lst_AtEnd(create, bmake_strdup(src));
+ /*
+ * Add the name to the .TARGETS variable as well, so the user can
+ * employ that, if desired.
+ */
+ Var_Append(".TARGETS", src, VAR_GLOBAL);
+ return;
+
+ case Order:
+ /*
+ * Create proper predecessor/successor links between the previous
+ * source and the current one.
+ */
+ gn = Targ_FindNode(src, TARG_CREATE);
+ if (doing_depend)
+ ParseMark(gn);
+ if (predecessor != NULL) {
+ (void)Lst_AtEnd(predecessor->order_succ, gn);
+ (void)Lst_AtEnd(gn->order_pred, predecessor);
+ if (DEBUG(PARSE)) {
+ fprintf(debug_file, "# %s: added Order dependency %s - %s\n",
+ __func__, predecessor->name, gn->name);
+ Targ_PrintNode(predecessor, 0);
+ Targ_PrintNode(gn, 0);
+ }
+ }
+ /*
+ * The current source now becomes the predecessor for the next one.
+ */
+ predecessor = gn;
+ break;
+
+ default:
+ /*
+ * If the source is not an attribute, we need to find/create
+ * a node for it. After that we can apply any operator to it
+ * from a special target or link it to its parents, as
+ * appropriate.
+ *
+ * In the case of a source that was the object of a :: operator,
+ * the attribute is applied to all of its instances (as kept in
+ * the 'cohorts' list of the node) or all the cohorts are linked
+ * to all the targets.
+ */
+
+ /* Find/create the 'src' node and attach to all targets */
+ gn = Targ_FindNode(src, TARG_CREATE);
+ if (doing_depend)
+ ParseMark(gn);
+ if (tOp) {
+ gn->type |= tOp;
+ } else {
+ Lst_ForEach(targets, ParseLinkSrc, gn);
+ }
+ break;
+ }
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * ParseFindMain --
+ * Find a real target in the list and set it to be the main one.
+ * Called by ParseDoDependency when a main target hasn't been found
+ * yet.
+ *
+ * Input:
+ * gnp Node to examine
+ *
+ * Results:
+ * 0 if main not found yet, 1 if it is.
+ *
+ * Side Effects:
+ * mainNode is changed and Targ_SetMain is called.
+ *
+ *-----------------------------------------------------------------------
+ */
+static int
+ParseFindMain(void *gnp, void *dummy MAKE_ATTR_UNUSED)
+{
+ GNode *gn = (GNode *)gnp;
+ if ((gn->type & OP_NOTARGET) == 0) {
+ mainNode = gn;
+ Targ_SetMain(gn);
+ return 1;
+ } else {
+ return 0;
+ }
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * ParseAddDir --
+ * Front-end for Dir_AddDir to make sure Lst_ForEach keeps going
+ *
+ * Results:
+ * === 0
+ *
+ * Side Effects:
+ * See Dir_AddDir.
+ *
+ *-----------------------------------------------------------------------
+ */
+static int
+ParseAddDir(void *path, void *name)
+{
+ (void)Dir_AddDir((Lst) path, (char *)name);
+ return(0);
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * ParseClearPath --
+ * Front-end for Dir_ClearPath to make sure Lst_ForEach keeps going
+ *
+ * Results:
+ * === 0
+ *
+ * Side Effects:
+ * See Dir_ClearPath
+ *
+ *-----------------------------------------------------------------------
+ */
+static int
+ParseClearPath(void *path, void *dummy MAKE_ATTR_UNUSED)
+{
+ Dir_ClearPath((Lst) path);
+ return 0;
+}
+
+/*-
+ *---------------------------------------------------------------------
+ * ParseDoDependency --
+ * Parse the dependency line in line.
+ *
+ * Input:
+ * line the line to parse
+ *
+ * Results:
+ * None
+ *
+ * Side Effects:
+ * The nodes of the sources are linked as children to the nodes of the
+ * targets. Some nodes may be created.
+ *
+ * We parse a dependency line by first extracting words from the line and
+ * finding nodes in the list of all targets with that name. This is done
+ * until a character is encountered which is an operator character. Currently
+ * these are only ! and :. At this point the operator is parsed and the
+ * pointer into the line advanced until the first source is encountered.
+ * The parsed operator is applied to each node in the 'targets' list,
+ * which is where the nodes found for the targets are kept, by means of
+ * the ParseDoOp function.
+ * The sources are read in much the same way as the targets were except
+ * that now they are expanded using the wildcarding scheme of the C-Shell
+ * and all instances of the resulting words in the list of all targets
+ * are found. Each of the resulting nodes is then linked to each of the
+ * targets as one of its children.
+ * Certain targets are handled specially. These are the ones detailed
+ * by the specType variable.
+ * The storing of transformation rules is also taken care of here.
+ * A target is recognized as a transformation rule by calling
+ * Suff_IsTransform. If it is a transformation rule, its node is gotten
+ * from the suffix module via Suff_AddTransform rather than the standard
+ * Targ_FindNode in the target module.
+ *---------------------------------------------------------------------
+ */
+static void
+ParseDoDependency(char *line)
+{
+ char *cp; /* our current position */
+ GNode *gn = NULL; /* a general purpose temporary node */
+ int op; /* the operator on the line */
+ char savec; /* a place to save a character */
+ Lst paths; /* List of search paths to alter when parsing
+ * a list of .PATH targets */
+ int tOp; /* operator from special target */
+ Lst sources; /* list of archive source names after
+ * expansion */
+ Lst curTargs; /* list of target names to be found and added
+ * to the targets list */
+ char *lstart = line;
+
+ if (DEBUG(PARSE))
+ fprintf(debug_file, "ParseDoDependency(%s)\n", line);
+ tOp = 0;
+
+ specType = Not;
+ paths = NULL;
+
+ curTargs = Lst_Init(FALSE);
+
+ /*
+ * First, grind through the targets.
+ */
+
+ do {
+ /*
+ * Here LINE points to the beginning of the next word, and
+ * LSTART points to the actual beginning of the line.
+ */
+
+ /* Find the end of the next word. */
+ for (cp = line; *cp && (ParseIsEscaped(lstart, cp) ||
+ !(isspace((unsigned char)*cp) ||
+ *cp == '!' || *cp == ':' || *cp == LPAREN));
+ cp++) {
+ if (*cp == '$') {
+ /*
+ * Must be a dynamic source (would have been expanded
+ * otherwise), so call the Var module to parse the puppy
+ * so we can safely advance beyond it...There should be
+ * no errors in this, as they would have been discovered
+ * in the initial Var_Subst and we wouldn't be here.
+ */
+ int length;
+ void *freeIt;
+
+ (void)Var_Parse(cp, VAR_CMD, VARF_UNDEFERR|VARF_WANTRES,
+ &length, &freeIt);
+ free(freeIt);
+ cp += length-1;
+ }
+ }
+
+ /*
+ * If the word is followed by a left parenthesis, it's the
+ * name of an object file inside an archive (ar file).
+ */
+ if (!ParseIsEscaped(lstart, cp) && *cp == LPAREN) {
+ /*
+ * Archives must be handled specially to make sure the OP_ARCHV
+ * flag is set in their 'type' field, for one thing, and because
+ * things like "archive(file1.o file2.o file3.o)" are permissible.
+ * Arch_ParseArchive will set 'line' to be the first non-blank
+ * after the archive-spec. It creates/finds nodes for the members
+ * and places them on the given list, returning SUCCESS if all
+ * went well and FAILURE if there was an error in the
+ * specification. On error, line should remain untouched.
+ */
+ if (Arch_ParseArchive(&line, targets, VAR_CMD) != SUCCESS) {
+ Parse_Error(PARSE_FATAL,
+ "Error in archive specification: \"%s\"", line);
+ goto out;
+ } else {
+ /* Done with this word; on to the next. */
+ cp = line;
+ continue;
+ }
+ }
+
+ if (!*cp) {
+ /*
+ * We got to the end of the line while we were still
+ * looking at targets.
+ *
+ * Ending a dependency line without an operator is a Bozo
+ * no-no. As a heuristic, this is also often triggered by
+ * undetected conflicts from cvs/rcs merges.
+ */
+ if ((strncmp(line, "<<<<<<", 6) == 0) ||
+ (strncmp(line, "======", 6) == 0) ||
+ (strncmp(line, ">>>>>>", 6) == 0))
+ Parse_Error(PARSE_FATAL,
+ "Makefile appears to contain unresolved cvs/rcs/??? merge conflicts");
+ else
+ Parse_Error(PARSE_FATAL, lstart[0] == '.' ? "Unknown directive"
+ : "Need an operator");
+ goto out;
+ }
+
+ /* Insert a null terminator. */
+ savec = *cp;
+ *cp = '\0';
+
+ /*
+ * Got the word. See if it's a special target and if so set
+ * specType to match it.
+ */
+ if (*line == '.' && isupper ((unsigned char)line[1])) {
+ /*
+ * See if the target is a special target that must have it
+ * or its sources handled specially.
+ */
+ int keywd = ParseFindKeyword(line);
+ if (keywd != -1) {
+ if (specType == ExPath && parseKeywords[keywd].spec != ExPath) {
+ Parse_Error(PARSE_FATAL, "Mismatched special targets");
+ goto out;
+ }
+
+ specType = parseKeywords[keywd].spec;
+ tOp = parseKeywords[keywd].op;
+
+ /*
+ * Certain special targets have special semantics:
+ * .PATH Have to set the dirSearchPath
+ * variable too
+ * .MAIN Its sources are only used if
+ * nothing has been specified to
+ * create.
+ * .DEFAULT Need to create a node to hang
+ * commands on, but we don't want
+ * it in the graph, nor do we want
+ * it to be the Main Target, so we
+ * create it, set OP_NOTMAIN and
+ * add it to the list, setting
+ * DEFAULT to the new node for
+ * later use. We claim the node is
+ * A transformation rule to make
+ * life easier later, when we'll
+ * use Make_HandleUse to actually
+ * apply the .DEFAULT commands.
+ * .PHONY The list of targets
+ * .NOPATH Don't search for file in the path
+ * .STALE
+ * .BEGIN
+ * .END
+ * .ERROR
+ * .DELETE_ON_ERROR
+ * .INTERRUPT Are not to be considered the
+ * main target.
+ * .NOTPARALLEL Make only one target at a time.
+ * .SINGLESHELL Create a shell for each command.
+ * .ORDER Must set initial predecessor to NULL
+ */
+ switch (specType) {
+ case ExPath:
+ if (paths == NULL) {
+ paths = Lst_Init(FALSE);
+ }
+ (void)Lst_AtEnd(paths, dirSearchPath);
+ break;
+ case Main:
+ if (!Lst_IsEmpty(create)) {
+ specType = Not;
+ }
+ break;
+ case Begin:
+ case End:
+ case Stale:
+ case dotError:
+ case Interrupt:
+ gn = Targ_FindNode(line, TARG_CREATE);
+ if (doing_depend)
+ ParseMark(gn);
+ gn->type |= OP_NOTMAIN|OP_SPECIAL;
+ (void)Lst_AtEnd(targets, gn);
+ break;
+ case Default:
+ gn = Targ_NewGN(".DEFAULT");
+ gn->type |= (OP_NOTMAIN|OP_TRANSFORM);
+ (void)Lst_AtEnd(targets, gn);
+ DEFAULT = gn;
+ break;
+ case DeleteOnError:
+ deleteOnError = TRUE;
+ break;
+ case NotParallel:
+ maxJobs = 1;
+ break;
+ case SingleShell:
+ compatMake = TRUE;
+ break;
+ case Order:
+ predecessor = NULL;
+ break;
+ default:
+ break;
+ }
+ } else if (strncmp(line, ".PATH", 5) == 0) {
+ /*
+ * .PATH<suffix> has to be handled specially.
+ * Call on the suffix module to give us a path to
+ * modify.
+ */
+ Lst path;
+
+ specType = ExPath;
+ path = Suff_GetPath(&line[5]);
+ if (path == NULL) {
+ Parse_Error(PARSE_FATAL,
+ "Suffix '%s' not defined (yet)",
+ &line[5]);
+ goto out;
+ } else {
+ if (paths == NULL) {
+ paths = Lst_Init(FALSE);
+ }
+ (void)Lst_AtEnd(paths, path);
+ }
+ }
+ }
+
+ /*
+ * Have word in line. Get or create its node and stick it at
+ * the end of the targets list
+ */
+ if ((specType == Not) && (*line != '\0')) {
+ if (Dir_HasWildcards(line)) {
+ /*
+ * Targets are to be sought only in the current directory,
+ * so create an empty path for the thing. Note we need to
+ * use Dir_Destroy in the destruction of the path as the
+ * Dir module could have added a directory to the path...
+ */
+ Lst emptyPath = Lst_Init(FALSE);
+
+ Dir_Expand(line, emptyPath, curTargs);
+
+ Lst_Destroy(emptyPath, Dir_Destroy);
+ } else {
+ /*
+ * No wildcards, but we want to avoid code duplication,
+ * so create a list with the word on it.
+ */
+ (void)Lst_AtEnd(curTargs, line);
+ }
+
+ /* Apply the targets. */
+
+ while(!Lst_IsEmpty(curTargs)) {
+ char *targName = (char *)Lst_DeQueue(curTargs);
+
+ if (!Suff_IsTransform (targName)) {
+ gn = Targ_FindNode(targName, TARG_CREATE);
+ } else {
+ gn = Suff_AddTransform(targName);
+ }
+ if (doing_depend)
+ ParseMark(gn);
+
+ (void)Lst_AtEnd(targets, gn);
+ }
+ } else if (specType == ExPath && *line != '.' && *line != '\0') {
+ Parse_Error(PARSE_WARNING, "Extra target (%s) ignored", line);
+ }
+
+ /* Don't need the inserted null terminator any more. */
+ *cp = savec;
+
+ /*
+ * If it is a special type and not .PATH, it's the only target we
+ * allow on this line...
+ */
+ if (specType != Not && specType != ExPath) {
+ Boolean warning = FALSE;
+
+ while (*cp && (ParseIsEscaped(lstart, cp) ||
+ ((*cp != '!') && (*cp != ':')))) {
+ if (ParseIsEscaped(lstart, cp) ||
+ (*cp != ' ' && *cp != '\t')) {
+ warning = TRUE;
+ }
+ cp++;
+ }
+ if (warning) {
+ Parse_Error(PARSE_WARNING, "Extra target ignored");
+ }
+ } else {
+ while (*cp && isspace ((unsigned char)*cp)) {
+ cp++;
+ }
+ }
+ line = cp;
+ } while (*line && (ParseIsEscaped(lstart, line) ||
+ ((*line != '!') && (*line != ':'))));
+
+ /*
+ * Don't need the list of target names anymore...
+ */
+ Lst_Destroy(curTargs, NULL);
+ curTargs = NULL;
+
+ if (!Lst_IsEmpty(targets)) {
+ switch(specType) {
+ default:
+ Parse_Error(PARSE_WARNING, "Special and mundane targets don't mix. Mundane ones ignored");
+ break;
+ case Default:
+ case Stale:
+ case Begin:
+ case End:
+ case dotError:
+ case Interrupt:
+ /*
+ * These four create nodes on which to hang commands, so
+ * targets shouldn't be empty...
+ */
+ case Not:
+ /*
+ * Nothing special here -- targets can be empty if it wants.
+ */
+ break;
+ }
+ }
+
+ /*
+ * Have now parsed all the target names. Must parse the operator next. The
+ * result is left in op .
+ */
+ if (*cp == '!') {
+ op = OP_FORCE;
+ } else if (*cp == ':') {
+ if (cp[1] == ':') {
+ op = OP_DOUBLEDEP;
+ cp++;
+ } else {
+ op = OP_DEPENDS;
+ }
+ } else {
+ Parse_Error(PARSE_FATAL, lstart[0] == '.' ? "Unknown directive"
+ : "Missing dependency operator");
+ goto out;
+ }
+
+ /* Advance beyond the operator */
+ cp++;
+
+ /*
+ * Apply the operator to the target. This is how we remember which
+ * operator a target was defined with. It fails if the operator
+ * used isn't consistent across all references.
+ */
+ Lst_ForEach(targets, ParseDoOp, &op);
+
+ /*
+ * Onward to the sources.
+ *
+ * LINE will now point to the first source word, if any, or the
+ * end of the string if not.
+ */
+ while (*cp && isspace ((unsigned char)*cp)) {
+ cp++;
+ }
+ line = cp;
+
+ /*
+ * Several special targets take different actions if present with no
+ * sources:
+ * a .SUFFIXES line with no sources clears out all old suffixes
+ * a .PRECIOUS line makes all targets precious
+ * a .IGNORE line ignores errors for all targets
+ * a .SILENT line creates silence when making all targets
+ * a .PATH removes all directories from the search path(s).
+ */
+ if (!*line) {
+ switch (specType) {
+ case Suffixes:
+ Suff_ClearSuffixes();
+ break;
+ case Precious:
+ allPrecious = TRUE;
+ break;
+ case Ignore:
+ ignoreErrors = TRUE;
+ break;
+ case Silent:
+ beSilent = TRUE;
+ break;
+ case ExPath:
+ Lst_ForEach(paths, ParseClearPath, NULL);
+ Dir_SetPATH();
+ break;
+#ifdef POSIX
+ case Posix:
+ Var_Set("%POSIX", "1003.2", VAR_GLOBAL, 0);
+ break;
+#endif
+ default:
+ break;
+ }
+ } else if (specType == MFlags) {
+ /*
+ * Call on functions in main.c to deal with these arguments and
+ * set the initial character to a null-character so the loop to
+ * get sources won't get anything
+ */
+ Main_ParseArgLine(line);
+ *line = '\0';
+ } else if (specType == ExShell) {
+ if (Job_ParseShell(line) != SUCCESS) {
+ Parse_Error(PARSE_FATAL, "improper shell specification");
+ goto out;
+ }
+ *line = '\0';
+ } else if ((specType == NotParallel) || (specType == SingleShell) ||
+ (specType == DeleteOnError)) {
+ *line = '\0';
+ }
+
+ /*
+ * NOW GO FOR THE SOURCES
+ */
+ if ((specType == Suffixes) || (specType == ExPath) ||
+ (specType == Includes) || (specType == Libs) ||
+ (specType == Null) || (specType == ExObjdir))
+ {
+ while (*line) {
+ /*
+ * If the target was one that doesn't take files as its sources
+ * but takes something like suffixes, we take each
+ * space-separated word on the line as a something and deal
+ * with it accordingly.
+ *
+ * If the target was .SUFFIXES, we take each source as a
+ * suffix and add it to the list of suffixes maintained by the
+ * Suff module.
+ *
+ * If the target was a .PATH, we add the source as a directory
+ * to search on the search path.
+ *
+ * If it was .INCLUDES, the source is taken to be the suffix of
+ * files which will be #included and whose search path should
+ * be present in the .INCLUDES variable.
+ *
+ * If it was .LIBS, the source is taken to be the suffix of
+ * files which are considered libraries and whose search path
+ * should be present in the .LIBS variable.
+ *
+ * If it was .NULL, the source is the suffix to use when a file
+ * has no valid suffix.
+ *
+ * If it was .OBJDIR, the source is a new definition for .OBJDIR,
+ * and will cause make to do a new chdir to that path.
+ */
+ while (*cp && !isspace ((unsigned char)*cp)) {
+ cp++;
+ }
+ savec = *cp;
+ *cp = '\0';
+ switch (specType) {
+ case Suffixes:
+ Suff_AddSuffix(line, &mainNode);
+ break;
+ case ExPath:
+ Lst_ForEach(paths, ParseAddDir, line);
+ break;
+ case Includes:
+ Suff_AddInclude(line);
+ break;
+ case Libs:
+ Suff_AddLib(line);
+ break;
+ case Null:
+ Suff_SetNull(line);
+ break;
+ case ExObjdir:
+ Main_SetObjdir("%s", line);
+ break;
+ default:
+ break;
+ }
+ *cp = savec;
+ if (savec != '\0') {
+ cp++;
+ }
+ while (*cp && isspace ((unsigned char)*cp)) {
+ cp++;
+ }
+ line = cp;
+ }
+ if (paths) {
+ Lst_Destroy(paths, NULL);
+ paths = NULL;
+ }
+ if (specType == ExPath)
+ Dir_SetPATH();
+ } else {
+ assert(paths == NULL);
+ while (*line) {
+ /*
+ * The targets take real sources, so we must beware of archive
+ * specifications (i.e. things with left parentheses in them)
+ * and handle them accordingly.
+ */
+ for (; *cp && !isspace ((unsigned char)*cp); cp++) {
+ if ((*cp == LPAREN) && (cp > line) && (cp[-1] != '$')) {
+ /*
+ * Only stop for a left parenthesis if it isn't at the
+ * start of a word (that'll be for variable changes
+ * later) and isn't preceded by a dollar sign (a dynamic
+ * source).
+ */
+ break;
+ }
+ }
+
+ if (*cp == LPAREN) {
+ sources = Lst_Init(FALSE);
+ if (Arch_ParseArchive(&line, sources, VAR_CMD) != SUCCESS) {
+ Parse_Error(PARSE_FATAL,
+ "Error in source archive spec \"%s\"", line);
+ goto out;
+ }
+
+ while (!Lst_IsEmpty (sources)) {
+ gn = (GNode *)Lst_DeQueue(sources);
+ ParseDoSrc(tOp, gn->name);
+ }
+ Lst_Destroy(sources, NULL);
+ cp = line;
+ } else {
+ if (*cp) {
+ *cp = '\0';
+ cp += 1;
+ }
+
+ ParseDoSrc(tOp, line);
+ }
+ while (*cp && isspace ((unsigned char)*cp)) {
+ cp++;
+ }
+ line = cp;
+ }
+ }
+
+ if (mainNode == NULL) {
+ /*
+ * If we have yet to decide on a main target to make, in the
+ * absence of any user input, we want the first target on
+ * the first dependency line that is actually a real target
+ * (i.e. isn't a .USE or .EXEC rule) to be made.
+ */
+ Lst_ForEach(targets, ParseFindMain, NULL);
+ }
+
+out:
+ assert(paths == NULL);
+ if (curTargs)
+ Lst_Destroy(curTargs, NULL);
+}
+
+/*-
+ *---------------------------------------------------------------------
+ * Parse_IsVar --
+ * Return TRUE if the passed line is a variable assignment. A variable
+ * assignment consists of a single word followed by optional whitespace
+ * followed by either a += or an = operator.
+ * This function is used both by the Parse_File function and main when
+ * parsing the command-line arguments.
+ *
+ * Input:
+ * line the line to check
+ *
+ * Results:
+ * TRUE if it is. FALSE if it ain't
+ *
+ * Side Effects:
+ * none
+ *---------------------------------------------------------------------
+ */
+Boolean
+Parse_IsVar(char *line)
+{
+ Boolean wasSpace = FALSE; /* set TRUE if found a space */
+ char ch;
+ int level = 0;
+#define ISEQOPERATOR(c) \
+ (((c) == '+') || ((c) == ':') || ((c) == '?') || ((c) == '!'))
+
+ /*
+ * Skip to variable name
+ */
+ for (;(*line == ' ') || (*line == '\t'); line++)
+ continue;
+
+ /* Scan for one of the assignment operators outside a variable expansion */
+ while ((ch = *line++) != 0) {
+ if (ch == '(' || ch == '{') {
+ level++;
+ continue;
+ }
+ if (ch == ')' || ch == '}') {
+ level--;
+ continue;
+ }
+ if (level != 0)
+ continue;
+ while (ch == ' ' || ch == '\t') {
+ ch = *line++;
+ wasSpace = TRUE;
+ }
+#ifdef SUNSHCMD
+ if (ch == ':' && strncmp(line, "sh", 2) == 0) {
+ line += 2;
+ continue;
+ }
+#endif
+ if (ch == '=')
+ return TRUE;
+ if (*line == '=' && ISEQOPERATOR(ch))
+ return TRUE;
+ if (wasSpace)
+ return FALSE;
+ }
+
+ return FALSE;
+}
+
+/*-
+ *---------------------------------------------------------------------
+ * Parse_DoVar --
+ * Take the variable assignment in the passed line and do it in the
+ * global context.
+ *
+ * Note: There is a lexical ambiguity with assignment modifier characters
+ * in variable names. This routine interprets the character before the =
+ * as a modifier. Therefore, an assignment like
+ * C++=/usr/bin/CC
+ * is interpreted as "C+ +=" instead of "C++ =".
+ *
+ * Input:
+ * line a line guaranteed to be a variable assignment.
+ * This reduces error checks
+ * ctxt Context in which to do the assignment
+ *
+ * Results:
+ * none
+ *
+ * Side Effects:
+ * the variable structure of the given variable name is altered in the
+ * global context.
+ *---------------------------------------------------------------------
+ */
+void
+Parse_DoVar(char *line, GNode *ctxt)
+{
+ char *cp; /* pointer into line */
+ enum {
+ VAR_SUBST, VAR_APPEND, VAR_SHELL, VAR_NORMAL
+ } type; /* Type of assignment */
+ char *opc; /* ptr to operator character to
+ * null-terminate the variable name */
+ Boolean freeCp = FALSE; /* TRUE if cp needs to be freed,
+ * i.e. if any variable expansion was
+ * performed */
+ int depth;
+
+ /*
+ * Skip to variable name
+ */
+ while ((*line == ' ') || (*line == '\t')) {
+ line++;
+ }
+
+ /*
+ * Skip to operator character, nulling out whitespace as we go
+ * XXX Rather than counting () and {} we should look for $ and
+ * then expand the variable.
+ */
+ for (depth = 0, cp = line + 1; depth > 0 || *cp != '='; cp++) {
+ if (*cp == '(' || *cp == '{') {
+ depth++;
+ continue;
+ }
+ if (*cp == ')' || *cp == '}') {
+ depth--;
+ continue;
+ }
+ if (depth == 0 && isspace ((unsigned char)*cp)) {
+ *cp = '\0';
+ }
+ }
+ opc = cp-1; /* operator is the previous character */
+ *cp++ = '\0'; /* nuke the = */
+
+ /*
+ * Check operator type
+ */
+ switch (*opc) {
+ case '+':
+ type = VAR_APPEND;
+ *opc = '\0';
+ break;
+
+ case '?':
+ /*
+ * If the variable already has a value, we don't do anything.
+ */
+ *opc = '\0';
+ if (Var_Exists(line, ctxt)) {
+ return;
+ } else {
+ type = VAR_NORMAL;
+ }
+ break;
+
+ case ':':
+ type = VAR_SUBST;
+ *opc = '\0';
+ break;
+
+ case '!':
+ type = VAR_SHELL;
+ *opc = '\0';
+ break;
+
+ default:
+#ifdef SUNSHCMD
+ while (opc > line && *opc != ':')
+ opc--;
+
+ if (strncmp(opc, ":sh", 3) == 0) {
+ type = VAR_SHELL;
+ *opc = '\0';
+ break;
+ }
+#endif
+ type = VAR_NORMAL;
+ break;
+ }
+
+ while (isspace ((unsigned char)*cp)) {
+ cp++;
+ }
+
+ if (type == VAR_APPEND) {
+ Var_Append(line, cp, ctxt);
+ } else if (type == VAR_SUBST) {
+ /*
+ * Allow variables in the old value to be undefined, but leave their
+ * invocation alone -- this is done by forcing oldVars to be false.
+ * XXX: This can cause recursive variables, but that's not hard to do,
+ * and this allows someone to do something like
+ *
+ * CFLAGS = $(.INCLUDES)
+ * CFLAGS := -I.. $(CFLAGS)
+ *
+ * And not get an error.
+ */
+ Boolean oldOldVars = oldVars;
+
+ oldVars = FALSE;
+
+ /*
+ * make sure that we set the variable the first time to nothing
+ * so that it gets substituted!
+ */
+ if (!Var_Exists(line, ctxt))
+ Var_Set(line, "", ctxt, 0);
+
+ cp = Var_Subst(NULL, cp, ctxt, VARF_WANTRES|VARF_ASSIGN);
+ oldVars = oldOldVars;
+ freeCp = TRUE;
+
+ Var_Set(line, cp, ctxt, 0);
+ } else if (type == VAR_SHELL) {
+ char *res;
+ const char *error;
+
+ if (strchr(cp, '$') != NULL) {
+ /*
+ * There's a dollar sign in the command, so perform variable
+ * expansion on the whole thing. The resulting string will need
+ * freeing when we're done, so set freeCmd to TRUE.
+ */
+ cp = Var_Subst(NULL, cp, VAR_CMD, VARF_UNDEFERR|VARF_WANTRES);
+ freeCp = TRUE;
+ }
+
+ res = Cmd_Exec(cp, &error);
+ Var_Set(line, res, ctxt, 0);
+ free(res);
+
+ if (error)
+ Parse_Error(PARSE_WARNING, error, cp);
+ } else {
+ /*
+ * Normal assignment -- just do it.
+ */
+ Var_Set(line, cp, ctxt, 0);
+ }
+ if (strcmp(line, MAKEOVERRIDES) == 0)
+ Main_ExportMAKEFLAGS(FALSE); /* re-export MAKEFLAGS */
+ else if (strcmp(line, ".CURDIR") == 0) {
+ /*
+ * Somone is being (too?) clever...
+ * Let's pretend they know what they are doing and
+ * re-initialize the 'cur' Path.
+ */
+ Dir_InitCur(cp);
+ Dir_SetPATH();
+ } else if (strcmp(line, MAKE_JOB_PREFIX) == 0) {
+ Job_SetPrefix();
+ } else if (strcmp(line, MAKE_EXPORTED) == 0) {
+ Var_Export(cp, 0);
+ }
+ if (freeCp)
+ free(cp);
+}
+
+
+/*
+ * ParseMaybeSubMake --
+ * Scan the command string to see if it a possible submake node
+ * Input:
+ * cmd the command to scan
+ * Results:
+ * TRUE if the command is possibly a submake, FALSE if not.
+ */
+static Boolean
+ParseMaybeSubMake(const char *cmd)
+{
+ size_t i;
+ static struct {
+ const char *name;
+ size_t len;
+ } vals[] = {
+#define MKV(A) { A, sizeof(A) - 1 }
+ MKV("${MAKE}"),
+ MKV("${.MAKE}"),
+ MKV("$(MAKE)"),
+ MKV("$(.MAKE)"),
+ MKV("make"),
+ };
+ for (i = 0; i < sizeof(vals)/sizeof(vals[0]); i++) {
+ char *ptr;
+ if ((ptr = strstr(cmd, vals[i].name)) == NULL)
+ continue;
+ if ((ptr == cmd || !isalnum((unsigned char)ptr[-1]))
+ && !isalnum((unsigned char)ptr[vals[i].len]))
+ return TRUE;
+ }
+ return FALSE;
+}
+
+/*-
+ * ParseAddCmd --
+ * Lst_ForEach function to add a command line to all targets
+ *
+ * Input:
+ * gnp the node to which the command is to be added
+ * cmd the command to add
+ *
+ * Results:
+ * Always 0
+ *
+ * Side Effects:
+ * A new element is added to the commands list of the node,
+ * and the node can be marked as a submake node if the command is
+ * determined to be that.
+ */
+static int
+ParseAddCmd(void *gnp, void *cmd)
+{
+ GNode *gn = (GNode *)gnp;
+
+ /* Add to last (ie current) cohort for :: targets */
+ if ((gn->type & OP_DOUBLEDEP) && !Lst_IsEmpty (gn->cohorts))
+ gn = (GNode *)Lst_Datum(Lst_Last(gn->cohorts));
+
+ /* if target already supplied, ignore commands */
+ if (!(gn->type & OP_HAS_COMMANDS)) {
+ (void)Lst_AtEnd(gn->commands, cmd);
+ if (ParseMaybeSubMake(cmd))
+ gn->type |= OP_SUBMAKE;
+ ParseMark(gn);
+ } else {
+#ifdef notyet
+ /* XXX: We cannot do this until we fix the tree */
+ (void)Lst_AtEnd(gn->commands, cmd);
+ Parse_Error(PARSE_WARNING,
+ "overriding commands for target \"%s\"; "
+ "previous commands defined at %s: %d ignored",
+ gn->name, gn->fname, gn->lineno);
+#else
+ Parse_Error(PARSE_WARNING,
+ "duplicate script for target \"%s\" ignored",
+ gn->name);
+ ParseErrorInternal(gn->fname, gn->lineno, PARSE_WARNING,
+ "using previous script for \"%s\" defined here",
+ gn->name);
+#endif
+ }
+ return(0);
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * ParseHasCommands --
+ * Callback procedure for Parse_File when destroying the list of
+ * targets on the last dependency line. Marks a target as already
+ * having commands if it does, to keep from having shell commands
+ * on multiple dependency lines.
+ *
+ * Input:
+ * gnp Node to examine
+ *
+ * Results:
+ * None
+ *
+ * Side Effects:
+ * OP_HAS_COMMANDS may be set for the target.
+ *
+ *-----------------------------------------------------------------------
+ */
+static void
+ParseHasCommands(void *gnp)
+{
+ GNode *gn = (GNode *)gnp;
+ if (!Lst_IsEmpty(gn->commands)) {
+ gn->type |= OP_HAS_COMMANDS;
+ }
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * Parse_AddIncludeDir --
+ * Add a directory to the path searched for included makefiles
+ * bracketed by double-quotes. Used by functions in main.c
+ *
+ * Input:
+ * dir The name of the directory to add
+ *
+ * Results:
+ * None.
+ *
+ * Side Effects:
+ * The directory is appended to the list.
+ *
+ *-----------------------------------------------------------------------
+ */
+void
+Parse_AddIncludeDir(char *dir)
+{
+ (void)Dir_AddDir(parseIncPath, dir);
+}
+
+/*-
+ *---------------------------------------------------------------------
+ * ParseDoInclude --
+ * Push to another file.
+ *
+ * The input is the line minus the `.'. A file spec is a string
+ * enclosed in <> or "". The former is looked for only in sysIncPath.
+ * The latter in . and the directories specified by -I command line
+ * options
+ *
+ * Results:
+ * None
+ *
+ * Side Effects:
+ * A structure is added to the includes Lst and readProc, lineno,
+ * fname and curFILE are altered for the new file
+ *---------------------------------------------------------------------
+ */
+
+static void
+Parse_include_file(char *file, Boolean isSystem, Boolean depinc, int silent)
+{
+ struct loadedfile *lf;
+ char *fullname; /* full pathname of file */
+ char *newName;
+ char *prefEnd, *incdir;
+ int fd;
+ int i;
+
+ /*
+ * Now we know the file's name and its search path, we attempt to
+ * find the durn thing. A return of NULL indicates the file don't
+ * exist.
+ */
+ fullname = file[0] == '/' ? bmake_strdup(file) : NULL;
+
+ if (fullname == NULL && !isSystem) {
+ /*
+ * Include files contained in double-quotes are first searched for
+ * relative to the including file's location. We don't want to
+ * cd there, of course, so we just tack on the old file's
+ * leading path components and call Dir_FindFile to see if
+ * we can locate the beast.
+ */
+
+ incdir = bmake_strdup(curFile->fname);
+ prefEnd = strrchr(incdir, '/');
+ if (prefEnd != NULL) {
+ *prefEnd = '\0';
+ /* Now do lexical processing of leading "../" on the filename */
+ for (i = 0; strncmp(file + i, "../", 3) == 0; i += 3) {
+ prefEnd = strrchr(incdir + 1, '/');
+ if (prefEnd == NULL || strcmp(prefEnd, "/..") == 0)
+ break;
+ *prefEnd = '\0';
+ }
+ newName = str_concat(incdir, file + i, STR_ADDSLASH);
+ fullname = Dir_FindFile(newName, parseIncPath);
+ if (fullname == NULL)
+ fullname = Dir_FindFile(newName, dirSearchPath);
+ free(newName);
+ }
+ free(incdir);
+
+ if (fullname == NULL) {
+ /*
+ * Makefile wasn't found in same directory as included makefile.
+ * Search for it first on the -I search path,
+ * then on the .PATH search path, if not found in a -I directory.
+ * If we have a suffix specific path we should use that.
+ */
+ char *suff;
+ Lst suffPath = NULL;
+
+ if ((suff = strrchr(file, '.'))) {
+ suffPath = Suff_GetPath(suff);
+ if (suffPath != NULL) {
+ fullname = Dir_FindFile(file, suffPath);
+ }
+ }
+ if (fullname == NULL) {
+ fullname = Dir_FindFile(file, parseIncPath);
+ if (fullname == NULL) {
+ fullname = Dir_FindFile(file, dirSearchPath);
+ }
+ }
+ }
+ }
+
+ /* Looking for a system file or file still not found */
+ if (fullname == NULL) {
+ /*
+ * Look for it on the system path
+ */
+ fullname = Dir_FindFile(file,
+ Lst_IsEmpty(sysIncPath) ? defIncPath : sysIncPath);
+ }
+
+ if (fullname == NULL) {
+ if (!silent)
+ Parse_Error(PARSE_FATAL, "Could not find %s", file);
+ return;
+ }
+
+ /* Actually open the file... */
+ fd = open(fullname, O_RDONLY);
+ if (fd == -1) {
+ if (!silent)
+ Parse_Error(PARSE_FATAL, "Cannot open %s", fullname);
+ free(fullname);
+ return;
+ }
+
+ /* load it */
+ lf = loadfile(fullname, fd);
+
+ ParseSetIncludedFile();
+ /* Start reading from this file next */
+ Parse_SetInput(fullname, 0, -1, loadedfile_nextbuf, lf);
+ curFile->lf = lf;
+ if (depinc)
+ doing_depend = depinc; /* only turn it on */
+}
+
+static void
+ParseDoInclude(char *line)
+{
+ char endc; /* the character which ends the file spec */
+ char *cp; /* current position in file spec */
+ int silent = (*line != 'i') ? 1 : 0;
+ char *file = &line[7 + silent];
+
+ /* Skip to delimiter character so we know where to look */
+ while (*file == ' ' || *file == '\t')
+ file++;
+
+ if (*file != '"' && *file != '<') {
+ Parse_Error(PARSE_FATAL,
+ ".include filename must be delimited by '\"' or '<'");
+ return;
+ }
+
+ /*
+ * Set the search path on which to find the include file based on the
+ * characters which bracket its name. Angle-brackets imply it's
+ * a system Makefile while double-quotes imply it's a user makefile
+ */
+ if (*file == '<') {
+ endc = '>';
+ } else {
+ endc = '"';
+ }
+
+ /* Skip to matching delimiter */
+ for (cp = ++file; *cp && *cp != endc; cp++)
+ continue;
+
+ if (*cp != endc) {
+ Parse_Error(PARSE_FATAL,
+ "Unclosed %cinclude filename. '%c' expected",
+ '.', endc);
+ return;
+ }
+ *cp = '\0';
+
+ /*
+ * Substitute for any variables in the file name before trying to
+ * find the thing.
+ */
+ file = Var_Subst(NULL, file, VAR_CMD, VARF_WANTRES);
+
+ Parse_include_file(file, endc == '>', (*line == 'd'), silent);
+ free(file);
+}
+
+
+/*-
+ *---------------------------------------------------------------------
+ * ParseSetIncludedFile --
+ * Set the .INCLUDEDFROMFILE variable to the contents of .PARSEFILE
+ * and the .INCLUDEDFROMDIR variable to the contents of .PARSEDIR
+ *
+ * Results:
+ * None
+ *
+ * Side Effects:
+ * The .INCLUDEDFROMFILE variable is overwritten by the contents
+ * of .PARSEFILE and the .INCLUDEDFROMDIR variable is overwriten
+ * by the contents of .PARSEDIR
+ *---------------------------------------------------------------------
+ */
+static void
+ParseSetIncludedFile(void)
+{
+ char *pf, *fp = NULL;
+ char *pd, *dp = NULL;
+
+ pf = Var_Value(".PARSEFILE", VAR_GLOBAL, &fp);
+ Var_Set(".INCLUDEDFROMFILE", pf, VAR_GLOBAL, 0);
+ pd = Var_Value(".PARSEDIR", VAR_GLOBAL, &dp);
+ Var_Set(".INCLUDEDFROMDIR", pd, VAR_GLOBAL, 0);
+
+ if (DEBUG(PARSE))
+ fprintf(debug_file, "%s: ${.INCLUDEDFROMDIR} = `%s' "
+ "${.INCLUDEDFROMFILE} = `%s'\n", __func__, pd, pf);
+
+ free(fp);
+ free(dp);
+}
+/*-
+ *---------------------------------------------------------------------
+ * ParseSetParseFile --
+ * Set the .PARSEDIR and .PARSEFILE variables to the dirname and
+ * basename of the given filename
+ *
+ * Results:
+ * None
+ *
+ * Side Effects:
+ * The .PARSEDIR and .PARSEFILE variables are overwritten by the
+ * dirname and basename of the given filename.
+ *---------------------------------------------------------------------
+ */
+static void
+ParseSetParseFile(const char *filename)
+{
+ char *slash, *dirname;
+ const char *pd, *pf;
+ int len;
+
+ slash = strrchr(filename, '/');
+ if (slash == NULL) {
+ Var_Set(".PARSEDIR", pd = curdir, VAR_GLOBAL, 0);
+ Var_Set(".PARSEFILE", pf = filename, VAR_GLOBAL, 0);
+ dirname= NULL;
+ } else {
+ len = slash - filename;
+ dirname = bmake_malloc(len + 1);
+ memcpy(dirname, filename, len);
+ dirname[len] = '\0';
+ Var_Set(".PARSEDIR", pd = dirname, VAR_GLOBAL, 0);
+ Var_Set(".PARSEFILE", pf = slash + 1, VAR_GLOBAL, 0);
+ }
+ if (DEBUG(PARSE))
+ fprintf(debug_file, "%s: ${.PARSEDIR} = `%s' ${.PARSEFILE} = `%s'\n",
+ __func__, pd, pf);
+ free(dirname);
+}
+
+/*
+ * Track the makefiles we read - so makefiles can
+ * set dependencies on them.
+ * Avoid adding anything more than once.
+ */
+
+static void
+ParseTrackInput(const char *name)
+{
+ char *old;
+ char *ep;
+ char *fp = NULL;
+ size_t name_len = strlen(name);
+
+ old = Var_Value(MAKE_MAKEFILES, VAR_GLOBAL, &fp);
+ if (old) {
+ ep = old + strlen(old) - name_len;
+ /* does it contain name? */
+ for (; old != NULL; old = strchr(old, ' ')) {
+ if (*old == ' ')
+ old++;
+ if (old >= ep)
+ break; /* cannot contain name */
+ if (memcmp(old, name, name_len) == 0
+ && (old[name_len] == 0 || old[name_len] == ' '))
+ goto cleanup;
+ }
+ }
+ Var_Append (MAKE_MAKEFILES, name, VAR_GLOBAL);
+ cleanup:
+ if (fp) {
+ free(fp);
+ }
+}
+
+
+/*-
+ *---------------------------------------------------------------------
+ * Parse_setInput --
+ * Start Parsing from the given source
+ *
+ * Results:
+ * None
+ *
+ * Side Effects:
+ * A structure is added to the includes Lst and readProc, lineno,
+ * fname and curFile are altered for the new file
+ *---------------------------------------------------------------------
+ */
+void
+Parse_SetInput(const char *name, int line, int fd,
+ char *(*nextbuf)(void *, size_t *), void *arg)
+{
+ char *buf;
+ size_t len;
+
+ if (name == NULL)
+ name = curFile->fname;
+ else
+ ParseTrackInput(name);
+
+ if (DEBUG(PARSE))
+ fprintf(debug_file, "%s: file %s, line %d, fd %d, nextbuf %p, arg %p\n",
+ __func__, name, line, fd, nextbuf, arg);
+
+ if (fd == -1 && nextbuf == NULL)
+ /* sanity */
+ return;
+
+ if (curFile != NULL)
+ /* Save exiting file info */
+ Lst_AtFront(includes, curFile);
+
+ /* Allocate and fill in new structure */
+ curFile = bmake_malloc(sizeof *curFile);
+
+ /*
+ * Once the previous state has been saved, we can get down to reading
+ * the new file. We set up the name of the file to be the absolute
+ * name of the include file so error messages refer to the right
+ * place.
+ */
+ curFile->fname = bmake_strdup(name);
+ curFile->lineno = line;
+ curFile->first_lineno = line;
+ curFile->nextbuf = nextbuf;
+ curFile->nextbuf_arg = arg;
+ curFile->lf = NULL;
+ curFile->depending = doing_depend; /* restore this on EOF */
+
+ assert(nextbuf != NULL);
+
+ /* Get first block of input data */
+ buf = curFile->nextbuf(curFile->nextbuf_arg, &len);
+ if (buf == NULL) {
+ /* Was all a waste of time ... */
+ if (curFile->fname)
+ free(curFile->fname);
+ free(curFile);
+ return;
+ }
+ curFile->P_str = buf;
+ curFile->P_ptr = buf;
+ curFile->P_end = buf+len;
+
+ curFile->cond_depth = Cond_save_depth();
+ ParseSetParseFile(name);
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * IsInclude --
+ * Check if the line is an include directive
+ *
+ * Results:
+ * TRUE if it is.
+ *
+ * Side Effects:
+ * None
+ *
+ *-----------------------------------------------------------------------
+ */
+static Boolean
+IsInclude(const char *line, Boolean sysv)
+{
+ static const char inc[] = "include";
+ static const size_t inclen = sizeof(inc) - 1;
+
+ // 'd' is not valid for sysv
+ int o = strchr(&("ds-"[sysv]), *line) != NULL;
+
+ if (strncmp(line + o, inc, inclen) != 0)
+ return FALSE;
+
+ // Space is not mandatory for BSD .include
+ return !sysv || isspace((unsigned char)line[inclen + o]);
+}
+
+
+#ifdef SYSVINCLUDE
+/*-
+ *-----------------------------------------------------------------------
+ * IsSysVInclude --
+ * Check if the line is a SYSV include directive
+ *
+ * Results:
+ * TRUE if it is.
+ *
+ * Side Effects:
+ * None
+ *
+ *-----------------------------------------------------------------------
+ */
+static Boolean
+IsSysVInclude(const char *line)
+{
+ const char *p;
+
+ if (!IsInclude(line, TRUE))
+ return FALSE;
+
+ /* Avoid interpeting a dependency line as an include */
+ for (p = line; (p = strchr(p, ':')) != NULL;) {
+ if (*++p == '\0') {
+ /* end of line -> dependency */
+ return FALSE;
+ }
+ if (*p == ':' || isspace((unsigned char)*p)) {
+ /* :: operator or ': ' -> dependency */
+ return FALSE;
+ }
+ }
+ return TRUE;
+}
+
+/*-
+ *---------------------------------------------------------------------
+ * ParseTraditionalInclude --
+ * Push to another file.
+ *
+ * The input is the current line. The file name(s) are
+ * following the "include".
+ *
+ * Results:
+ * None
+ *
+ * Side Effects:
+ * A structure is added to the includes Lst and readProc, lineno,
+ * fname and curFILE are altered for the new file
+ *---------------------------------------------------------------------
+ */
+static void
+ParseTraditionalInclude(char *line)
+{
+ char *cp; /* current position in file spec */
+ int done = 0;
+ int silent = (line[0] != 'i') ? 1 : 0;
+ char *file = &line[silent + 7];
+ char *all_files;
+
+ if (DEBUG(PARSE)) {
+ fprintf(debug_file, "%s: %s\n", __func__, file);
+ }
+
+ /*
+ * Skip over whitespace
+ */
+ while (isspace((unsigned char)*file))
+ file++;
+
+ /*
+ * Substitute for any variables in the file name before trying to
+ * find the thing.
+ */
+ all_files = Var_Subst(NULL, file, VAR_CMD, VARF_WANTRES);
+
+ if (*file == '\0') {
+ Parse_Error(PARSE_FATAL,
+ "Filename missing from \"include\"");
+ goto out;
+ }
+
+ for (file = all_files; !done; file = cp + 1) {
+ /* Skip to end of line or next whitespace */
+ for (cp = file; *cp && !isspace((unsigned char) *cp); cp++)
+ continue;
+
+ if (*cp)
+ *cp = '\0';
+ else
+ done = 1;
+
+ Parse_include_file(file, FALSE, FALSE, silent);
+ }
+out:
+ free(all_files);
+}
+#endif
+
+#ifdef GMAKEEXPORT
+/*-
+ *---------------------------------------------------------------------
+ * ParseGmakeExport --
+ * Parse export <variable>=<value>
+ *
+ * And set the environment with it.
+ *
+ * Results:
+ * None
+ *
+ * Side Effects:
+ * None
+ *---------------------------------------------------------------------
+ */
+static void
+ParseGmakeExport(char *line)
+{
+ char *variable = &line[6];
+ char *value;
+
+ if (DEBUG(PARSE)) {
+ fprintf(debug_file, "%s: %s\n", __func__, variable);
+ }
+
+ /*
+ * Skip over whitespace
+ */
+ while (isspace((unsigned char)*variable))
+ variable++;
+
+ for (value = variable; *value && *value != '='; value++)
+ continue;
+
+ if (*value != '=') {
+ Parse_Error(PARSE_FATAL,
+ "Variable/Value missing from \"export\"");
+ return;
+ }
+ *value++ = '\0'; /* terminate variable */
+
+ /*
+ * Expand the value before putting it in the environment.
+ */
+ value = Var_Subst(NULL, value, VAR_CMD, VARF_WANTRES);
+ setenv(variable, value, 1);
+ free(value);
+}
+#endif
+
+/*-
+ *---------------------------------------------------------------------
+ * ParseEOF --
+ * Called when EOF is reached in the current file. If we were reading
+ * an include file, the includes stack is popped and things set up
+ * to go back to reading the previous file at the previous location.
+ *
+ * Results:
+ * CONTINUE if there's more to do. DONE if not.
+ *
+ * Side Effects:
+ * The old curFILE, is closed. The includes list is shortened.
+ * lineno, curFILE, and fname are changed if CONTINUE is returned.
+ *---------------------------------------------------------------------
+ */
+static int
+ParseEOF(void)
+{
+ char *ptr;
+ size_t len;
+
+ assert(curFile->nextbuf != NULL);
+
+ doing_depend = curFile->depending; /* restore this */
+ /* get next input buffer, if any */
+ ptr = curFile->nextbuf(curFile->nextbuf_arg, &len);
+ curFile->P_ptr = ptr;
+ curFile->P_str = ptr;
+ curFile->P_end = ptr + len;
+ curFile->lineno = curFile->first_lineno;
+ if (ptr != NULL) {
+ /* Iterate again */
+ return CONTINUE;
+ }
+
+ /* Ensure the makefile (or loop) didn't have mismatched conditionals */
+ Cond_restore_depth(curFile->cond_depth);
+
+ if (curFile->lf != NULL) {
+ loadedfile_destroy(curFile->lf);
+ curFile->lf = NULL;
+ }
+
+ /* Dispose of curFile info */
+ /* Leak curFile->fname because all the gnodes have pointers to it */
+ free(curFile->P_str);
+ free(curFile);
+
+ curFile = Lst_DeQueue(includes);
+
+ if (curFile == NULL) {
+ /* We've run out of input */
+ Var_Delete(".PARSEDIR", VAR_GLOBAL);
+ Var_Delete(".PARSEFILE", VAR_GLOBAL);
+ Var_Delete(".INCLUDEDFROMDIR", VAR_GLOBAL);
+ Var_Delete(".INCLUDEDFROMFILE", VAR_GLOBAL);
+ return DONE;
+ }
+
+ if (DEBUG(PARSE))
+ fprintf(debug_file, "ParseEOF: returning to file %s, line %d\n",
+ curFile->fname, curFile->lineno);
+
+ /* Restore the PARSEDIR/PARSEFILE variables */
+ ParseSetParseFile(curFile->fname);
+ return (CONTINUE);
+}
+
+#define PARSE_RAW 1
+#define PARSE_SKIP 2
+
+static char *
+ParseGetLine(int flags, int *length)
+{
+ IFile *cf = curFile;
+ char *ptr;
+ char ch;
+ char *line;
+ char *line_end;
+ char *escaped;
+ char *comment;
+ char *tp;
+
+ /* Loop through blank lines and comment lines */
+ for (;;) {
+ cf->lineno++;
+ line = cf->P_ptr;
+ ptr = line;
+ line_end = line;
+ escaped = NULL;
+ comment = NULL;
+ for (;;) {
+ if (cf->P_end != NULL && ptr == cf->P_end) {
+ /* end of buffer */
+ ch = 0;
+ break;
+ }
+ ch = *ptr;
+ if (ch == 0 || (ch == '\\' && ptr[1] == 0)) {
+ if (cf->P_end == NULL)
+ /* End of string (aka for loop) data */
+ break;
+ /* see if there is more we can parse */
+ while (ptr++ < cf->P_end) {
+ if ((ch = *ptr) == '\n') {
+ if (ptr > line && ptr[-1] == '\\')
+ continue;
+ Parse_Error(PARSE_WARNING,
+ "Zero byte read from file, skipping rest of line.");
+ break;
+ }
+ }
+ if (cf->nextbuf != NULL) {
+ /*
+ * End of this buffer; return EOF and outer logic
+ * will get the next one. (eww)
+ */
+ break;
+ }
+ Parse_Error(PARSE_FATAL, "Zero byte read from file");
+ return NULL;
+ }
+
+ if (ch == '\\') {
+ /* Don't treat next character as special, remember first one */
+ if (escaped == NULL)
+ escaped = ptr;
+ if (ptr[1] == '\n')
+ cf->lineno++;
+ ptr += 2;
+ line_end = ptr;
+ continue;
+ }
+ if (ch == '#' && comment == NULL) {
+ /* Remember first '#' for comment stripping */
+ /* Unless previous char was '[', as in modifier :[#] */
+ if (!(ptr > line && ptr[-1] == '['))
+ comment = line_end;
+ }
+ ptr++;
+ if (ch == '\n')
+ break;
+ if (!isspace((unsigned char)ch))
+ /* We are not interested in trailing whitespace */
+ line_end = ptr;
+ }
+
+ /* Save next 'to be processed' location */
+ cf->P_ptr = ptr;
+
+ /* Check we have a non-comment, non-blank line */
+ if (line_end == line || comment == line) {
+ if (ch == 0)
+ /* At end of file */
+ return NULL;
+ /* Parse another line */
+ continue;
+ }
+
+ /* We now have a line of data */
+ *line_end = 0;
+
+ if (flags & PARSE_RAW) {
+ /* Leave '\' (etc) in line buffer (eg 'for' lines) */
+ *length = line_end - line;
+ return line;
+ }
+
+ if (flags & PARSE_SKIP) {
+ /* Completely ignore non-directives */
+ if (line[0] != '.')
+ continue;
+ /* We could do more of the .else/.elif/.endif checks here */
+ }
+ break;
+ }
+
+ /* Brutally ignore anything after a non-escaped '#' in non-commands */
+ if (comment != NULL && line[0] != '\t') {
+ line_end = comment;
+ *line_end = 0;
+ }
+
+ /* If we didn't see a '\\' then the in-situ data is fine */
+ if (escaped == NULL) {
+ *length = line_end - line;
+ return line;
+ }
+
+ /* Remove escapes from '\n' and '#' */
+ tp = ptr = escaped;
+ escaped = line;
+ for (; ; *tp++ = ch) {
+ ch = *ptr++;
+ if (ch != '\\') {
+ if (ch == 0)
+ break;
+ continue;
+ }
+
+ ch = *ptr++;
+ if (ch == 0) {
+ /* Delete '\\' at end of buffer */
+ tp--;
+ break;
+ }
+
+ if (ch == '#' && line[0] != '\t')
+ /* Delete '\\' from before '#' on non-command lines */
+ continue;
+
+ if (ch != '\n') {
+ /* Leave '\\' in buffer for later */
+ *tp++ = '\\';
+ /* Make sure we don't delete an escaped ' ' from the line end */
+ escaped = tp + 1;
+ continue;
+ }
+
+ /* Escaped '\n' replace following whitespace with a single ' ' */
+ while (ptr[0] == ' ' || ptr[0] == '\t')
+ ptr++;
+ ch = ' ';
+ }
+
+ /* Delete any trailing spaces - eg from empty continuations */
+ while (tp > escaped && isspace((unsigned char)tp[-1]))
+ tp--;
+
+ *tp = 0;
+ *length = tp - line;
+ return line;
+}
+
+/*-
+ *---------------------------------------------------------------------
+ * ParseReadLine --
+ * Read an entire line from the input file. Called only by Parse_File.
+ *
+ * Results:
+ * A line w/o its newline
+ *
+ * Side Effects:
+ * Only those associated with reading a character
+ *---------------------------------------------------------------------
+ */
+static char *
+ParseReadLine(void)
+{
+ char *line; /* Result */
+ int lineLength; /* Length of result */
+ int lineno; /* Saved line # */
+ int rval;
+
+ for (;;) {
+ line = ParseGetLine(0, &lineLength);
+ if (line == NULL)
+ return NULL;
+
+ if (line[0] != '.')
+ return line;
+
+ /*
+ * The line might be a conditional. Ask the conditional module
+ * about it and act accordingly
+ */
+ switch (Cond_Eval(line)) {
+ case COND_SKIP:
+ /* Skip to next conditional that evaluates to COND_PARSE. */
+ do {
+ line = ParseGetLine(PARSE_SKIP, &lineLength);
+ } while (line && Cond_Eval(line) != COND_PARSE);
+ if (line == NULL)
+ break;
+ continue;
+ case COND_PARSE:
+ continue;
+ case COND_INVALID: /* Not a conditional line */
+ /* Check for .for loops */
+ rval = For_Eval(line);
+ if (rval == 0)
+ /* Not a .for line */
+ break;
+ if (rval < 0)
+ /* Syntax error - error printed, ignore line */
+ continue;
+ /* Start of a .for loop */
+ lineno = curFile->lineno;
+ /* Accumulate loop lines until matching .endfor */
+ do {
+ line = ParseGetLine(PARSE_RAW, &lineLength);
+ if (line == NULL) {
+ Parse_Error(PARSE_FATAL,
+ "Unexpected end of file in for loop.");
+ break;
+ }
+ } while (For_Accum(line));
+ /* Stash each iteration as a new 'input file' */
+ For_Run(lineno);
+ /* Read next line from for-loop buffer */
+ continue;
+ }
+ return (line);
+ }
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * ParseFinishLine --
+ * Handle the end of a dependency group.
+ *
+ * Results:
+ * Nothing.
+ *
+ * Side Effects:
+ * inLine set FALSE. 'targets' list destroyed.
+ *
+ *-----------------------------------------------------------------------
+ */
+static void
+ParseFinishLine(void)
+{
+ if (inLine) {
+ Lst_ForEach(targets, Suff_EndTransform, NULL);
+ Lst_Destroy(targets, ParseHasCommands);
+ targets = NULL;
+ inLine = FALSE;
+ }
+}
+
+
+/*-
+ *---------------------------------------------------------------------
+ * Parse_File --
+ * Parse a file into its component parts, incorporating it into the
+ * current dependency graph. This is the main function and controls
+ * almost every other function in this module
+ *
+ * Input:
+ * name the name of the file being read
+ * fd Open file to makefile to parse
+ *
+ * Results:
+ * None
+ *
+ * Side Effects:
+ * closes fd.
+ * Loads. Nodes are added to the list of all targets, nodes and links
+ * are added to the dependency graph. etc. etc. etc.
+ *---------------------------------------------------------------------
+ */
+void
+Parse_File(const char *name, int fd)
+{
+ char *cp; /* pointer into the line */
+ char *line; /* the line we're working on */
+ struct loadedfile *lf;
+
+ lf = loadfile(name, fd);
+
+ inLine = FALSE;
+ fatals = 0;
+
+ if (name == NULL) {
+ name = "(stdin)";
+ }
+
+ Parse_SetInput(name, 0, -1, loadedfile_nextbuf, lf);
+ curFile->lf = lf;
+
+ do {
+ for (; (line = ParseReadLine()) != NULL; ) {
+ if (DEBUG(PARSE))
+ fprintf(debug_file, "ParseReadLine (%d): '%s'\n",
+ curFile->lineno, line);
+ if (*line == '.') {
+ /*
+ * Lines that begin with the special character may be
+ * include or undef directives.
+ * On the other hand they can be suffix rules (.c.o: ...)
+ * or just dependencies for filenames that start '.'.
+ */
+ for (cp = line + 1; isspace((unsigned char)*cp); cp++) {
+ continue;
+ }
+ if (IsInclude(cp, FALSE)) {
+ ParseDoInclude(cp);
+ continue;
+ }
+ if (strncmp(cp, "undef", 5) == 0) {
+ char *cp2;
+ for (cp += 5; isspace((unsigned char) *cp); cp++)
+ continue;
+ for (cp2 = cp; !isspace((unsigned char) *cp2) &&
+ (*cp2 != '\0'); cp2++)
+ continue;
+ *cp2 = '\0';
+ Var_Delete(cp, VAR_GLOBAL);
+ continue;
+ } else if (strncmp(cp, "export", 6) == 0) {
+ for (cp += 6; isspace((unsigned char) *cp); cp++)
+ continue;
+ Var_Export(cp, 1);
+ continue;
+ } else if (strncmp(cp, "unexport", 8) == 0) {
+ Var_UnExport(cp);
+ continue;
+ } else if (strncmp(cp, "info", 4) == 0 ||
+ strncmp(cp, "error", 5) == 0 ||
+ strncmp(cp, "warning", 7) == 0) {
+ if (ParseMessage(cp))
+ continue;
+ }
+ }
+
+ if (*line == '\t') {
+ /*
+ * If a line starts with a tab, it can only hope to be
+ * a creation command.
+ */
+ cp = line + 1;
+ shellCommand:
+ for (; isspace ((unsigned char)*cp); cp++) {
+ continue;
+ }
+ if (*cp) {
+ if (!inLine)
+ Parse_Error(PARSE_FATAL,
+ "Unassociated shell command \"%s\"",
+ cp);
+ /*
+ * So long as it's not a blank line and we're actually
+ * in a dependency spec, add the command to the list of
+ * commands of all targets in the dependency spec
+ */
+ if (targets) {
+ cp = bmake_strdup(cp);
+ Lst_ForEach(targets, ParseAddCmd, cp);
+#ifdef CLEANUP
+ Lst_AtEnd(targCmds, cp);
+#endif
+ }
+ }
+ continue;
+ }
+
+#ifdef SYSVINCLUDE
+ if (IsSysVInclude(line)) {
+ /*
+ * It's an S3/S5-style "include".
+ */
+ ParseTraditionalInclude(line);
+ continue;
+ }
+#endif
+#ifdef GMAKEEXPORT
+ if (strncmp(line, "export", 6) == 0 &&
+ isspace((unsigned char) line[6]) &&
+ strchr(line, ':') == NULL) {
+ /*
+ * It's a Gmake "export".
+ */
+ ParseGmakeExport(line);
+ continue;
+ }
+#endif
+ if (Parse_IsVar(line)) {
+ ParseFinishLine();
+ Parse_DoVar(line, VAR_GLOBAL);
+ continue;
+ }
+
+#ifndef POSIX
+ /*
+ * To make life easier on novices, if the line is indented we
+ * first make sure the line has a dependency operator in it.
+ * If it doesn't have an operator and we're in a dependency
+ * line's script, we assume it's actually a shell command
+ * and add it to the current list of targets.
+ */
+ cp = line;
+ if (isspace((unsigned char) line[0])) {
+ while ((*cp != '\0') && isspace((unsigned char) *cp))
+ cp++;
+ while (*cp && (ParseIsEscaped(line, cp) ||
+ (*cp != ':') && (*cp != '!'))) {
+ cp++;
+ }
+ if (*cp == '\0') {
+ if (inLine) {
+ Parse_Error(PARSE_WARNING,
+ "Shell command needs a leading tab");
+ goto shellCommand;
+ }
+ }
+ }
+#endif
+ ParseFinishLine();
+
+ /*
+ * For some reason - probably to make the parser impossible -
+ * a ';' can be used to separate commands from dependencies.
+ * Attempt to avoid ';' inside substitution patterns.
+ */
+ {
+ int level = 0;
+
+ for (cp = line; *cp != 0; cp++) {
+ if (*cp == '\\' && cp[1] != 0) {
+ cp++;
+ continue;
+ }
+ if (*cp == '$' &&
+ (cp[1] == '(' || cp[1] == '{')) {
+ level++;
+ continue;
+ }
+ if (level > 0) {
+ if (*cp == ')' || *cp == '}') {
+ level--;
+ continue;
+ }
+ } else if (*cp == ';') {
+ break;
+ }
+ }
+ }
+ if (*cp != 0)
+ /* Terminate the dependency list at the ';' */
+ *cp++ = 0;
+ else
+ cp = NULL;
+
+ /*
+ * We now know it's a dependency line so it needs to have all
+ * variables expanded before being parsed. Tell the variable
+ * module to complain if some variable is undefined...
+ */
+ line = Var_Subst(NULL, line, VAR_CMD, VARF_UNDEFERR|VARF_WANTRES);
+
+ /*
+ * Need a non-circular list for the target nodes
+ */
+ if (targets)
+ Lst_Destroy(targets, NULL);
+
+ targets = Lst_Init(FALSE);
+ inLine = TRUE;
+
+ ParseDoDependency(line);
+ free(line);
+
+ /* If there were commands after a ';', add them now */
+ if (cp != NULL) {
+ goto shellCommand;
+ }
+ }
+ /*
+ * Reached EOF, but it may be just EOF of an include file...
+ */
+ } while (ParseEOF() == CONTINUE);
+
+ if (fatals) {
+ (void)fflush(stdout);
+ (void)fprintf(stderr,
+ "%s: Fatal errors encountered -- cannot continue",
+ progname);
+ PrintOnError(NULL, NULL);
+ exit(1);
+ }
+}
+
+/*-
+ *---------------------------------------------------------------------
+ * Parse_Init --
+ * initialize the parsing module
+ *
+ * Results:
+ * none
+ *
+ * Side Effects:
+ * the parseIncPath list is initialized...
+ *---------------------------------------------------------------------
+ */
+void
+Parse_Init(void)
+{
+ mainNode = NULL;
+ parseIncPath = Lst_Init(FALSE);
+ sysIncPath = Lst_Init(FALSE);
+ defIncPath = Lst_Init(FALSE);
+ includes = Lst_Init(FALSE);
+#ifdef CLEANUP
+ targCmds = Lst_Init(FALSE);
+#endif
+}
+
+void
+Parse_End(void)
+{
+#ifdef CLEANUP
+ Lst_Destroy(targCmds, (FreeProc *)free);
+ if (targets)
+ Lst_Destroy(targets, NULL);
+ Lst_Destroy(defIncPath, Dir_Destroy);
+ Lst_Destroy(sysIncPath, Dir_Destroy);
+ Lst_Destroy(parseIncPath, Dir_Destroy);
+ Lst_Destroy(includes, NULL); /* Should be empty now */
+#endif
+}
+
+
+/*-
+ *-----------------------------------------------------------------------
+ * Parse_MainName --
+ * Return a Lst of the main target to create for main()'s sake. If
+ * no such target exists, we Punt with an obnoxious error message.
+ *
+ * Results:
+ * A Lst of the single node to create.
+ *
+ * Side Effects:
+ * None.
+ *
+ *-----------------------------------------------------------------------
+ */
+Lst
+Parse_MainName(void)
+{
+ Lst mainList; /* result list */
+
+ mainList = Lst_Init(FALSE);
+
+ if (mainNode == NULL) {
+ Punt("no target to make.");
+ /*NOTREACHED*/
+ } else if (mainNode->type & OP_DOUBLEDEP) {
+ (void)Lst_AtEnd(mainList, mainNode);
+ Lst_Concat(mainList, mainNode->cohorts, LST_CONCNEW);
+ }
+ else
+ (void)Lst_AtEnd(mainList, mainNode);
+ Var_Append(".TARGETS", mainNode->name, VAR_GLOBAL);
+ return (mainList);
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * ParseMark --
+ * Add the filename and lineno to the GNode so that we remember
+ * where it was first defined.
+ *
+ * Side Effects:
+ * None.
+ *
+ *-----------------------------------------------------------------------
+ */
+static void
+ParseMark(GNode *gn)
+{
+ gn->fname = curFile->fname;
+ gn->lineno = curFile->lineno;
+}
diff --git a/usr.bin/make/pathnames.h b/usr.bin/make/pathnames.h
new file mode 100644
index 0000000..12c4f3d
--- /dev/null
+++ b/usr.bin/make/pathnames.h
@@ -0,0 +1,53 @@
+/* $NetBSD: pathnames.h,v 1.17 2009/04/11 09:41:18 apb Exp $ */
+
+/*
+ * Copyright (c) 1990, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * from: @(#)pathnames.h 5.2 (Berkeley) 6/1/90
+ */
+
+#ifndef MAKE_NATIVE
+#if HAVE_NBTOOL_CONFIG_H
+#include "nbtool_config.h"
+#endif
+#else
+#include <paths.h>
+#endif
+
+#define _PATH_OBJDIR "obj"
+#define _PATH_OBJDIRPREFIX "/usr/obj"
+#ifndef _PATH_DEFSHELLDIR
+#define _PATH_DEFSHELLDIR "/bin"
+#endif
+#define _PATH_DEFSYSMK "sys.mk"
+#ifndef _PATH_DEFSYSPATH
+#define _PATH_DEFSYSPATH "/usr/share/mk"
+#endif
+#ifndef _PATH_TMP
+#define _PATH_TMP "/tmp/" /* with trailing slash */
+#endif
diff --git a/usr.bin/make/sprite.h b/usr.bin/make/sprite.h
new file mode 100644
index 0000000..cdcffd9
--- /dev/null
+++ b/usr.bin/make/sprite.h
@@ -0,0 +1,116 @@
+/* $NetBSD: sprite.h,v 1.14 2017/05/31 22:02:06 maya Exp $ */
+
+/*
+ * Copyright (c) 1988, 1989, 1990, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Adam de Boor.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * from: @(#)sprite.h 8.1 (Berkeley) 6/6/93
+ */
+
+/*
+ * Copyright (c) 1989 by Berkeley Softworks
+ * All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Adam de Boor.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. All advertising materials mentioning features or use of this software
+ * must display the following acknowledgement:
+ * This product includes software developed by the University of
+ * California, Berkeley and its contributors.
+ * 4. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * from: @(#)sprite.h 8.1 (Berkeley) 6/6/93
+ */
+
+/*
+ * sprite.h --
+ *
+ * Common constants and type declarations for Sprite.
+ */
+
+#ifndef MAKE_SPRITE_H
+#define MAKE_SPRITE_H
+
+
+/*
+ * A boolean type is defined as an integer, not an enum. This allows a
+ * boolean argument to be an expression that isn't strictly 0 or 1 valued.
+ */
+
+typedef int Boolean;
+#ifndef TRUE
+#define TRUE 1
+#endif /* TRUE */
+#ifndef FALSE
+#define FALSE 0
+#endif /* FALSE */
+
+/*
+ * Functions that must return a status can return a ReturnStatus to
+ * indicate success or type of failure.
+ */
+
+typedef int ReturnStatus;
+
+/*
+ * The following statuses overlap with the first 2 generic statuses
+ * defined in status.h:
+ *
+ * SUCCESS There was no error.
+ * FAILURE There was a general error.
+ */
+
+#define SUCCESS 0x00000000
+#define FAILURE 0x00000001
+
+#endif /* MAKE_SPRITE_H */
diff --git a/usr.bin/make/str.c b/usr.bin/make/str.c
new file mode 100644
index 0000000..b5255bc
--- /dev/null
+++ b/usr.bin/make/str.c
@@ -0,0 +1,526 @@
+/* $NetBSD: str.c,v 1.38 2017/04/21 22:15:44 sjg Exp $ */
+
+/*-
+ * Copyright (c) 1988, 1989, 1990, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Adam de Boor.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+/*-
+ * Copyright (c) 1989 by Berkeley Softworks
+ * All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Adam de Boor.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. All advertising materials mentioning features or use of this software
+ * must display the following acknowledgement:
+ * This product includes software developed by the University of
+ * California, Berkeley and its contributors.
+ * 4. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#ifndef MAKE_NATIVE
+static char rcsid[] = "$NetBSD: str.c,v 1.38 2017/04/21 22:15:44 sjg Exp $";
+#else
+#include <sys/cdefs.h>
+#ifndef lint
+#if 0
+static char sccsid[] = "@(#)str.c 5.8 (Berkeley) 6/1/90";
+#else
+__RCSID("$NetBSD: str.c,v 1.38 2017/04/21 22:15:44 sjg Exp $");
+#endif
+#endif /* not lint */
+#endif
+
+#include "make.h"
+
+/*-
+ * str_concat --
+ * concatenate the two strings, inserting a space or slash between them,
+ * freeing them if requested.
+ *
+ * returns --
+ * the resulting string in allocated space.
+ */
+char *
+str_concat(const char *s1, const char *s2, int flags)
+{
+ int len1, len2;
+ char *result;
+
+ /* get the length of both strings */
+ len1 = strlen(s1);
+ len2 = strlen(s2);
+
+ /* allocate length plus separator plus EOS */
+ result = bmake_malloc((unsigned int)(len1 + len2 + 2));
+
+ /* copy first string into place */
+ memcpy(result, s1, len1);
+
+ /* add separator character */
+ if (flags & STR_ADDSPACE) {
+ result[len1] = ' ';
+ ++len1;
+ } else if (flags & STR_ADDSLASH) {
+ result[len1] = '/';
+ ++len1;
+ }
+
+ /* copy second string plus EOS into place */
+ memcpy(result + len1, s2, len2 + 1);
+
+ return(result);
+}
+
+/*-
+ * brk_string --
+ * Fracture a string into an array of words (as delineated by tabs or
+ * spaces) taking quotation marks into account. Leading tabs/spaces
+ * are ignored.
+ *
+ * If expand is TRUE, quotes are removed and escape sequences
+ * such as \r, \t, etc... are expanded.
+ *
+ * returns --
+ * Pointer to the array of pointers to the words.
+ * Memory containing the actual words in *buffer.
+ * Both of these must be free'd by the caller.
+ * Number of words in *store_argc.
+ */
+char **
+brk_string(const char *str, int *store_argc, Boolean expand, char **buffer)
+{
+ int argc, ch;
+ char inquote, *start, *t;
+ const char *p;
+ int len;
+ int argmax = 50, curlen = 0;
+ char **argv;
+
+ /* skip leading space chars. */
+ for (; *str == ' ' || *str == '\t'; ++str)
+ continue;
+
+ /* allocate room for a copy of the string */
+ if ((len = strlen(str) + 1) > curlen)
+ *buffer = bmake_malloc(curlen = len);
+
+ /*
+ * initial argmax based on len
+ */
+ argmax = MAX((len / 5), 50);
+ argv = bmake_malloc((argmax + 1) * sizeof(char *));
+
+ /*
+ * copy the string; at the same time, parse backslashes,
+ * quotes and build the argument list.
+ */
+ argc = 0;
+ inquote = '\0';
+ for (p = str, start = t = *buffer;; ++p) {
+ switch(ch = *p) {
+ case '"':
+ case '\'':
+ if (inquote) {
+ if (inquote == ch)
+ inquote = '\0';
+ else
+ break;
+ }
+ else {
+ inquote = (char) ch;
+ /* Don't miss "" or '' */
+ if (start == NULL && p[1] == inquote) {
+ if (!expand) {
+ start = t;
+ *t++ = ch;
+ } else
+ start = t + 1;
+ p++;
+ inquote = '\0';
+ break;
+ }
+ }
+ if (!expand) {
+ if (!start)
+ start = t;
+ *t++ = ch;
+ }
+ continue;
+ case ' ':
+ case '\t':
+ case '\n':
+ if (inquote)
+ break;
+ if (!start)
+ continue;
+ /* FALLTHROUGH */
+ case '\0':
+ /*
+ * end of a token -- make sure there's enough argv
+ * space and save off a pointer.
+ */
+ if (!start)
+ goto done;
+
+ *t++ = '\0';
+ if (argc == argmax) {
+ argmax *= 2; /* ramp up fast */
+ argv = (char **)bmake_realloc(argv,
+ (argmax + 1) * sizeof(char *));
+ }
+ argv[argc++] = start;
+ start = NULL;
+ if (ch == '\n' || ch == '\0') {
+ if (expand && inquote) {
+ free(argv);
+ free(*buffer);
+ *buffer = NULL;
+ return NULL;
+ }
+ goto done;
+ }
+ continue;
+ case '\\':
+ if (!expand) {
+ if (!start)
+ start = t;
+ *t++ = '\\';
+ if (*(p+1) == '\0') /* catch '\' at end of line */
+ continue;
+ ch = *++p;
+ break;
+ }
+
+ switch (ch = *++p) {
+ case '\0':
+ case '\n':
+ /* hmmm; fix it up as best we can */
+ ch = '\\';
+ --p;
+ break;
+ case 'b':
+ ch = '\b';
+ break;
+ case 'f':
+ ch = '\f';
+ break;
+ case 'n':
+ ch = '\n';
+ break;
+ case 'r':
+ ch = '\r';
+ break;
+ case 't':
+ ch = '\t';
+ break;
+ }
+ break;
+ }
+ if (!start)
+ start = t;
+ *t++ = (char) ch;
+ }
+done: argv[argc] = NULL;
+ *store_argc = argc;
+ return(argv);
+}
+
+/*
+ * Str_FindSubstring -- See if a string contains a particular substring.
+ *
+ * Input:
+ * string String to search.
+ * substring Substring to find in string.
+ *
+ * Results: If string contains substring, the return value is the location of
+ * the first matching instance of substring in string. If string doesn't
+ * contain substring, the return value is NULL. Matching is done on an exact
+ * character-for-character basis with no wildcards or special characters.
+ *
+ * Side effects: None.
+ */
+char *
+Str_FindSubstring(const char *string, const char *substring)
+{
+ const char *a, *b;
+
+ /*
+ * First scan quickly through the two strings looking for a single-
+ * character match. When it's found, then compare the rest of the
+ * substring.
+ */
+
+ for (b = substring; *string != 0; string += 1) {
+ if (*string != *b)
+ continue;
+ a = string;
+ for (;;) {
+ if (*b == 0)
+ return UNCONST(string);
+ if (*a++ != *b++)
+ break;
+ }
+ b = substring;
+ }
+ return NULL;
+}
+
+/*
+ * Str_Match --
+ *
+ * See if a particular string matches a particular pattern.
+ *
+ * Results: Non-zero is returned if string matches pattern, 0 otherwise. The
+ * matching operation permits the following special characters in the
+ * pattern: *?\[] (see the man page for details on what these mean).
+ *
+ * XXX this function does not detect or report malformed patterns.
+ *
+ * Side effects: None.
+ */
+int
+Str_Match(const char *string, const char *pattern)
+{
+ char c2;
+
+ for (;;) {
+ /*
+ * See if we're at the end of both the pattern and the
+ * string. If, we succeeded. If we're at the end of the
+ * pattern but not at the end of the string, we failed.
+ */
+ if (*pattern == 0)
+ return(!*string);
+ if (*string == 0 && *pattern != '*')
+ return(0);
+ /*
+ * Check for a "*" as the next pattern character. It matches
+ * any substring. We handle this by calling ourselves
+ * recursively for each postfix of string, until either we
+ * match or we reach the end of the string.
+ */
+ if (*pattern == '*') {
+ pattern += 1;
+ if (*pattern == 0)
+ return(1);
+ while (*string != 0) {
+ if (Str_Match(string, pattern))
+ return(1);
+ ++string;
+ }
+ return(0);
+ }
+ /*
+ * Check for a "?" as the next pattern character. It matches
+ * any single character.
+ */
+ if (*pattern == '?')
+ goto thisCharOK;
+ /*
+ * Check for a "[" as the next pattern character. It is
+ * followed by a list of characters that are acceptable, or
+ * by a range (two characters separated by "-").
+ */
+ if (*pattern == '[') {
+ int nomatch;
+
+ ++pattern;
+ if (*pattern == '^') {
+ ++pattern;
+ nomatch = 1;
+ } else
+ nomatch = 0;
+ for (;;) {
+ if ((*pattern == ']') || (*pattern == 0)) {
+ if (nomatch)
+ break;
+ return(0);
+ }
+ if (*pattern == *string)
+ break;
+ if (pattern[1] == '-') {
+ c2 = pattern[2];
+ if (c2 == 0)
+ return(nomatch);
+ if ((*pattern <= *string) &&
+ (c2 >= *string))
+ break;
+ if ((*pattern >= *string) &&
+ (c2 <= *string))
+ break;
+ pattern += 2;
+ }
+ ++pattern;
+ }
+ if (nomatch && (*pattern != ']') && (*pattern != 0))
+ return 0;
+ while ((*pattern != ']') && (*pattern != 0))
+ ++pattern;
+ goto thisCharOK;
+ }
+ /*
+ * If the next pattern character is '/', just strip off the
+ * '/' so we do exact matching on the character that follows.
+ */
+ if (*pattern == '\\') {
+ ++pattern;
+ if (*pattern == 0)
+ return(0);
+ }
+ /*
+ * There's no special character. Just make sure that the
+ * next characters of each string match.
+ */
+ if (*pattern != *string)
+ return(0);
+thisCharOK: ++pattern;
+ ++string;
+ }
+}
+
+
+/*-
+ *-----------------------------------------------------------------------
+ * Str_SYSVMatch --
+ * Check word against pattern for a match (% is wild),
+ *
+ * Input:
+ * word Word to examine
+ * pattern Pattern to examine against
+ * len Number of characters to substitute
+ *
+ * Results:
+ * Returns the beginning position of a match or null. The number
+ * of characters matched is returned in len.
+ *
+ * Side Effects:
+ * None
+ *
+ *-----------------------------------------------------------------------
+ */
+char *
+Str_SYSVMatch(const char *word, const char *pattern, int *len)
+{
+ const char *p = pattern;
+ const char *w = word;
+ const char *m;
+
+ if (*p == '\0') {
+ /* Null pattern is the whole string */
+ *len = strlen(w);
+ return UNCONST(w);
+ }
+
+ if ((m = strchr(p, '%')) != NULL) {
+ /* check that the prefix matches */
+ for (; p != m && *w && *w == *p; w++, p++)
+ continue;
+
+ if (p != m)
+ return NULL; /* No match */
+
+ if (*++p == '\0') {
+ /* No more pattern, return the rest of the string */
+ *len = strlen(w);
+ return UNCONST(w);
+ }
+ }
+
+ m = w;
+
+ /* Find a matching tail */
+ do
+ if (strcmp(p, w) == 0) {
+ *len = w - m;
+ return UNCONST(m);
+ }
+ while (*w++ != '\0');
+
+ return NULL;
+}
+
+
+/*-
+ *-----------------------------------------------------------------------
+ * Str_SYSVSubst --
+ * Substitute '%' on the pattern with len characters from src.
+ * If the pattern does not contain a '%' prepend len characters
+ * from src.
+ *
+ * Results:
+ * None
+ *
+ * Side Effects:
+ * Places result on buf
+ *
+ *-----------------------------------------------------------------------
+ */
+void
+Str_SYSVSubst(Buffer *buf, char *pat, char *src, int len)
+{
+ char *m;
+
+ if ((m = strchr(pat, '%')) != NULL) {
+ /* Copy the prefix */
+ Buf_AddBytes(buf, m - pat, pat);
+ /* skip the % */
+ pat = m + 1;
+ }
+
+ /* Copy the pattern */
+ Buf_AddBytes(buf, len, src);
+
+ /* append the rest */
+ Buf_AddBytes(buf, strlen(pat), pat);
+}
diff --git a/usr.bin/make/strlist.c b/usr.bin/make/strlist.c
new file mode 100644
index 0000000..3fb2f7d
--- /dev/null
+++ b/usr.bin/make/strlist.c
@@ -0,0 +1,93 @@
+/* $NetBSD: strlist.c,v 1.4 2009/01/24 11:59:39 dsl Exp $ */
+
+/*-
+ * Copyright (c) 2008 - 2009 The NetBSD Foundation, Inc.
+ * All rights reserved.
+ *
+ * This code is derived from software contributed to The NetBSD Foundation
+ * by David Laight.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of The NetBSD Foundation nor the names of its
+ * contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
+ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef MAKE_NATIVE
+static char rcsid[] = "$NetBSD: strlist.c,v 1.4 2009/01/24 11:59:39 dsl Exp $";
+#else
+#include <sys/cdefs.h>
+#ifndef lint
+__RCSID("$NetBSD: strlist.c,v 1.4 2009/01/24 11:59:39 dsl Exp $");
+#endif /* not lint */
+#endif
+
+#include <stddef.h>
+#include <stdlib.h>
+#include "strlist.h"
+#include "make_malloc.h"
+
+void
+strlist_init(strlist_t *sl)
+{
+ sl->sl_num = 0;
+ sl->sl_max = 0;
+ sl->sl_items = NULL;
+}
+
+void
+strlist_clean(strlist_t *sl)
+{
+ char *str;
+ int i;
+
+ STRLIST_FOREACH(str, sl, i)
+ free(str);
+ free(sl->sl_items);
+
+ sl->sl_num = 0;
+ sl->sl_max = 0;
+ sl->sl_items = NULL;
+}
+
+void
+strlist_add_str(strlist_t *sl, char *str, unsigned int info)
+{
+ unsigned int n;
+ strlist_item_t *items;
+
+ if (str == NULL)
+ return;
+
+ n = sl->sl_num + 1;
+ sl->sl_num = n;
+ items = sl->sl_items;
+ if (n >= sl->sl_max) {
+ items = bmake_realloc(items, (n + 7) * sizeof *sl->sl_items);
+ sl->sl_items = items;
+ sl->sl_max = n + 6;
+ }
+ items += n - 1;
+ items->si_str = str;
+ items->si_info = info;
+ items[1].si_str = NULL; /* STRLIST_FOREACH() terminator */
+}
diff --git a/usr.bin/make/strlist.h b/usr.bin/make/strlist.h
new file mode 100644
index 0000000..2fc049e
--- /dev/null
+++ b/usr.bin/make/strlist.h
@@ -0,0 +1,62 @@
+/* $NetBSD: strlist.h,v 1.3 2009/01/16 21:15:34 dsl Exp $ */
+
+/*-
+ * Copyright (c) 2008 - 2009 The NetBSD Foundation, Inc.
+ * All rights reserved.
+ *
+ * This code is derived from software contributed to The NetBSD Foundation
+ * by David Laight.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of The NetBSD Foundation nor the names of its
+ * contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
+ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef _STRLIST_H
+#define _STRLIST_H
+
+typedef struct {
+ char *si_str;
+ unsigned int si_info;
+} strlist_item_t;
+
+typedef struct {
+ unsigned int sl_num;
+ unsigned int sl_max;
+ strlist_item_t *sl_items;
+} strlist_t;
+
+void strlist_init(strlist_t *);
+void strlist_clean(strlist_t *);
+void strlist_add_str(strlist_t *, char *, unsigned int);
+
+#define strlist_num(sl) ((sl)->sl_num)
+#define strlist_str(sl, n) ((sl)->sl_items[n].si_str)
+#define strlist_info(sl, n) ((sl)->sl_items[n].si_info)
+#define strlist_set_info(sl, n, v) ((void)((sl)->sl_items[n].si_info = (v)))
+
+#define STRLIST_FOREACH(v, sl, index) \
+ if ((sl)->sl_items != NULL) \
+ for (index = 0; (v = strlist_str(sl, index)) != NULL; index++)
+
+#endif /* _STRLIST_H */
diff --git a/usr.bin/make/suff.c b/usr.bin/make/suff.c
new file mode 100644
index 0000000..df0306a
--- /dev/null
+++ b/usr.bin/make/suff.c
@@ -0,0 +1,2676 @@
+/* $NetBSD: suff.c,v 1.86 2017/04/16 20:38:18 riastradh Exp $ */
+
+/*
+ * Copyright (c) 1988, 1989, 1990, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Adam de Boor.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+/*
+ * Copyright (c) 1989 by Berkeley Softworks
+ * All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Adam de Boor.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. All advertising materials mentioning features or use of this software
+ * must display the following acknowledgement:
+ * This product includes software developed by the University of
+ * California, Berkeley and its contributors.
+ * 4. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#ifndef MAKE_NATIVE
+static char rcsid[] = "$NetBSD: suff.c,v 1.86 2017/04/16 20:38:18 riastradh Exp $";
+#else
+#include <sys/cdefs.h>
+#ifndef lint
+#if 0
+static char sccsid[] = "@(#)suff.c 8.4 (Berkeley) 3/21/94";
+#else
+__RCSID("$NetBSD: suff.c,v 1.86 2017/04/16 20:38:18 riastradh Exp $");
+#endif
+#endif /* not lint */
+#endif
+
+/*-
+ * suff.c --
+ * Functions to maintain suffix lists and find implicit dependents
+ * using suffix transformation rules
+ *
+ * Interface:
+ * Suff_Init Initialize all things to do with suffixes.
+ *
+ * Suff_End Cleanup the module
+ *
+ * Suff_DoPaths This function is used to make life easier
+ * when searching for a file according to its
+ * suffix. It takes the global search path,
+ * as defined using the .PATH: target, and appends
+ * its directories to the path of each of the
+ * defined suffixes, as specified using
+ * .PATH<suffix>: targets. In addition, all
+ * directories given for suffixes labeled as
+ * include files or libraries, using the .INCLUDES
+ * or .LIBS targets, are played with using
+ * Dir_MakeFlags to create the .INCLUDES and
+ * .LIBS global variables.
+ *
+ * Suff_ClearSuffixes Clear out all the suffixes and defined
+ * transformations.
+ *
+ * Suff_IsTransform Return TRUE if the passed string is the lhs
+ * of a transformation rule.
+ *
+ * Suff_AddSuffix Add the passed string as another known suffix.
+ *
+ * Suff_GetPath Return the search path for the given suffix.
+ *
+ * Suff_AddInclude Mark the given suffix as denoting an include
+ * file.
+ *
+ * Suff_AddLib Mark the given suffix as denoting a library.
+ *
+ * Suff_AddTransform Add another transformation to the suffix
+ * graph. Returns GNode suitable for framing, I
+ * mean, tacking commands, attributes, etc. on.
+ *
+ * Suff_SetNull Define the suffix to consider the suffix of
+ * any file that doesn't have a known one.
+ *
+ * Suff_FindDeps Find implicit sources for and the location of
+ * a target based on its suffix. Returns the
+ * bottom-most node added to the graph or NULL
+ * if the target had no implicit sources.
+ *
+ * Suff_FindPath Return the appropriate path to search in
+ * order to find the node.
+ */
+
+#include <assert.h>
+#include <stdio.h>
+#include "make.h"
+#include "hash.h"
+#include "dir.h"
+
+static Lst sufflist; /* Lst of suffixes */
+#ifdef CLEANUP
+static Lst suffClean; /* Lst of suffixes to be cleaned */
+#endif
+static Lst srclist; /* Lst of sources */
+static Lst transforms; /* Lst of transformation rules */
+
+static int sNum = 0; /* Counter for assigning suffix numbers */
+
+/*
+ * Structure describing an individual suffix.
+ */
+typedef struct _Suff {
+ char *name; /* The suffix itself */
+ int nameLen; /* Length of the suffix */
+ short flags; /* Type of suffix */
+#define SUFF_INCLUDE 0x01 /* One which is #include'd */
+#define SUFF_LIBRARY 0x02 /* One which contains a library */
+#define SUFF_NULL 0x04 /* The empty suffix */
+ Lst searchPath; /* The path along which files of this suffix
+ * may be found */
+ int sNum; /* The suffix number */
+ int refCount; /* Reference count of list membership */
+ Lst parents; /* Suffixes we have a transformation to */
+ Lst children; /* Suffixes we have a transformation from */
+ Lst ref; /* List of lists this suffix is referenced */
+} Suff;
+
+/*
+ * for SuffSuffIsSuffix
+ */
+typedef struct {
+ char *ename; /* The end of the name */
+ int len; /* Length of the name */
+} SuffixCmpData;
+
+/*
+ * Structure used in the search for implied sources.
+ */
+typedef struct _Src {
+ char *file; /* The file to look for */
+ char *pref; /* Prefix from which file was formed */
+ Suff *suff; /* The suffix on the file */
+ struct _Src *parent; /* The Src for which this is a source */
+ GNode *node; /* The node describing the file */
+ int children; /* Count of existing children (so we don't free
+ * this thing too early or never nuke it) */
+#ifdef DEBUG_SRC
+ Lst cp; /* Debug; children list */
+#endif
+} Src;
+
+/*
+ * A structure for passing more than one argument to the Lst-library-invoked
+ * function...
+ */
+typedef struct {
+ Lst l;
+ Src *s;
+} LstSrc;
+
+typedef struct {
+ GNode **gn;
+ Suff *s;
+ Boolean r;
+} GNodeSuff;
+
+static Suff *suffNull; /* The NULL suffix for this run */
+static Suff *emptySuff; /* The empty suffix required for POSIX
+ * single-suffix transformation rules */
+
+
+static const char *SuffStrIsPrefix(const char *, const char *);
+static char *SuffSuffIsSuffix(const Suff *, const SuffixCmpData *);
+static int SuffSuffIsSuffixP(const void *, const void *);
+static int SuffSuffHasNameP(const void *, const void *);
+static int SuffSuffIsPrefix(const void *, const void *);
+static int SuffGNHasNameP(const void *, const void *);
+static void SuffUnRef(void *, void *);
+static void SuffFree(void *);
+static void SuffInsert(Lst, Suff *);
+static void SuffRemove(Lst, Suff *);
+static Boolean SuffParseTransform(char *, Suff **, Suff **);
+static int SuffRebuildGraph(void *, void *);
+static int SuffScanTargets(void *, void *);
+static int SuffAddSrc(void *, void *);
+static int SuffRemoveSrc(Lst);
+static void SuffAddLevel(Lst, Src *);
+static Src *SuffFindThem(Lst, Lst);
+static Src *SuffFindCmds(Src *, Lst);
+static void SuffExpandChildren(LstNode, GNode *);
+static void SuffExpandWildcards(LstNode, GNode *);
+static Boolean SuffApplyTransform(GNode *, GNode *, Suff *, Suff *);
+static void SuffFindDeps(GNode *, Lst);
+static void SuffFindArchiveDeps(GNode *, Lst);
+static void SuffFindNormalDeps(GNode *, Lst);
+static int SuffPrintName(void *, void *);
+static int SuffPrintSuff(void *, void *);
+static int SuffPrintTrans(void *, void *);
+
+ /*************** Lst Predicates ****************/
+/*-
+ *-----------------------------------------------------------------------
+ * SuffStrIsPrefix --
+ * See if pref is a prefix of str.
+ *
+ * Input:
+ * pref possible prefix
+ * str string to check
+ *
+ * Results:
+ * NULL if it ain't, pointer to character in str after prefix if so
+ *
+ * Side Effects:
+ * None
+ *-----------------------------------------------------------------------
+ */
+static const char *
+SuffStrIsPrefix(const char *pref, const char *str)
+{
+ while (*str && *pref == *str) {
+ pref++;
+ str++;
+ }
+
+ return (*pref ? NULL : str);
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * SuffSuffIsSuffix --
+ * See if suff is a suffix of str. sd->ename should point to THE END
+ * of the string to check. (THE END == the null byte)
+ *
+ * Input:
+ * s possible suffix
+ * sd string to examine
+ *
+ * Results:
+ * NULL if it ain't, pointer to character in str before suffix if
+ * it is.
+ *
+ * Side Effects:
+ * None
+ *-----------------------------------------------------------------------
+ */
+static char *
+SuffSuffIsSuffix(const Suff *s, const SuffixCmpData *sd)
+{
+ char *p1; /* Pointer into suffix name */
+ char *p2; /* Pointer into string being examined */
+
+ if (sd->len < s->nameLen)
+ return NULL; /* this string is shorter than the suffix */
+
+ p1 = s->name + s->nameLen;
+ p2 = sd->ename;
+
+ while (p1 >= s->name && *p1 == *p2) {
+ p1--;
+ p2--;
+ }
+
+ return (p1 == s->name - 1 ? p2 : NULL);
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * SuffSuffIsSuffixP --
+ * Predicate form of SuffSuffIsSuffix. Passed as the callback function
+ * to Lst_Find.
+ *
+ * Results:
+ * 0 if the suffix is the one desired, non-zero if not.
+ *
+ * Side Effects:
+ * None.
+ *
+ *-----------------------------------------------------------------------
+ */
+static int
+SuffSuffIsSuffixP(const void *s, const void *sd)
+{
+ return(!SuffSuffIsSuffix(s, sd));
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * SuffSuffHasNameP --
+ * Callback procedure for finding a suffix based on its name. Used by
+ * Suff_GetPath.
+ *
+ * Input:
+ * s Suffix to check
+ * sd Desired name
+ *
+ * Results:
+ * 0 if the suffix is of the given name. non-zero otherwise.
+ *
+ * Side Effects:
+ * None
+ *-----------------------------------------------------------------------
+ */
+static int
+SuffSuffHasNameP(const void *s, const void *sname)
+{
+ return (strcmp(sname, ((const Suff *)s)->name));
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * SuffSuffIsPrefix --
+ * See if the suffix described by s is a prefix of the string. Care
+ * must be taken when using this to search for transformations and
+ * what-not, since there could well be two suffixes, one of which
+ * is a prefix of the other...
+ *
+ * Input:
+ * s suffix to compare
+ * str string to examine
+ *
+ * Results:
+ * 0 if s is a prefix of str. non-zero otherwise
+ *
+ * Side Effects:
+ * None
+ *-----------------------------------------------------------------------
+ */
+static int
+SuffSuffIsPrefix(const void *s, const void *str)
+{
+ return SuffStrIsPrefix(((const Suff *)s)->name, str) == NULL;
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * SuffGNHasNameP --
+ * See if the graph node has the desired name
+ *
+ * Input:
+ * gn current node we're looking at
+ * name name we're looking for
+ *
+ * Results:
+ * 0 if it does. non-zero if it doesn't
+ *
+ * Side Effects:
+ * None
+ *-----------------------------------------------------------------------
+ */
+static int
+SuffGNHasNameP(const void *gn, const void *name)
+{
+ return (strcmp(name, ((const GNode *)gn)->name));
+}
+
+ /*********** Maintenance Functions ************/
+
+static void
+SuffUnRef(void *lp, void *sp)
+{
+ Lst l = (Lst) lp;
+
+ LstNode ln = Lst_Member(l, sp);
+ if (ln != NULL) {
+ Lst_Remove(l, ln);
+ ((Suff *)sp)->refCount--;
+ }
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * SuffFree --
+ * Free up all memory associated with the given suffix structure.
+ *
+ * Results:
+ * none
+ *
+ * Side Effects:
+ * the suffix entry is detroyed
+ *-----------------------------------------------------------------------
+ */
+static void
+SuffFree(void *sp)
+{
+ Suff *s = (Suff *)sp;
+
+ if (s == suffNull)
+ suffNull = NULL;
+
+ if (s == emptySuff)
+ emptySuff = NULL;
+
+#ifdef notdef
+ /* We don't delete suffixes in order, so we cannot use this */
+ if (s->refCount)
+ Punt("Internal error deleting suffix `%s' with refcount = %d", s->name,
+ s->refCount);
+#endif
+
+ Lst_Destroy(s->ref, NULL);
+ Lst_Destroy(s->children, NULL);
+ Lst_Destroy(s->parents, NULL);
+ Lst_Destroy(s->searchPath, Dir_Destroy);
+
+ free(s->name);
+ free(s);
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * SuffRemove --
+ * Remove the suffix into the list
+ *
+ * Results:
+ * None
+ *
+ * Side Effects:
+ * The reference count for the suffix is decremented and the
+ * suffix is possibly freed
+ *-----------------------------------------------------------------------
+ */
+static void
+SuffRemove(Lst l, Suff *s)
+{
+ SuffUnRef(l, s);
+ if (s->refCount == 0) {
+ SuffUnRef(sufflist, s);
+ SuffFree(s);
+ }
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * SuffInsert --
+ * Insert the suffix into the list keeping the list ordered by suffix
+ * numbers.
+ *
+ * Input:
+ * l the list where in s should be inserted
+ * s the suffix to insert
+ *
+ * Results:
+ * None
+ *
+ * Side Effects:
+ * The reference count of the suffix is incremented
+ *-----------------------------------------------------------------------
+ */
+static void
+SuffInsert(Lst l, Suff *s)
+{
+ LstNode ln; /* current element in l we're examining */
+ Suff *s2 = NULL; /* the suffix descriptor in this element */
+
+ if (Lst_Open(l) == FAILURE) {
+ return;
+ }
+ while ((ln = Lst_Next(l)) != NULL) {
+ s2 = (Suff *)Lst_Datum(ln);
+ if (s2->sNum >= s->sNum) {
+ break;
+ }
+ }
+
+ Lst_Close(l);
+ if (DEBUG(SUFF)) {
+ fprintf(debug_file, "inserting %s(%d)...", s->name, s->sNum);
+ }
+ if (ln == NULL) {
+ if (DEBUG(SUFF)) {
+ fprintf(debug_file, "at end of list\n");
+ }
+ (void)Lst_AtEnd(l, s);
+ s->refCount++;
+ (void)Lst_AtEnd(s->ref, l);
+ } else if (s2->sNum != s->sNum) {
+ if (DEBUG(SUFF)) {
+ fprintf(debug_file, "before %s(%d)\n", s2->name, s2->sNum);
+ }
+ (void)Lst_InsertBefore(l, ln, s);
+ s->refCount++;
+ (void)Lst_AtEnd(s->ref, l);
+ } else if (DEBUG(SUFF)) {
+ fprintf(debug_file, "already there\n");
+ }
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * Suff_ClearSuffixes --
+ * This is gross. Nuke the list of suffixes but keep all transformation
+ * rules around. The transformation graph is destroyed in this process,
+ * but we leave the list of rules so when a new graph is formed the rules
+ * will remain.
+ * This function is called from the parse module when a
+ * .SUFFIXES:\n line is encountered.
+ *
+ * Results:
+ * none
+ *
+ * Side Effects:
+ * the sufflist and its graph nodes are destroyed
+ *-----------------------------------------------------------------------
+ */
+void
+Suff_ClearSuffixes(void)
+{
+#ifdef CLEANUP
+ Lst_Concat(suffClean, sufflist, LST_CONCLINK);
+#endif
+ sufflist = Lst_Init(FALSE);
+ sNum = 0;
+ if (suffNull)
+ SuffFree(suffNull);
+ emptySuff = suffNull = bmake_malloc(sizeof(Suff));
+
+ suffNull->name = bmake_strdup("");
+ suffNull->nameLen = 0;
+ suffNull->searchPath = Lst_Init(FALSE);
+ Dir_Concat(suffNull->searchPath, dirSearchPath);
+ suffNull->children = Lst_Init(FALSE);
+ suffNull->parents = Lst_Init(FALSE);
+ suffNull->ref = Lst_Init(FALSE);
+ suffNull->sNum = sNum++;
+ suffNull->flags = SUFF_NULL;
+ suffNull->refCount = 1;
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * SuffParseTransform --
+ * Parse a transformation string to find its two component suffixes.
+ *
+ * Input:
+ * str String being parsed
+ * srcPtr Place to store source of trans.
+ * targPtr Place to store target of trans.
+ *
+ * Results:
+ * TRUE if the string is a valid transformation and FALSE otherwise.
+ *
+ * Side Effects:
+ * The passed pointers are overwritten.
+ *
+ *-----------------------------------------------------------------------
+ */
+static Boolean
+SuffParseTransform(char *str, Suff **srcPtr, Suff **targPtr)
+{
+ LstNode srcLn; /* element in suffix list of trans source*/
+ Suff *src; /* Source of transformation */
+ LstNode targLn; /* element in suffix list of trans target*/
+ char *str2; /* Extra pointer (maybe target suffix) */
+ LstNode singleLn; /* element in suffix list of any suffix
+ * that exactly matches str */
+ Suff *single = NULL;/* Source of possible transformation to
+ * null suffix */
+
+ srcLn = NULL;
+ singleLn = NULL;
+
+ /*
+ * Loop looking first for a suffix that matches the start of the
+ * string and then for one that exactly matches the rest of it. If
+ * we can find two that meet these criteria, we've successfully
+ * parsed the string.
+ */
+ for (;;) {
+ if (srcLn == NULL) {
+ srcLn = Lst_Find(sufflist, str, SuffSuffIsPrefix);
+ } else {
+ srcLn = Lst_FindFrom(sufflist, Lst_Succ(srcLn), str,
+ SuffSuffIsPrefix);
+ }
+ if (srcLn == NULL) {
+ /*
+ * Ran out of source suffixes -- no such rule
+ */
+ if (singleLn != NULL) {
+ /*
+ * Not so fast Mr. Smith! There was a suffix that encompassed
+ * the entire string, so we assume it was a transformation
+ * to the null suffix (thank you POSIX). We still prefer to
+ * find a double rule over a singleton, hence we leave this
+ * check until the end.
+ *
+ * XXX: Use emptySuff over suffNull?
+ */
+ *srcPtr = single;
+ *targPtr = suffNull;
+ return(TRUE);
+ }
+ return (FALSE);
+ }
+ src = (Suff *)Lst_Datum(srcLn);
+ str2 = str + src->nameLen;
+ if (*str2 == '\0') {
+ single = src;
+ singleLn = srcLn;
+ } else {
+ targLn = Lst_Find(sufflist, str2, SuffSuffHasNameP);
+ if (targLn != NULL) {
+ *srcPtr = src;
+ *targPtr = (Suff *)Lst_Datum(targLn);
+ return (TRUE);
+ }
+ }
+ }
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * Suff_IsTransform --
+ * Return TRUE if the given string is a transformation rule
+ *
+ *
+ * Input:
+ * str string to check
+ *
+ * Results:
+ * TRUE if the string is a concatenation of two known suffixes.
+ * FALSE otherwise
+ *
+ * Side Effects:
+ * None
+ *-----------------------------------------------------------------------
+ */
+Boolean
+Suff_IsTransform(char *str)
+{
+ Suff *src, *targ;
+
+ return (SuffParseTransform(str, &src, &targ));
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * Suff_AddTransform --
+ * Add the transformation rule described by the line to the
+ * list of rules and place the transformation itself in the graph
+ *
+ * Input:
+ * line name of transformation to add
+ *
+ * Results:
+ * The node created for the transformation in the transforms list
+ *
+ * Side Effects:
+ * The node is placed on the end of the transforms Lst and links are
+ * made between the two suffixes mentioned in the target name
+ *-----------------------------------------------------------------------
+ */
+GNode *
+Suff_AddTransform(char *line)
+{
+ GNode *gn; /* GNode of transformation rule */
+ Suff *s, /* source suffix */
+ *t; /* target suffix */
+ LstNode ln; /* Node for existing transformation */
+
+ ln = Lst_Find(transforms, line, SuffGNHasNameP);
+ if (ln == NULL) {
+ /*
+ * Make a new graph node for the transformation. It will be filled in
+ * by the Parse module.
+ */
+ gn = Targ_NewGN(line);
+ (void)Lst_AtEnd(transforms, gn);
+ } else {
+ /*
+ * New specification for transformation rule. Just nuke the old list
+ * of commands so they can be filled in again... We don't actually
+ * free the commands themselves, because a given command can be
+ * attached to several different transformations.
+ */
+ gn = (GNode *)Lst_Datum(ln);
+ Lst_Destroy(gn->commands, NULL);
+ Lst_Destroy(gn->children, NULL);
+ gn->commands = Lst_Init(FALSE);
+ gn->children = Lst_Init(FALSE);
+ }
+
+ gn->type = OP_TRANSFORM;
+
+ (void)SuffParseTransform(line, &s, &t);
+
+ /*
+ * link the two together in the proper relationship and order
+ */
+ if (DEBUG(SUFF)) {
+ fprintf(debug_file, "defining transformation from `%s' to `%s'\n",
+ s->name, t->name);
+ }
+ SuffInsert(t->children, s);
+ SuffInsert(s->parents, t);
+
+ return (gn);
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * Suff_EndTransform --
+ * Handle the finish of a transformation definition, removing the
+ * transformation from the graph if it has neither commands nor
+ * sources. This is a callback procedure for the Parse module via
+ * Lst_ForEach
+ *
+ * Input:
+ * gnp Node for transformation
+ * dummy Node for transformation
+ *
+ * Results:
+ * === 0
+ *
+ * Side Effects:
+ * If the node has no commands or children, the children and parents
+ * lists of the affected suffixes are altered.
+ *
+ *-----------------------------------------------------------------------
+ */
+int
+Suff_EndTransform(void *gnp, void *dummy MAKE_ATTR_UNUSED)
+{
+ GNode *gn = (GNode *)gnp;
+
+ if ((gn->type & OP_DOUBLEDEP) && !Lst_IsEmpty (gn->cohorts))
+ gn = (GNode *)Lst_Datum(Lst_Last(gn->cohorts));
+ if ((gn->type & OP_TRANSFORM) && Lst_IsEmpty(gn->commands) &&
+ Lst_IsEmpty(gn->children))
+ {
+ Suff *s, *t;
+
+ /*
+ * SuffParseTransform() may fail for special rules which are not
+ * actual transformation rules. (e.g. .DEFAULT)
+ */
+ if (SuffParseTransform(gn->name, &s, &t)) {
+ Lst p;
+
+ if (DEBUG(SUFF)) {
+ fprintf(debug_file, "deleting transformation from `%s' to `%s'\n",
+ s->name, t->name);
+ }
+
+ /*
+ * Store s->parents because s could be deleted in SuffRemove
+ */
+ p = s->parents;
+
+ /*
+ * Remove the source from the target's children list. We check for a
+ * nil return to handle a beanhead saying something like
+ * .c.o .c.o:
+ *
+ * We'll be called twice when the next target is seen, but .c and .o
+ * are only linked once...
+ */
+ SuffRemove(t->children, s);
+
+ /*
+ * Remove the target from the source's parents list
+ */
+ SuffRemove(p, t);
+ }
+ } else if ((gn->type & OP_TRANSFORM) && DEBUG(SUFF)) {
+ fprintf(debug_file, "transformation %s complete\n", gn->name);
+ }
+
+ return 0;
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * SuffRebuildGraph --
+ * Called from Suff_AddSuffix via Lst_ForEach to search through the
+ * list of existing transformation rules and rebuild the transformation
+ * graph when it has been destroyed by Suff_ClearSuffixes. If the
+ * given rule is a transformation involving this suffix and another,
+ * existing suffix, the proper relationship is established between
+ * the two.
+ *
+ * Input:
+ * transformp Transformation to test
+ * sp Suffix to rebuild
+ *
+ * Results:
+ * Always 0.
+ *
+ * Side Effects:
+ * The appropriate links will be made between this suffix and
+ * others if transformation rules exist for it.
+ *
+ *-----------------------------------------------------------------------
+ */
+static int
+SuffRebuildGraph(void *transformp, void *sp)
+{
+ GNode *transform = (GNode *)transformp;
+ Suff *s = (Suff *)sp;
+ char *cp;
+ LstNode ln;
+ Suff *s2;
+ SuffixCmpData sd;
+
+ /*
+ * First see if it is a transformation from this suffix.
+ */
+ cp = UNCONST(SuffStrIsPrefix(s->name, transform->name));
+ if (cp != NULL) {
+ ln = Lst_Find(sufflist, cp, SuffSuffHasNameP);
+ if (ln != NULL) {
+ /*
+ * Found target. Link in and return, since it can't be anything
+ * else.
+ */
+ s2 = (Suff *)Lst_Datum(ln);
+ SuffInsert(s2->children, s);
+ SuffInsert(s->parents, s2);
+ return(0);
+ }
+ }
+
+ /*
+ * Not from, maybe to?
+ */
+ sd.len = strlen(transform->name);
+ sd.ename = transform->name + sd.len;
+ cp = SuffSuffIsSuffix(s, &sd);
+ if (cp != NULL) {
+ /*
+ * Null-terminate the source suffix in order to find it.
+ */
+ cp[1] = '\0';
+ ln = Lst_Find(sufflist, transform->name, SuffSuffHasNameP);
+ /*
+ * Replace the start of the target suffix
+ */
+ cp[1] = s->name[0];
+ if (ln != NULL) {
+ /*
+ * Found it -- establish the proper relationship
+ */
+ s2 = (Suff *)Lst_Datum(ln);
+ SuffInsert(s->children, s2);
+ SuffInsert(s2->parents, s);
+ }
+ }
+ return(0);
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * SuffScanTargets --
+ * Called from Suff_AddSuffix via Lst_ForEach to search through the
+ * list of existing targets and find if any of the existing targets
+ * can be turned into a transformation rule.
+ *
+ * Results:
+ * 1 if a new main target has been selected, 0 otherwise.
+ *
+ * Side Effects:
+ * If such a target is found and the target is the current main
+ * target, the main target is set to NULL and the next target
+ * examined (if that exists) becomes the main target.
+ *
+ *-----------------------------------------------------------------------
+ */
+static int
+SuffScanTargets(void *targetp, void *gsp)
+{
+ GNode *target = (GNode *)targetp;
+ GNodeSuff *gs = (GNodeSuff *)gsp;
+ Suff *s, *t;
+ char *ptr;
+
+ if (*gs->gn == NULL && gs->r && (target->type & OP_NOTARGET) == 0) {
+ *gs->gn = target;
+ Targ_SetMain(target);
+ return 1;
+ }
+
+ if ((unsigned int)target->type == OP_TRANSFORM)
+ return 0;
+
+ if ((ptr = strstr(target->name, gs->s->name)) == NULL ||
+ ptr == target->name)
+ return 0;
+
+ if (SuffParseTransform(target->name, &s, &t)) {
+ if (*gs->gn == target) {
+ gs->r = TRUE;
+ *gs->gn = NULL;
+ Targ_SetMain(NULL);
+ }
+ Lst_Destroy(target->children, NULL);
+ target->children = Lst_Init(FALSE);
+ target->type = OP_TRANSFORM;
+ /*
+ * link the two together in the proper relationship and order
+ */
+ if (DEBUG(SUFF)) {
+ fprintf(debug_file, "defining transformation from `%s' to `%s'\n",
+ s->name, t->name);
+ }
+ SuffInsert(t->children, s);
+ SuffInsert(s->parents, t);
+ }
+ return 0;
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * Suff_AddSuffix --
+ * Add the suffix in string to the end of the list of known suffixes.
+ * Should we restructure the suffix graph? Make doesn't...
+ *
+ * Input:
+ * str the name of the suffix to add
+ *
+ * Results:
+ * None
+ *
+ * Side Effects:
+ * A GNode is created for the suffix and a Suff structure is created and
+ * added to the suffixes list unless the suffix was already known.
+ * The mainNode passed can be modified if a target mutated into a
+ * transform and that target happened to be the main target.
+ *-----------------------------------------------------------------------
+ */
+void
+Suff_AddSuffix(char *str, GNode **gn)
+{
+ Suff *s; /* new suffix descriptor */
+ LstNode ln;
+ GNodeSuff gs;
+
+ ln = Lst_Find(sufflist, str, SuffSuffHasNameP);
+ if (ln == NULL) {
+ s = bmake_malloc(sizeof(Suff));
+
+ s->name = bmake_strdup(str);
+ s->nameLen = strlen(s->name);
+ s->searchPath = Lst_Init(FALSE);
+ s->children = Lst_Init(FALSE);
+ s->parents = Lst_Init(FALSE);
+ s->ref = Lst_Init(FALSE);
+ s->sNum = sNum++;
+ s->flags = 0;
+ s->refCount = 1;
+
+ (void)Lst_AtEnd(sufflist, s);
+ /*
+ * We also look at our existing targets list to see if adding
+ * this suffix will make one of our current targets mutate into
+ * a suffix rule. This is ugly, but other makes treat all targets
+ * that start with a . as suffix rules.
+ */
+ gs.gn = gn;
+ gs.s = s;
+ gs.r = FALSE;
+ Lst_ForEach(Targ_List(), SuffScanTargets, &gs);
+ /*
+ * Look for any existing transformations from or to this suffix.
+ * XXX: Only do this after a Suff_ClearSuffixes?
+ */
+ Lst_ForEach(transforms, SuffRebuildGraph, s);
+ }
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * Suff_GetPath --
+ * Return the search path for the given suffix, if it's defined.
+ *
+ * Results:
+ * The searchPath for the desired suffix or NULL if the suffix isn't
+ * defined.
+ *
+ * Side Effects:
+ * None
+ *-----------------------------------------------------------------------
+ */
+Lst
+Suff_GetPath(char *sname)
+{
+ LstNode ln;
+ Suff *s;
+
+ ln = Lst_Find(sufflist, sname, SuffSuffHasNameP);
+ if (ln == NULL) {
+ return NULL;
+ } else {
+ s = (Suff *)Lst_Datum(ln);
+ return (s->searchPath);
+ }
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * Suff_DoPaths --
+ * Extend the search paths for all suffixes to include the default
+ * search path.
+ *
+ * Results:
+ * None.
+ *
+ * Side Effects:
+ * The searchPath field of all the suffixes is extended by the
+ * directories in dirSearchPath. If paths were specified for the
+ * ".h" suffix, the directories are stuffed into a global variable
+ * called ".INCLUDES" with each directory preceded by a -I. The same
+ * is done for the ".a" suffix, except the variable is called
+ * ".LIBS" and the flag is -L.
+ *-----------------------------------------------------------------------
+ */
+void
+Suff_DoPaths(void)
+{
+ Suff *s;
+ LstNode ln;
+ char *ptr;
+ Lst inIncludes; /* Cumulative .INCLUDES path */
+ Lst inLibs; /* Cumulative .LIBS path */
+
+ if (Lst_Open(sufflist) == FAILURE) {
+ return;
+ }
+
+ inIncludes = Lst_Init(FALSE);
+ inLibs = Lst_Init(FALSE);
+
+ while ((ln = Lst_Next(sufflist)) != NULL) {
+ s = (Suff *)Lst_Datum(ln);
+ if (!Lst_IsEmpty (s->searchPath)) {
+#ifdef INCLUDES
+ if (s->flags & SUFF_INCLUDE) {
+ Dir_Concat(inIncludes, s->searchPath);
+ }
+#endif /* INCLUDES */
+#ifdef LIBRARIES
+ if (s->flags & SUFF_LIBRARY) {
+ Dir_Concat(inLibs, s->searchPath);
+ }
+#endif /* LIBRARIES */
+ Dir_Concat(s->searchPath, dirSearchPath);
+ } else {
+ Lst_Destroy(s->searchPath, Dir_Destroy);
+ s->searchPath = Lst_Duplicate(dirSearchPath, Dir_CopyDir);
+ }
+ }
+
+ Var_Set(".INCLUDES", ptr = Dir_MakeFlags("-I", inIncludes), VAR_GLOBAL, 0);
+ free(ptr);
+ Var_Set(".LIBS", ptr = Dir_MakeFlags("-L", inLibs), VAR_GLOBAL, 0);
+ free(ptr);
+
+ Lst_Destroy(inIncludes, Dir_Destroy);
+ Lst_Destroy(inLibs, Dir_Destroy);
+
+ Lst_Close(sufflist);
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * Suff_AddInclude --
+ * Add the given suffix as a type of file which gets included.
+ * Called from the parse module when a .INCLUDES line is parsed.
+ * The suffix must have already been defined.
+ *
+ * Input:
+ * sname Name of the suffix to mark
+ *
+ * Results:
+ * None.
+ *
+ * Side Effects:
+ * The SUFF_INCLUDE bit is set in the suffix's flags field
+ *
+ *-----------------------------------------------------------------------
+ */
+void
+Suff_AddInclude(char *sname)
+{
+ LstNode ln;
+ Suff *s;
+
+ ln = Lst_Find(sufflist, sname, SuffSuffHasNameP);
+ if (ln != NULL) {
+ s = (Suff *)Lst_Datum(ln);
+ s->flags |= SUFF_INCLUDE;
+ }
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * Suff_AddLib --
+ * Add the given suffix as a type of file which is a library.
+ * Called from the parse module when parsing a .LIBS line. The
+ * suffix must have been defined via .SUFFIXES before this is
+ * called.
+ *
+ * Input:
+ * sname Name of the suffix to mark
+ *
+ * Results:
+ * None.
+ *
+ * Side Effects:
+ * The SUFF_LIBRARY bit is set in the suffix's flags field
+ *
+ *-----------------------------------------------------------------------
+ */
+void
+Suff_AddLib(char *sname)
+{
+ LstNode ln;
+ Suff *s;
+
+ ln = Lst_Find(sufflist, sname, SuffSuffHasNameP);
+ if (ln != NULL) {
+ s = (Suff *)Lst_Datum(ln);
+ s->flags |= SUFF_LIBRARY;
+ }
+}
+
+ /********** Implicit Source Search Functions *********/
+
+/*-
+ *-----------------------------------------------------------------------
+ * SuffAddSrc --
+ * Add a suffix as a Src structure to the given list with its parent
+ * being the given Src structure. If the suffix is the null suffix,
+ * the prefix is used unaltered as the file name in the Src structure.
+ *
+ * Input:
+ * sp suffix for which to create a Src structure
+ * lsp list and parent for the new Src
+ *
+ * Results:
+ * always returns 0
+ *
+ * Side Effects:
+ * A Src structure is created and tacked onto the end of the list
+ *-----------------------------------------------------------------------
+ */
+static int
+SuffAddSrc(void *sp, void *lsp)
+{
+ Suff *s = (Suff *)sp;
+ LstSrc *ls = (LstSrc *)lsp;
+ Src *s2; /* new Src structure */
+ Src *targ; /* Target structure */
+
+ targ = ls->s;
+
+ if ((s->flags & SUFF_NULL) && (*s->name != '\0')) {
+ /*
+ * If the suffix has been marked as the NULL suffix, also create a Src
+ * structure for a file with no suffix attached. Two birds, and all
+ * that...
+ */
+ s2 = bmake_malloc(sizeof(Src));
+ s2->file = bmake_strdup(targ->pref);
+ s2->pref = targ->pref;
+ s2->parent = targ;
+ s2->node = NULL;
+ s2->suff = s;
+ s->refCount++;
+ s2->children = 0;
+ targ->children += 1;
+ (void)Lst_AtEnd(ls->l, s2);
+#ifdef DEBUG_SRC
+ s2->cp = Lst_Init(FALSE);
+ Lst_AtEnd(targ->cp, s2);
+ fprintf(debug_file, "1 add %p %p to %p:", targ, s2, ls->l);
+ Lst_ForEach(ls->l, PrintAddr, NULL);
+ fprintf(debug_file, "\n");
+#endif
+ }
+ s2 = bmake_malloc(sizeof(Src));
+ s2->file = str_concat(targ->pref, s->name, 0);
+ s2->pref = targ->pref;
+ s2->parent = targ;
+ s2->node = NULL;
+ s2->suff = s;
+ s->refCount++;
+ s2->children = 0;
+ targ->children += 1;
+ (void)Lst_AtEnd(ls->l, s2);
+#ifdef DEBUG_SRC
+ s2->cp = Lst_Init(FALSE);
+ Lst_AtEnd(targ->cp, s2);
+ fprintf(debug_file, "2 add %p %p to %p:", targ, s2, ls->l);
+ Lst_ForEach(ls->l, PrintAddr, NULL);
+ fprintf(debug_file, "\n");
+#endif
+
+ return(0);
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * SuffAddLevel --
+ * Add all the children of targ as Src structures to the given list
+ *
+ * Input:
+ * l list to which to add the new level
+ * targ Src structure to use as the parent
+ *
+ * Results:
+ * None
+ *
+ * Side Effects:
+ * Lots of structures are created and added to the list
+ *-----------------------------------------------------------------------
+ */
+static void
+SuffAddLevel(Lst l, Src *targ)
+{
+ LstSrc ls;
+
+ ls.s = targ;
+ ls.l = l;
+
+ Lst_ForEach(targ->suff->children, SuffAddSrc, &ls);
+}
+
+/*-
+ *----------------------------------------------------------------------
+ * SuffRemoveSrc --
+ * Free all src structures in list that don't have a reference count
+ *
+ * Results:
+ * Ture if an src was removed
+ *
+ * Side Effects:
+ * The memory is free'd.
+ *----------------------------------------------------------------------
+ */
+static int
+SuffRemoveSrc(Lst l)
+{
+ LstNode ln;
+ Src *s;
+ int t = 0;
+
+ if (Lst_Open(l) == FAILURE) {
+ return 0;
+ }
+#ifdef DEBUG_SRC
+ fprintf(debug_file, "cleaning %lx: ", (unsigned long) l);
+ Lst_ForEach(l, PrintAddr, NULL);
+ fprintf(debug_file, "\n");
+#endif
+
+
+ while ((ln = Lst_Next(l)) != NULL) {
+ s = (Src *)Lst_Datum(ln);
+ if (s->children == 0) {
+ free(s->file);
+ if (!s->parent)
+ free(s->pref);
+ else {
+#ifdef DEBUG_SRC
+ LstNode ln2 = Lst_Member(s->parent->cp, s);
+ if (ln2 != NULL)
+ Lst_Remove(s->parent->cp, ln2);
+#endif
+ --s->parent->children;
+ }
+#ifdef DEBUG_SRC
+ fprintf(debug_file, "free: [l=%p] p=%p %d\n", l, s, s->children);
+ Lst_Destroy(s->cp, NULL);
+#endif
+ Lst_Remove(l, ln);
+ free(s);
+ t |= 1;
+ Lst_Close(l);
+ return TRUE;
+ }
+#ifdef DEBUG_SRC
+ else {
+ fprintf(debug_file, "keep: [l=%p] p=%p %d: ", l, s, s->children);
+ Lst_ForEach(s->cp, PrintAddr, NULL);
+ fprintf(debug_file, "\n");
+ }
+#endif
+ }
+
+ Lst_Close(l);
+
+ return t;
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * SuffFindThem --
+ * Find the first existing file/target in the list srcs
+ *
+ * Input:
+ * srcs list of Src structures to search through
+ *
+ * Results:
+ * The lowest structure in the chain of transformations
+ *
+ * Side Effects:
+ * None
+ *-----------------------------------------------------------------------
+ */
+static Src *
+SuffFindThem(Lst srcs, Lst slst)
+{
+ Src *s; /* current Src */
+ Src *rs; /* returned Src */
+ char *ptr;
+
+ rs = NULL;
+
+ while (!Lst_IsEmpty (srcs)) {
+ s = (Src *)Lst_DeQueue(srcs);
+
+ if (DEBUG(SUFF)) {
+ fprintf(debug_file, "\ttrying %s...", s->file);
+ }
+
+ /*
+ * A file is considered to exist if either a node exists in the
+ * graph for it or the file actually exists.
+ */
+ if (Targ_FindNode(s->file, TARG_NOCREATE) != NULL) {
+#ifdef DEBUG_SRC
+ fprintf(debug_file, "remove %p from %p\n", s, srcs);
+#endif
+ rs = s;
+ break;
+ }
+
+ if ((ptr = Dir_FindFile(s->file, s->suff->searchPath)) != NULL) {
+ rs = s;
+#ifdef DEBUG_SRC
+ fprintf(debug_file, "remove %p from %p\n", s, srcs);
+#endif
+ free(ptr);
+ break;
+ }
+
+ if (DEBUG(SUFF)) {
+ fprintf(debug_file, "not there\n");
+ }
+
+ SuffAddLevel(srcs, s);
+ Lst_AtEnd(slst, s);
+ }
+
+ if (DEBUG(SUFF) && rs) {
+ fprintf(debug_file, "got it\n");
+ }
+ return (rs);
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * SuffFindCmds --
+ * See if any of the children of the target in the Src structure is
+ * one from which the target can be transformed. If there is one,
+ * a Src structure is put together for it and returned.
+ *
+ * Input:
+ * targ Src structure to play with
+ *
+ * Results:
+ * The Src structure of the "winning" child, or NULL if no such beast.
+ *
+ * Side Effects:
+ * A Src structure may be allocated.
+ *
+ *-----------------------------------------------------------------------
+ */
+static Src *
+SuffFindCmds(Src *targ, Lst slst)
+{
+ LstNode ln; /* General-purpose list node */
+ GNode *t, /* Target GNode */
+ *s; /* Source GNode */
+ int prefLen;/* The length of the defined prefix */
+ Suff *suff; /* Suffix on matching beastie */
+ Src *ret; /* Return value */
+ char *cp;
+
+ t = targ->node;
+ (void)Lst_Open(t->children);
+ prefLen = strlen(targ->pref);
+
+ for (;;) {
+ ln = Lst_Next(t->children);
+ if (ln == NULL) {
+ Lst_Close(t->children);
+ return NULL;
+ }
+ s = (GNode *)Lst_Datum(ln);
+
+ if (s->type & OP_OPTIONAL && Lst_IsEmpty(t->commands)) {
+ /*
+ * We haven't looked to see if .OPTIONAL files exist yet, so
+ * don't use one as the implicit source.
+ * This allows us to use .OPTIONAL in .depend files so make won't
+ * complain "don't know how to make xxx.h' when a dependent file
+ * has been moved/deleted.
+ */
+ continue;
+ }
+
+ cp = strrchr(s->name, '/');
+ if (cp == NULL) {
+ cp = s->name;
+ } else {
+ cp++;
+ }
+ if (strncmp(cp, targ->pref, prefLen) != 0)
+ continue;
+ /*
+ * The node matches the prefix ok, see if it has a known
+ * suffix.
+ */
+ ln = Lst_Find(sufflist, &cp[prefLen], SuffSuffHasNameP);
+ if (ln == NULL)
+ continue;
+ /*
+ * It even has a known suffix, see if there's a transformation
+ * defined between the node's suffix and the target's suffix.
+ *
+ * XXX: Handle multi-stage transformations here, too.
+ */
+ suff = (Suff *)Lst_Datum(ln);
+
+ if (Lst_Member(suff->parents, targ->suff) != NULL)
+ break;
+ }
+
+ /*
+ * Hot Damn! Create a new Src structure to describe
+ * this transformation (making sure to duplicate the
+ * source node's name so Suff_FindDeps can free it
+ * again (ick)), and return the new structure.
+ */
+ ret = bmake_malloc(sizeof(Src));
+ ret->file = bmake_strdup(s->name);
+ ret->pref = targ->pref;
+ ret->suff = suff;
+ suff->refCount++;
+ ret->parent = targ;
+ ret->node = s;
+ ret->children = 0;
+ targ->children += 1;
+#ifdef DEBUG_SRC
+ ret->cp = Lst_Init(FALSE);
+ fprintf(debug_file, "3 add %p %p\n", targ, ret);
+ Lst_AtEnd(targ->cp, ret);
+#endif
+ Lst_AtEnd(slst, ret);
+ if (DEBUG(SUFF)) {
+ fprintf(debug_file, "\tusing existing source %s\n", s->name);
+ }
+ return (ret);
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * SuffExpandChildren --
+ * Expand the names of any children of a given node that contain
+ * variable invocations or file wildcards into actual targets.
+ *
+ * Input:
+ * cln Child to examine
+ * pgn Parent node being processed
+ *
+ * Results:
+ * === 0 (continue)
+ *
+ * Side Effects:
+ * The expanded node is removed from the parent's list of children,
+ * and the parent's unmade counter is decremented, but other nodes
+ * may be added.
+ *
+ *-----------------------------------------------------------------------
+ */
+static void
+SuffExpandChildren(LstNode cln, GNode *pgn)
+{
+ GNode *cgn = (GNode *)Lst_Datum(cln);
+ GNode *gn; /* New source 8) */
+ char *cp; /* Expanded value */
+
+ if (!Lst_IsEmpty(cgn->order_pred) || !Lst_IsEmpty(cgn->order_succ))
+ /* It is all too hard to process the result of .ORDER */
+ return;
+
+ if (cgn->type & OP_WAIT)
+ /* Ignore these (& OP_PHONY ?) */
+ return;
+
+ /*
+ * First do variable expansion -- this takes precedence over
+ * wildcard expansion. If the result contains wildcards, they'll be gotten
+ * to later since the resulting words are tacked on to the end of
+ * the children list.
+ */
+ if (strchr(cgn->name, '$') == NULL) {
+ SuffExpandWildcards(cln, pgn);
+ return;
+ }
+
+ if (DEBUG(SUFF)) {
+ fprintf(debug_file, "Expanding \"%s\"...", cgn->name);
+ }
+ cp = Var_Subst(NULL, cgn->name, pgn, VARF_UNDEFERR|VARF_WANTRES);
+
+ if (cp != NULL) {
+ Lst members = Lst_Init(FALSE);
+
+ if (cgn->type & OP_ARCHV) {
+ /*
+ * Node was an archive(member) target, so we want to call
+ * on the Arch module to find the nodes for us, expanding
+ * variables in the parent's context.
+ */
+ char *sacrifice = cp;
+
+ (void)Arch_ParseArchive(&sacrifice, members, pgn);
+ } else {
+ /*
+ * Break the result into a vector of strings whose nodes
+ * we can find, then add those nodes to the members list.
+ * Unfortunately, we can't use brk_string b/c it
+ * doesn't understand about variable specifications with
+ * spaces in them...
+ */
+ char *start;
+ char *initcp = cp; /* For freeing... */
+
+ for (start = cp; *start == ' ' || *start == '\t'; start++)
+ continue;
+ for (cp = start; *cp != '\0'; cp++) {
+ if (*cp == ' ' || *cp == '\t') {
+ /*
+ * White-space -- terminate element, find the node,
+ * add it, skip any further spaces.
+ */
+ *cp++ = '\0';
+ gn = Targ_FindNode(start, TARG_CREATE);
+ (void)Lst_AtEnd(members, gn);
+ while (*cp == ' ' || *cp == '\t') {
+ cp++;
+ }
+ /*
+ * Adjust cp for increment at start of loop, but
+ * set start to first non-space.
+ */
+ start = cp--;
+ } else if (*cp == '$') {
+ /*
+ * Start of a variable spec -- contact variable module
+ * to find the end so we can skip over it.
+ */
+ char *junk;
+ int len;
+ void *freeIt;
+
+ junk = Var_Parse(cp, pgn, VARF_UNDEFERR|VARF_WANTRES,
+ &len, &freeIt);
+ if (junk != var_Error) {
+ cp += len - 1;
+ }
+
+ free(freeIt);
+ } else if (*cp == '\\' && cp[1] != '\0') {
+ /*
+ * Escaped something -- skip over it
+ */
+ cp++;
+ }
+ }
+
+ if (cp != start) {
+ /*
+ * Stuff left over -- add it to the list too
+ */
+ gn = Targ_FindNode(start, TARG_CREATE);
+ (void)Lst_AtEnd(members, gn);
+ }
+ /*
+ * Point cp back at the beginning again so the variable value
+ * can be freed.
+ */
+ cp = initcp;
+ }
+
+ /*
+ * Add all elements of the members list to the parent node.
+ */
+ while(!Lst_IsEmpty(members)) {
+ gn = (GNode *)Lst_DeQueue(members);
+
+ if (DEBUG(SUFF)) {
+ fprintf(debug_file, "%s...", gn->name);
+ }
+ /* Add gn to the parents child list before the original child */
+ (void)Lst_InsertBefore(pgn->children, cln, gn);
+ (void)Lst_AtEnd(gn->parents, pgn);
+ pgn->unmade++;
+ /* Expand wildcards on new node */
+ SuffExpandWildcards(Lst_Prev(cln), pgn);
+ }
+ Lst_Destroy(members, NULL);
+
+ /*
+ * Free the result
+ */
+ free(cp);
+ }
+ if (DEBUG(SUFF)) {
+ fprintf(debug_file, "\n");
+ }
+
+ /*
+ * Now the source is expanded, remove it from the list of children to
+ * keep it from being processed.
+ */
+ pgn->unmade--;
+ Lst_Remove(pgn->children, cln);
+ Lst_Remove(cgn->parents, Lst_Member(cgn->parents, pgn));
+}
+
+static void
+SuffExpandWildcards(LstNode cln, GNode *pgn)
+{
+ GNode *cgn = (GNode *)Lst_Datum(cln);
+ GNode *gn; /* New source 8) */
+ char *cp; /* Expanded value */
+ Lst explist; /* List of expansions */
+
+ if (!Dir_HasWildcards(cgn->name))
+ return;
+
+ /*
+ * Expand the word along the chosen path
+ */
+ explist = Lst_Init(FALSE);
+ Dir_Expand(cgn->name, Suff_FindPath(cgn), explist);
+
+ while (!Lst_IsEmpty(explist)) {
+ /*
+ * Fetch next expansion off the list and find its GNode
+ */
+ cp = (char *)Lst_DeQueue(explist);
+
+ if (DEBUG(SUFF)) {
+ fprintf(debug_file, "%s...", cp);
+ }
+ gn = Targ_FindNode(cp, TARG_CREATE);
+
+ /* Add gn to the parents child list before the original child */
+ (void)Lst_InsertBefore(pgn->children, cln, gn);
+ (void)Lst_AtEnd(gn->parents, pgn);
+ pgn->unmade++;
+ }
+
+ /*
+ * Nuke what's left of the list
+ */
+ Lst_Destroy(explist, NULL);
+
+ if (DEBUG(SUFF)) {
+ fprintf(debug_file, "\n");
+ }
+
+ /*
+ * Now the source is expanded, remove it from the list of children to
+ * keep it from being processed.
+ */
+ pgn->unmade--;
+ Lst_Remove(pgn->children, cln);
+ Lst_Remove(cgn->parents, Lst_Member(cgn->parents, pgn));
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * Suff_FindPath --
+ * Find a path along which to expand the node.
+ *
+ * If the word has a known suffix, use that path.
+ * If it has no known suffix, use the default system search path.
+ *
+ * Input:
+ * gn Node being examined
+ *
+ * Results:
+ * The appropriate path to search for the GNode.
+ *
+ * Side Effects:
+ * XXX: We could set the suffix here so that we don't have to scan
+ * again.
+ *
+ *-----------------------------------------------------------------------
+ */
+Lst
+Suff_FindPath(GNode* gn)
+{
+ Suff *suff = gn->suffix;
+
+ if (suff == NULL) {
+ SuffixCmpData sd; /* Search string data */
+ LstNode ln;
+ sd.len = strlen(gn->name);
+ sd.ename = gn->name + sd.len;
+ ln = Lst_Find(sufflist, &sd, SuffSuffIsSuffixP);
+
+ if (DEBUG(SUFF)) {
+ fprintf(debug_file, "Wildcard expanding \"%s\"...", gn->name);
+ }
+ if (ln != NULL)
+ suff = (Suff *)Lst_Datum(ln);
+ /* XXX: Here we can save the suffix so we don't have to do this again */
+ }
+
+ if (suff != NULL) {
+ if (DEBUG(SUFF)) {
+ fprintf(debug_file, "suffix is \"%s\"...", suff->name);
+ }
+ return suff->searchPath;
+ } else {
+ /*
+ * Use default search path
+ */
+ return dirSearchPath;
+ }
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * SuffApplyTransform --
+ * Apply a transformation rule, given the source and target nodes
+ * and suffixes.
+ *
+ * Input:
+ * tGn Target node
+ * sGn Source node
+ * t Target suffix
+ * s Source suffix
+ *
+ * Results:
+ * TRUE if successful, FALSE if not.
+ *
+ * Side Effects:
+ * The source and target are linked and the commands from the
+ * transformation are added to the target node's commands list.
+ * All attributes but OP_DEPMASK and OP_TRANSFORM are applied
+ * to the target. The target also inherits all the sources for
+ * the transformation rule.
+ *
+ *-----------------------------------------------------------------------
+ */
+static Boolean
+SuffApplyTransform(GNode *tGn, GNode *sGn, Suff *t, Suff *s)
+{
+ LstNode ln, nln; /* General node */
+ char *tname; /* Name of transformation rule */
+ GNode *gn; /* Node for same */
+
+ /*
+ * Form the proper links between the target and source.
+ */
+ (void)Lst_AtEnd(tGn->children, sGn);
+ (void)Lst_AtEnd(sGn->parents, tGn);
+ tGn->unmade += 1;
+
+ /*
+ * Locate the transformation rule itself
+ */
+ tname = str_concat(s->name, t->name, 0);
+ ln = Lst_Find(transforms, tname, SuffGNHasNameP);
+ free(tname);
+
+ if (ln == NULL) {
+ /*
+ * Not really such a transformation rule (can happen when we're
+ * called to link an OP_MEMBER and OP_ARCHV node), so return
+ * FALSE.
+ */
+ return(FALSE);
+ }
+
+ gn = (GNode *)Lst_Datum(ln);
+
+ if (DEBUG(SUFF)) {
+ fprintf(debug_file, "\tapplying %s -> %s to \"%s\"\n", s->name, t->name, tGn->name);
+ }
+
+ /*
+ * Record last child for expansion purposes
+ */
+ ln = Lst_Last(tGn->children);
+
+ /*
+ * Pass the buck to Make_HandleUse to apply the rule
+ */
+ (void)Make_HandleUse(gn, tGn);
+
+ /*
+ * Deal with wildcards and variables in any acquired sources
+ */
+ for (ln = Lst_Succ(ln); ln != NULL; ln = nln) {
+ nln = Lst_Succ(ln);
+ SuffExpandChildren(ln, tGn);
+ }
+
+ /*
+ * Keep track of another parent to which this beast is transformed so
+ * the .IMPSRC variable can be set correctly for the parent.
+ */
+ (void)Lst_AtEnd(sGn->iParents, tGn);
+
+ return(TRUE);
+}
+
+
+/*-
+ *-----------------------------------------------------------------------
+ * SuffFindArchiveDeps --
+ * Locate dependencies for an OP_ARCHV node.
+ *
+ * Input:
+ * gn Node for which to locate dependencies
+ *
+ * Results:
+ * None
+ *
+ * Side Effects:
+ * Same as Suff_FindDeps
+ *
+ *-----------------------------------------------------------------------
+ */
+static void
+SuffFindArchiveDeps(GNode *gn, Lst slst)
+{
+ char *eoarch; /* End of archive portion */
+ char *eoname; /* End of member portion */
+ GNode *mem; /* Node for member */
+ static const char *copy[] = {
+ /* Variables to be copied from the member node */
+ TARGET, /* Must be first */
+ PREFIX, /* Must be second */
+ };
+ LstNode ln, nln; /* Next suffix node to check */
+ int i; /* Index into copy and vals */
+ Suff *ms; /* Suffix descriptor for member */
+ char *name; /* Start of member's name */
+
+ /*
+ * The node is an archive(member) pair. so we must find a
+ * suffix for both of them.
+ */
+ eoarch = strchr(gn->name, '(');
+ eoname = strchr(eoarch, ')');
+
+ /*
+ * Caller guarantees the format `libname(member)', via
+ * Arch_ParseArchive.
+ */
+ assert(eoarch != NULL);
+ assert(eoname != NULL);
+
+ *eoname = '\0'; /* Nuke parentheses during suffix search */
+ *eoarch = '\0'; /* So a suffix can be found */
+
+ name = eoarch + 1;
+
+ /*
+ * To simplify things, call Suff_FindDeps recursively on the member now,
+ * so we can simply compare the member's .PREFIX and .TARGET variables
+ * to locate its suffix. This allows us to figure out the suffix to
+ * use for the archive without having to do a quadratic search over the
+ * suffix list, backtracking for each one...
+ */
+ mem = Targ_FindNode(name, TARG_CREATE);
+ SuffFindDeps(mem, slst);
+
+ /*
+ * Create the link between the two nodes right off
+ */
+ (void)Lst_AtEnd(gn->children, mem);
+ (void)Lst_AtEnd(mem->parents, gn);
+ gn->unmade += 1;
+
+ /*
+ * Copy in the variables from the member node to this one.
+ */
+ for (i = (sizeof(copy)/sizeof(copy[0]))-1; i >= 0; i--) {
+ char *p1;
+ Var_Set(copy[i], Var_Value(copy[i], mem, &p1), gn, 0);
+ free(p1);
+
+ }
+
+ ms = mem->suffix;
+ if (ms == NULL) {
+ /*
+ * Didn't know what it was -- use .NULL suffix if not in make mode
+ */
+ if (DEBUG(SUFF)) {
+ fprintf(debug_file, "using null suffix\n");
+ }
+ ms = suffNull;
+ }
+
+
+ /*
+ * Set the other two local variables required for this target.
+ */
+ Var_Set(MEMBER, name, gn, 0);
+ Var_Set(ARCHIVE, gn->name, gn, 0);
+
+ /*
+ * Set $@ for compatibility with other makes
+ */
+ Var_Set(TARGET, gn->name, gn, 0);
+
+ /*
+ * Now we've got the important local variables set, expand any sources
+ * that still contain variables or wildcards in their names.
+ */
+ for (ln = Lst_First(gn->children); ln != NULL; ln = nln) {
+ nln = Lst_Succ(ln);
+ SuffExpandChildren(ln, gn);
+ }
+
+ if (ms != NULL) {
+ /*
+ * Member has a known suffix, so look for a transformation rule from
+ * it to a possible suffix of the archive. Rather than searching
+ * through the entire list, we just look at suffixes to which the
+ * member's suffix may be transformed...
+ */
+ SuffixCmpData sd; /* Search string data */
+
+ /*
+ * Use first matching suffix...
+ */
+ sd.len = eoarch - gn->name;
+ sd.ename = eoarch;
+ ln = Lst_Find(ms->parents, &sd, SuffSuffIsSuffixP);
+
+ if (ln != NULL) {
+ /*
+ * Got one -- apply it
+ */
+ if (!SuffApplyTransform(gn, mem, (Suff *)Lst_Datum(ln), ms) &&
+ DEBUG(SUFF))
+ {
+ fprintf(debug_file, "\tNo transformation from %s -> %s\n",
+ ms->name, ((Suff *)Lst_Datum(ln))->name);
+ }
+ }
+ }
+
+ /*
+ * Replace the opening and closing parens now we've no need of the separate
+ * pieces.
+ */
+ *eoarch = '('; *eoname = ')';
+
+ /*
+ * Pretend gn appeared to the left of a dependency operator so
+ * the user needn't provide a transformation from the member to the
+ * archive.
+ */
+ if (OP_NOP(gn->type)) {
+ gn->type |= OP_DEPENDS;
+ }
+
+ /*
+ * Flag the member as such so we remember to look in the archive for
+ * its modification time. The OP_JOIN | OP_MADE is needed because this
+ * target should never get made.
+ */
+ mem->type |= OP_MEMBER | OP_JOIN | OP_MADE;
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * SuffFindNormalDeps --
+ * Locate implicit dependencies for regular targets.
+ *
+ * Input:
+ * gn Node for which to find sources
+ *
+ * Results:
+ * None.
+ *
+ * Side Effects:
+ * Same as Suff_FindDeps...
+ *
+ *-----------------------------------------------------------------------
+ */
+static void
+SuffFindNormalDeps(GNode *gn, Lst slst)
+{
+ char *eoname; /* End of name */
+ char *sopref; /* Start of prefix */
+ LstNode ln, nln; /* Next suffix node to check */
+ Lst srcs; /* List of sources at which to look */
+ Lst targs; /* List of targets to which things can be
+ * transformed. They all have the same file,
+ * but different suff and pref fields */
+ Src *bottom; /* Start of found transformation path */
+ Src *src; /* General Src pointer */
+ char *pref; /* Prefix to use */
+ Src *targ; /* General Src target pointer */
+ SuffixCmpData sd; /* Search string data */
+
+
+ sd.len = strlen(gn->name);
+ sd.ename = eoname = gn->name + sd.len;
+
+ sopref = gn->name;
+
+ /*
+ * Begin at the beginning...
+ */
+ ln = Lst_First(sufflist);
+ srcs = Lst_Init(FALSE);
+ targs = Lst_Init(FALSE);
+
+ /*
+ * We're caught in a catch-22 here. On the one hand, we want to use any
+ * transformation implied by the target's sources, but we can't examine
+ * the sources until we've expanded any variables/wildcards they may hold,
+ * and we can't do that until we've set up the target's local variables
+ * and we can't do that until we know what the proper suffix for the
+ * target is (in case there are two suffixes one of which is a suffix of
+ * the other) and we can't know that until we've found its implied
+ * source, which we may not want to use if there's an existing source
+ * that implies a different transformation.
+ *
+ * In an attempt to get around this, which may not work all the time,
+ * but should work most of the time, we look for implied sources first,
+ * checking transformations to all possible suffixes of the target,
+ * use what we find to set the target's local variables, expand the
+ * children, then look for any overriding transformations they imply.
+ * Should we find one, we discard the one we found before.
+ */
+ bottom = NULL;
+ targ = NULL;
+
+ if (!(gn->type & OP_PHONY)) {
+
+ while (ln != NULL) {
+ /*
+ * Look for next possible suffix...
+ */
+ ln = Lst_FindFrom(sufflist, ln, &sd, SuffSuffIsSuffixP);
+
+ if (ln != NULL) {
+ int prefLen; /* Length of the prefix */
+
+ /*
+ * Allocate a Src structure to which things can be transformed
+ */
+ targ = bmake_malloc(sizeof(Src));
+ targ->file = bmake_strdup(gn->name);
+ targ->suff = (Suff *)Lst_Datum(ln);
+ targ->suff->refCount++;
+ targ->node = gn;
+ targ->parent = NULL;
+ targ->children = 0;
+#ifdef DEBUG_SRC
+ targ->cp = Lst_Init(FALSE);
+#endif
+
+ /*
+ * Allocate room for the prefix, whose end is found by
+ * subtracting the length of the suffix from
+ * the end of the name.
+ */
+ prefLen = (eoname - targ->suff->nameLen) - sopref;
+ targ->pref = bmake_malloc(prefLen + 1);
+ memcpy(targ->pref, sopref, prefLen);
+ targ->pref[prefLen] = '\0';
+
+ /*
+ * Add nodes from which the target can be made
+ */
+ SuffAddLevel(srcs, targ);
+
+ /*
+ * Record the target so we can nuke it
+ */
+ (void)Lst_AtEnd(targs, targ);
+
+ /*
+ * Search from this suffix's successor...
+ */
+ ln = Lst_Succ(ln);
+ }
+ }
+
+ /*
+ * Handle target of unknown suffix...
+ */
+ if (Lst_IsEmpty(targs) && suffNull != NULL) {
+ if (DEBUG(SUFF)) {
+ fprintf(debug_file, "\tNo known suffix on %s. Using .NULL suffix\n", gn->name);
+ }
+
+ targ = bmake_malloc(sizeof(Src));
+ targ->file = bmake_strdup(gn->name);
+ targ->suff = suffNull;
+ targ->suff->refCount++;
+ targ->node = gn;
+ targ->parent = NULL;
+ targ->children = 0;
+ targ->pref = bmake_strdup(sopref);
+#ifdef DEBUG_SRC
+ targ->cp = Lst_Init(FALSE);
+#endif
+
+ /*
+ * Only use the default suffix rules if we don't have commands
+ * defined for this gnode; traditional make programs used to
+ * not define suffix rules if the gnode had children but we
+ * don't do this anymore.
+ */
+ if (Lst_IsEmpty(gn->commands))
+ SuffAddLevel(srcs, targ);
+ else {
+ if (DEBUG(SUFF))
+ fprintf(debug_file, "not ");
+ }
+
+ if (DEBUG(SUFF))
+ fprintf(debug_file, "adding suffix rules\n");
+
+ (void)Lst_AtEnd(targs, targ);
+ }
+
+ /*
+ * Using the list of possible sources built up from the target
+ * suffix(es), try and find an existing file/target that matches.
+ */
+ bottom = SuffFindThem(srcs, slst);
+
+ if (bottom == NULL) {
+ /*
+ * No known transformations -- use the first suffix found
+ * for setting the local variables.
+ */
+ if (!Lst_IsEmpty(targs)) {
+ targ = (Src *)Lst_Datum(Lst_First(targs));
+ } else {
+ targ = NULL;
+ }
+ } else {
+ /*
+ * Work up the transformation path to find the suffix of the
+ * target to which the transformation was made.
+ */
+ for (targ = bottom; targ->parent != NULL; targ = targ->parent)
+ continue;
+ }
+ }
+
+ Var_Set(TARGET, gn->path ? gn->path : gn->name, gn, 0);
+
+ pref = (targ != NULL) ? targ->pref : gn->name;
+ Var_Set(PREFIX, pref, gn, 0);
+
+ /*
+ * Now we've got the important local variables set, expand any sources
+ * that still contain variables or wildcards in their names.
+ */
+ for (ln = Lst_First(gn->children); ln != NULL; ln = nln) {
+ nln = Lst_Succ(ln);
+ SuffExpandChildren(ln, gn);
+ }
+
+ if (targ == NULL) {
+ if (DEBUG(SUFF)) {
+ fprintf(debug_file, "\tNo valid suffix on %s\n", gn->name);
+ }
+
+sfnd_abort:
+ /*
+ * Deal with finding the thing on the default search path. We
+ * always do that, not only if the node is only a source (not
+ * on the lhs of a dependency operator or [XXX] it has neither
+ * children or commands) as the old pmake did.
+ */
+ if ((gn->type & (OP_PHONY|OP_NOPATH)) == 0) {
+ free(gn->path);
+ gn->path = Dir_FindFile(gn->name,
+ (targ == NULL ? dirSearchPath :
+ targ->suff->searchPath));
+ if (gn->path != NULL) {
+ char *ptr;
+ Var_Set(TARGET, gn->path, gn, 0);
+
+ if (targ != NULL) {
+ /*
+ * Suffix known for the thing -- trim the suffix off
+ * the path to form the proper .PREFIX variable.
+ */
+ int savep = strlen(gn->path) - targ->suff->nameLen;
+ char savec;
+
+ if (gn->suffix)
+ gn->suffix->refCount--;
+ gn->suffix = targ->suff;
+ gn->suffix->refCount++;
+
+ savec = gn->path[savep];
+ gn->path[savep] = '\0';
+
+ if ((ptr = strrchr(gn->path, '/')) != NULL)
+ ptr++;
+ else
+ ptr = gn->path;
+
+ Var_Set(PREFIX, ptr, gn, 0);
+
+ gn->path[savep] = savec;
+ } else {
+ /*
+ * The .PREFIX gets the full path if the target has
+ * no known suffix.
+ */
+ if (gn->suffix)
+ gn->suffix->refCount--;
+ gn->suffix = NULL;
+
+ if ((ptr = strrchr(gn->path, '/')) != NULL)
+ ptr++;
+ else
+ ptr = gn->path;
+
+ Var_Set(PREFIX, ptr, gn, 0);
+ }
+ }
+ }
+
+ goto sfnd_return;
+ }
+
+ /*
+ * If the suffix indicates that the target is a library, mark that in
+ * the node's type field.
+ */
+ if (targ->suff->flags & SUFF_LIBRARY) {
+ gn->type |= OP_LIB;
+ }
+
+ /*
+ * Check for overriding transformation rule implied by sources
+ */
+ if (!Lst_IsEmpty(gn->children)) {
+ src = SuffFindCmds(targ, slst);
+
+ if (src != NULL) {
+ /*
+ * Free up all the Src structures in the transformation path
+ * up to, but not including, the parent node.
+ */
+ while (bottom && bottom->parent != NULL) {
+ if (Lst_Member(slst, bottom) == NULL) {
+ Lst_AtEnd(slst, bottom);
+ }
+ bottom = bottom->parent;
+ }
+ bottom = src;
+ }
+ }
+
+ if (bottom == NULL) {
+ /*
+ * No idea from where it can come -- return now.
+ */
+ goto sfnd_abort;
+ }
+
+ /*
+ * We now have a list of Src structures headed by 'bottom' and linked via
+ * their 'parent' pointers. What we do next is create links between
+ * source and target nodes (which may or may not have been created)
+ * and set the necessary local variables in each target. The
+ * commands for each target are set from the commands of the
+ * transformation rule used to get from the src suffix to the targ
+ * suffix. Note that this causes the commands list of the original
+ * node, gn, to be replaced by the commands of the final
+ * transformation rule. Also, the unmade field of gn is incremented.
+ * Etc.
+ */
+ if (bottom->node == NULL) {
+ bottom->node = Targ_FindNode(bottom->file, TARG_CREATE);
+ }
+
+ for (src = bottom; src->parent != NULL; src = src->parent) {
+ targ = src->parent;
+
+ if (src->node->suffix)
+ src->node->suffix->refCount--;
+ src->node->suffix = src->suff;
+ src->node->suffix->refCount++;
+
+ if (targ->node == NULL) {
+ targ->node = Targ_FindNode(targ->file, TARG_CREATE);
+ }
+
+ SuffApplyTransform(targ->node, src->node,
+ targ->suff, src->suff);
+
+ if (targ->node != gn) {
+ /*
+ * Finish off the dependency-search process for any nodes
+ * between bottom and gn (no point in questing around the
+ * filesystem for their implicit source when it's already
+ * known). Note that the node can't have any sources that
+ * need expanding, since SuffFindThem will stop on an existing
+ * node, so all we need to do is set the standard and System V
+ * variables.
+ */
+ targ->node->type |= OP_DEPS_FOUND;
+
+ Var_Set(PREFIX, targ->pref, targ->node, 0);
+
+ Var_Set(TARGET, targ->node->name, targ->node, 0);
+ }
+ }
+
+ if (gn->suffix)
+ gn->suffix->refCount--;
+ gn->suffix = src->suff;
+ gn->suffix->refCount++;
+
+ /*
+ * Nuke the transformation path and the Src structures left over in the
+ * two lists.
+ */
+sfnd_return:
+ if (bottom)
+ if (Lst_Member(slst, bottom) == NULL)
+ Lst_AtEnd(slst, bottom);
+
+ while (SuffRemoveSrc(srcs) || SuffRemoveSrc(targs))
+ continue;
+
+ Lst_Concat(slst, srcs, LST_CONCLINK);
+ Lst_Concat(slst, targs, LST_CONCLINK);
+}
+
+
+/*-
+ *-----------------------------------------------------------------------
+ * Suff_FindDeps --
+ * Find implicit sources for the target described by the graph node
+ * gn
+ *
+ * Results:
+ * Nothing.
+ *
+ * Side Effects:
+ * Nodes are added to the graph below the passed-in node. The nodes
+ * are marked to have their IMPSRC variable filled in. The
+ * PREFIX variable is set for the given node and all its
+ * implied children.
+ *
+ * Notes:
+ * The path found by this target is the shortest path in the
+ * transformation graph, which may pass through non-existent targets,
+ * to an existing target. The search continues on all paths from the
+ * root suffix until a file is found. I.e. if there's a path
+ * .o -> .c -> .l -> .l,v from the root and the .l,v file exists but
+ * the .c and .l files don't, the search will branch out in
+ * all directions from .o and again from all the nodes on the
+ * next level until the .l,v node is encountered.
+ *
+ *-----------------------------------------------------------------------
+ */
+
+void
+Suff_FindDeps(GNode *gn)
+{
+
+ SuffFindDeps(gn, srclist);
+ while (SuffRemoveSrc(srclist))
+ continue;
+}
+
+
+/*
+ * Input:
+ * gn node we're dealing with
+ *
+ */
+static void
+SuffFindDeps(GNode *gn, Lst slst)
+{
+ if (gn->type & OP_DEPS_FOUND) {
+ /*
+ * If dependencies already found, no need to do it again...
+ */
+ return;
+ } else {
+ gn->type |= OP_DEPS_FOUND;
+ }
+ /*
+ * Make sure we have these set, may get revised below.
+ */
+ Var_Set(TARGET, gn->path ? gn->path : gn->name, gn, 0);
+ Var_Set(PREFIX, gn->name, gn, 0);
+
+ if (DEBUG(SUFF)) {
+ fprintf(debug_file, "SuffFindDeps (%s)\n", gn->name);
+ }
+
+ if (gn->type & OP_ARCHV) {
+ SuffFindArchiveDeps(gn, slst);
+ } else if (gn->type & OP_LIB) {
+ /*
+ * If the node is a library, it is the arch module's job to find it
+ * and set the TARGET variable accordingly. We merely provide the
+ * search path, assuming all libraries end in ".a" (if the suffix
+ * hasn't been defined, there's nothing we can do for it, so we just
+ * set the TARGET variable to the node's name in order to give it a
+ * value).
+ */
+ LstNode ln;
+ Suff *s;
+
+ ln = Lst_Find(sufflist, LIBSUFF, SuffSuffHasNameP);
+ if (gn->suffix)
+ gn->suffix->refCount--;
+ if (ln != NULL) {
+ gn->suffix = s = (Suff *)Lst_Datum(ln);
+ gn->suffix->refCount++;
+ Arch_FindLib(gn, s->searchPath);
+ } else {
+ gn->suffix = NULL;
+ Var_Set(TARGET, gn->name, gn, 0);
+ }
+ /*
+ * Because a library (-lfoo) target doesn't follow the standard
+ * filesystem conventions, we don't set the regular variables for
+ * the thing. .PREFIX is simply made empty...
+ */
+ Var_Set(PREFIX, "", gn, 0);
+ } else {
+ SuffFindNormalDeps(gn, slst);
+ }
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * Suff_SetNull --
+ * Define which suffix is the null suffix.
+ *
+ * Input:
+ * name Name of null suffix
+ *
+ * Results:
+ * None.
+ *
+ * Side Effects:
+ * 'suffNull' is altered.
+ *
+ * Notes:
+ * Need to handle the changing of the null suffix gracefully so the
+ * old transformation rules don't just go away.
+ *
+ *-----------------------------------------------------------------------
+ */
+void
+Suff_SetNull(char *name)
+{
+ Suff *s;
+ LstNode ln;
+
+ ln = Lst_Find(sufflist, name, SuffSuffHasNameP);
+ if (ln != NULL) {
+ s = (Suff *)Lst_Datum(ln);
+ if (suffNull != NULL) {
+ suffNull->flags &= ~SUFF_NULL;
+ }
+ s->flags |= SUFF_NULL;
+ /*
+ * XXX: Here's where the transformation mangling would take place
+ */
+ suffNull = s;
+ } else {
+ Parse_Error(PARSE_WARNING, "Desired null suffix %s not defined.",
+ name);
+ }
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * Suff_Init --
+ * Initialize suffixes module
+ *
+ * Results:
+ * None
+ *
+ * Side Effects:
+ * Many
+ *-----------------------------------------------------------------------
+ */
+void
+Suff_Init(void)
+{
+#ifdef CLEANUP
+ suffClean = Lst_Init(FALSE);
+#endif
+ srclist = Lst_Init(FALSE);
+ transforms = Lst_Init(FALSE);
+
+ /*
+ * Create null suffix for single-suffix rules (POSIX). The thing doesn't
+ * actually go on the suffix list or everyone will think that's its
+ * suffix.
+ */
+ Suff_ClearSuffixes();
+}
+
+
+/*-
+ *----------------------------------------------------------------------
+ * Suff_End --
+ * Cleanup the this module
+ *
+ * Results:
+ * None
+ *
+ * Side Effects:
+ * The memory is free'd.
+ *----------------------------------------------------------------------
+ */
+
+void
+Suff_End(void)
+{
+#ifdef CLEANUP
+ Lst_Destroy(sufflist, SuffFree);
+ Lst_Destroy(suffClean, SuffFree);
+ if (suffNull)
+ SuffFree(suffNull);
+ Lst_Destroy(srclist, NULL);
+ Lst_Destroy(transforms, NULL);
+#endif
+}
+
+
+/********************* DEBUGGING FUNCTIONS **********************/
+
+static int SuffPrintName(void *s, void *dummy MAKE_ATTR_UNUSED)
+{
+
+ fprintf(debug_file, "%s ", ((Suff *)s)->name);
+ return 0;
+}
+
+static int
+SuffPrintSuff(void *sp, void *dummy MAKE_ATTR_UNUSED)
+{
+ Suff *s = (Suff *)sp;
+ int flags;
+ int flag;
+
+ fprintf(debug_file, "# `%s' [%d] ", s->name, s->refCount);
+
+ flags = s->flags;
+ if (flags) {
+ fputs(" (", debug_file);
+ while (flags) {
+ flag = 1 << (ffs(flags) - 1);
+ flags &= ~flag;
+ switch (flag) {
+ case SUFF_NULL:
+ fprintf(debug_file, "NULL");
+ break;
+ case SUFF_INCLUDE:
+ fprintf(debug_file, "INCLUDE");
+ break;
+ case SUFF_LIBRARY:
+ fprintf(debug_file, "LIBRARY");
+ break;
+ }
+ fputc(flags ? '|' : ')', debug_file);
+ }
+ }
+ fputc('\n', debug_file);
+ fprintf(debug_file, "#\tTo: ");
+ Lst_ForEach(s->parents, SuffPrintName, NULL);
+ fputc('\n', debug_file);
+ fprintf(debug_file, "#\tFrom: ");
+ Lst_ForEach(s->children, SuffPrintName, NULL);
+ fputc('\n', debug_file);
+ fprintf(debug_file, "#\tSearch Path: ");
+ Dir_PrintPath(s->searchPath);
+ fputc('\n', debug_file);
+ return 0;
+}
+
+static int
+SuffPrintTrans(void *tp, void *dummy MAKE_ATTR_UNUSED)
+{
+ GNode *t = (GNode *)tp;
+
+ fprintf(debug_file, "%-16s: ", t->name);
+ Targ_PrintType(t->type);
+ fputc('\n', debug_file);
+ Lst_ForEach(t->commands, Targ_PrintCmd, NULL);
+ fputc('\n', debug_file);
+ return 0;
+}
+
+void
+Suff_PrintAll(void)
+{
+ fprintf(debug_file, "#*** Suffixes:\n");
+ Lst_ForEach(sufflist, SuffPrintSuff, NULL);
+
+ fprintf(debug_file, "#*** Transformations:\n");
+ Lst_ForEach(transforms, SuffPrintTrans, NULL);
+}
diff --git a/usr.bin/make/targ.c b/usr.bin/make/targ.c
new file mode 100644
index 0000000..01c3d1c
--- /dev/null
+++ b/usr.bin/make/targ.c
@@ -0,0 +1,846 @@
+/* $NetBSD: targ.c,v 1.62 2017/04/16 19:53:58 riastradh Exp $ */
+
+/*
+ * Copyright (c) 1988, 1989, 1990, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Adam de Boor.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+/*
+ * Copyright (c) 1989 by Berkeley Softworks
+ * All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Adam de Boor.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. All advertising materials mentioning features or use of this software
+ * must display the following acknowledgement:
+ * This product includes software developed by the University of
+ * California, Berkeley and its contributors.
+ * 4. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#ifndef MAKE_NATIVE
+static char rcsid[] = "$NetBSD: targ.c,v 1.62 2017/04/16 19:53:58 riastradh Exp $";
+#else
+#include <sys/cdefs.h>
+#ifndef lint
+#if 0
+static char sccsid[] = "@(#)targ.c 8.2 (Berkeley) 3/19/94";
+#else
+__RCSID("$NetBSD: targ.c,v 1.62 2017/04/16 19:53:58 riastradh Exp $");
+#endif
+#endif /* not lint */
+#endif
+
+/*-
+ * targ.c --
+ * Functions for maintaining the Lst allTargets. Target nodes are
+ * kept in two structures: a Lst, maintained by the list library, and a
+ * hash table, maintained by the hash library.
+ *
+ * Interface:
+ * Targ_Init Initialization procedure.
+ *
+ * Targ_End Cleanup the module
+ *
+ * Targ_List Return the list of all targets so far.
+ *
+ * Targ_NewGN Create a new GNode for the passed target
+ * (string). The node is *not* placed in the
+ * hash table, though all its fields are
+ * initialized.
+ *
+ * Targ_FindNode Find the node for a given target, creating
+ * and storing it if it doesn't exist and the
+ * flags are right (TARG_CREATE)
+ *
+ * Targ_FindList Given a list of names, find nodes for all
+ * of them. If a name doesn't exist and the
+ * TARG_NOCREATE flag was given, an error message
+ * is printed. Else, if a name doesn't exist,
+ * its node is created.
+ *
+ * Targ_Ignore Return TRUE if errors should be ignored when
+ * creating the given target.
+ *
+ * Targ_Silent Return TRUE if we should be silent when
+ * creating the given target.
+ *
+ * Targ_Precious Return TRUE if the target is precious and
+ * should not be removed if we are interrupted.
+ *
+ * Targ_Propagate Propagate information between related
+ * nodes. Should be called after the
+ * makefiles are parsed but before any
+ * action is taken.
+ *
+ * Debugging:
+ * Targ_PrintGraph Print out the entire graphm all variables
+ * and statistics for the directory cache. Should
+ * print something for suffixes, too, but...
+ */
+
+#include <stdio.h>
+#include <time.h>
+
+#include "make.h"
+#include "hash.h"
+#include "dir.h"
+
+static Lst allTargets; /* the list of all targets found so far */
+#ifdef CLEANUP
+static Lst allGNs; /* List of all the GNodes */
+#endif
+static Hash_Table targets; /* a hash table of same */
+
+#define HTSIZE 191 /* initial size of hash table */
+
+static int TargPrintOnlySrc(void *, void *);
+static int TargPrintName(void *, void *);
+#ifdef CLEANUP
+static void TargFreeGN(void *);
+#endif
+static int TargPropagateCohort(void *, void *);
+static int TargPropagateNode(void *, void *);
+
+/*-
+ *-----------------------------------------------------------------------
+ * Targ_Init --
+ * Initialize this module
+ *
+ * Results:
+ * None
+ *
+ * Side Effects:
+ * The allTargets list and the targets hash table are initialized
+ *-----------------------------------------------------------------------
+ */
+void
+Targ_Init(void)
+{
+ allTargets = Lst_Init(FALSE);
+ Hash_InitTable(&targets, HTSIZE);
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * Targ_End --
+ * Finalize this module
+ *
+ * Results:
+ * None
+ *
+ * Side Effects:
+ * All lists and gnodes are cleared
+ *-----------------------------------------------------------------------
+ */
+void
+Targ_End(void)
+{
+#ifdef CLEANUP
+ Lst_Destroy(allTargets, NULL);
+ if (allGNs)
+ Lst_Destroy(allGNs, TargFreeGN);
+ Hash_DeleteTable(&targets);
+#endif
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * Targ_List --
+ * Return the list of all targets
+ *
+ * Results:
+ * The list of all targets.
+ *
+ * Side Effects:
+ * None
+ *-----------------------------------------------------------------------
+ */
+Lst
+Targ_List(void)
+{
+ return allTargets;
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * Targ_NewGN --
+ * Create and initialize a new graph node
+ *
+ * Input:
+ * name the name to stick in the new node
+ *
+ * Results:
+ * An initialized graph node with the name field filled with a copy
+ * of the passed name
+ *
+ * Side Effects:
+ * The gnode is added to the list of all gnodes.
+ *-----------------------------------------------------------------------
+ */
+GNode *
+Targ_NewGN(const char *name)
+{
+ GNode *gn;
+
+ gn = bmake_malloc(sizeof(GNode));
+ gn->name = bmake_strdup(name);
+ gn->uname = NULL;
+ gn->path = NULL;
+ if (name[0] == '-' && name[1] == 'l') {
+ gn->type = OP_LIB;
+ } else {
+ gn->type = 0;
+ }
+ gn->unmade = 0;
+ gn->unmade_cohorts = 0;
+ gn->cohort_num[0] = 0;
+ gn->centurion = NULL;
+ gn->made = UNMADE;
+ gn->flags = 0;
+ gn->checked = 0;
+ gn->mtime = 0;
+ gn->cmgn = NULL;
+ gn->iParents = Lst_Init(FALSE);
+ gn->cohorts = Lst_Init(FALSE);
+ gn->parents = Lst_Init(FALSE);
+ gn->children = Lst_Init(FALSE);
+ gn->order_pred = Lst_Init(FALSE);
+ gn->order_succ = Lst_Init(FALSE);
+ Hash_InitTable(&gn->context, 0);
+ gn->commands = Lst_Init(FALSE);
+ gn->suffix = NULL;
+ gn->lineno = 0;
+ gn->fname = NULL;
+
+#ifdef CLEANUP
+ if (allGNs == NULL)
+ allGNs = Lst_Init(FALSE);
+ Lst_AtEnd(allGNs, gn);
+#endif
+
+ return (gn);
+}
+
+#ifdef CLEANUP
+/*-
+ *-----------------------------------------------------------------------
+ * TargFreeGN --
+ * Destroy a GNode
+ *
+ * Results:
+ * None.
+ *
+ * Side Effects:
+ * None.
+ *-----------------------------------------------------------------------
+ */
+static void
+TargFreeGN(void *gnp)
+{
+ GNode *gn = (GNode *)gnp;
+
+
+ free(gn->name);
+ free(gn->uname);
+ free(gn->path);
+ /* gn->fname points to name allocated when file was opened, don't free */
+
+ Lst_Destroy(gn->iParents, NULL);
+ Lst_Destroy(gn->cohorts, NULL);
+ Lst_Destroy(gn->parents, NULL);
+ Lst_Destroy(gn->children, NULL);
+ Lst_Destroy(gn->order_succ, NULL);
+ Lst_Destroy(gn->order_pred, NULL);
+ Hash_DeleteTable(&gn->context);
+ Lst_Destroy(gn->commands, NULL);
+ free(gn);
+}
+#endif
+
+
+/*-
+ *-----------------------------------------------------------------------
+ * Targ_FindNode --
+ * Find a node in the list using the given name for matching
+ *
+ * Input:
+ * name the name to find
+ * flags flags governing events when target not
+ * found
+ *
+ * Results:
+ * The node in the list if it was. If it wasn't, return NULL of
+ * flags was TARG_NOCREATE or the newly created and initialized node
+ * if it was TARG_CREATE
+ *
+ * Side Effects:
+ * Sometimes a node is created and added to the list
+ *-----------------------------------------------------------------------
+ */
+GNode *
+Targ_FindNode(const char *name, int flags)
+{
+ GNode *gn; /* node in that element */
+ Hash_Entry *he = NULL; /* New or used hash entry for node */
+ Boolean isNew; /* Set TRUE if Hash_CreateEntry had to create */
+ /* an entry for the node */
+
+ if (!(flags & (TARG_CREATE | TARG_NOHASH))) {
+ he = Hash_FindEntry(&targets, name);
+ if (he == NULL)
+ return NULL;
+ return (GNode *)Hash_GetValue(he);
+ }
+
+ if (!(flags & TARG_NOHASH)) {
+ he = Hash_CreateEntry(&targets, name, &isNew);
+ if (!isNew)
+ return (GNode *)Hash_GetValue(he);
+ }
+
+ gn = Targ_NewGN(name);
+ if (!(flags & TARG_NOHASH))
+ Hash_SetValue(he, gn);
+ Var_Append(".ALLTARGETS", name, VAR_GLOBAL);
+ (void)Lst_AtEnd(allTargets, gn);
+ if (doing_depend)
+ gn->flags |= FROM_DEPEND;
+ return gn;
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * Targ_FindList --
+ * Make a complete list of GNodes from the given list of names
+ *
+ * Input:
+ * name list of names to find
+ * flags flags used if no node is found for a given name
+ *
+ * Results:
+ * A complete list of graph nodes corresponding to all instances of all
+ * the names in names.
+ *
+ * Side Effects:
+ * If flags is TARG_CREATE, nodes will be created for all names in
+ * names which do not yet have graph nodes. If flags is TARG_NOCREATE,
+ * an error message will be printed for each name which can't be found.
+ * -----------------------------------------------------------------------
+ */
+Lst
+Targ_FindList(Lst names, int flags)
+{
+ Lst nodes; /* result list */
+ LstNode ln; /* name list element */
+ GNode *gn; /* node in tLn */
+ char *name;
+
+ nodes = Lst_Init(FALSE);
+
+ if (Lst_Open(names) == FAILURE) {
+ return (nodes);
+ }
+ while ((ln = Lst_Next(names)) != NULL) {
+ name = (char *)Lst_Datum(ln);
+ gn = Targ_FindNode(name, flags);
+ if (gn != NULL) {
+ /*
+ * Note: Lst_AtEnd must come before the Lst_Concat so the nodes
+ * are added to the list in the order in which they were
+ * encountered in the makefile.
+ */
+ (void)Lst_AtEnd(nodes, gn);
+ } else if (flags == TARG_NOCREATE) {
+ Error("\"%s\" -- target unknown.", name);
+ }
+ }
+ Lst_Close(names);
+ return (nodes);
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * Targ_Ignore --
+ * Return true if should ignore errors when creating gn
+ *
+ * Input:
+ * gn node to check for
+ *
+ * Results:
+ * TRUE if should ignore errors
+ *
+ * Side Effects:
+ * None
+ *-----------------------------------------------------------------------
+ */
+Boolean
+Targ_Ignore(GNode *gn)
+{
+ if (ignoreErrors || gn->type & OP_IGNORE) {
+ return (TRUE);
+ } else {
+ return (FALSE);
+ }
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * Targ_Silent --
+ * Return true if be silent when creating gn
+ *
+ * Input:
+ * gn node to check for
+ *
+ * Results:
+ * TRUE if should be silent
+ *
+ * Side Effects:
+ * None
+ *-----------------------------------------------------------------------
+ */
+Boolean
+Targ_Silent(GNode *gn)
+{
+ if (beSilent || gn->type & OP_SILENT) {
+ return (TRUE);
+ } else {
+ return (FALSE);
+ }
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * Targ_Precious --
+ * See if the given target is precious
+ *
+ * Input:
+ * gn the node to check
+ *
+ * Results:
+ * TRUE if it is precious. FALSE otherwise
+ *
+ * Side Effects:
+ * None
+ *-----------------------------------------------------------------------
+ */
+Boolean
+Targ_Precious(GNode *gn)
+{
+ if (allPrecious || (gn->type & (OP_PRECIOUS|OP_DOUBLEDEP))) {
+ return (TRUE);
+ } else {
+ return (FALSE);
+ }
+}
+
+/******************* DEBUG INFO PRINTING ****************/
+
+static GNode *mainTarg; /* the main target, as set by Targ_SetMain */
+/*-
+ *-----------------------------------------------------------------------
+ * Targ_SetMain --
+ * Set our idea of the main target we'll be creating. Used for
+ * debugging output.
+ *
+ * Input:
+ * gn The main target we'll create
+ *
+ * Results:
+ * None.
+ *
+ * Side Effects:
+ * "mainTarg" is set to the main target's node.
+ *-----------------------------------------------------------------------
+ */
+void
+Targ_SetMain(GNode *gn)
+{
+ mainTarg = gn;
+}
+
+static int
+TargPrintName(void *gnp, void *pflags MAKE_ATTR_UNUSED)
+{
+ GNode *gn = (GNode *)gnp;
+
+ fprintf(debug_file, "%s%s ", gn->name, gn->cohort_num);
+
+ return 0;
+}
+
+
+int
+Targ_PrintCmd(void *cmd, void *dummy MAKE_ATTR_UNUSED)
+{
+ fprintf(debug_file, "\t%s\n", (char *)cmd);
+ return 0;
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * Targ_FmtTime --
+ * Format a modification time in some reasonable way and return it.
+ *
+ * Results:
+ * The time reformatted.
+ *
+ * Side Effects:
+ * The time is placed in a static area, so it is overwritten
+ * with each call.
+ *
+ *-----------------------------------------------------------------------
+ */
+char *
+Targ_FmtTime(time_t tm)
+{
+ struct tm *parts;
+ static char buf[128];
+
+ parts = localtime(&tm);
+ (void)strftime(buf, sizeof buf, "%k:%M:%S %b %d, %Y", parts);
+ return(buf);
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * Targ_PrintType --
+ * Print out a type field giving only those attributes the user can
+ * set.
+ *
+ * Results:
+ *
+ * Side Effects:
+ *
+ *-----------------------------------------------------------------------
+ */
+void
+Targ_PrintType(int type)
+{
+ int tbit;
+
+#define PRINTBIT(attr) case CONCAT(OP_,attr): fprintf(debug_file, "." #attr " "); break
+#define PRINTDBIT(attr) case CONCAT(OP_,attr): if (DEBUG(TARG))fprintf(debug_file, "." #attr " "); break
+
+ type &= ~OP_OPMASK;
+
+ while (type) {
+ tbit = 1 << (ffs(type) - 1);
+ type &= ~tbit;
+
+ switch(tbit) {
+ PRINTBIT(OPTIONAL);
+ PRINTBIT(USE);
+ PRINTBIT(EXEC);
+ PRINTBIT(IGNORE);
+ PRINTBIT(PRECIOUS);
+ PRINTBIT(SILENT);
+ PRINTBIT(MAKE);
+ PRINTBIT(JOIN);
+ PRINTBIT(INVISIBLE);
+ PRINTBIT(NOTMAIN);
+ PRINTDBIT(LIB);
+ /*XXX: MEMBER is defined, so CONCAT(OP_,MEMBER) gives OP_"%" */
+ case OP_MEMBER: if (DEBUG(TARG))fprintf(debug_file, ".MEMBER "); break;
+ PRINTDBIT(ARCHV);
+ PRINTDBIT(MADE);
+ PRINTDBIT(PHONY);
+ }
+ }
+}
+
+static const char *
+made_name(enum enum_made made)
+{
+ switch (made) {
+ case UNMADE: return "unmade";
+ case DEFERRED: return "deferred";
+ case REQUESTED: return "requested";
+ case BEINGMADE: return "being made";
+ case MADE: return "made";
+ case UPTODATE: return "up-to-date";
+ case ERROR: return "error when made";
+ case ABORTED: return "aborted";
+ default: return "unknown enum_made value";
+ }
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * TargPrintNode --
+ * print the contents of a node
+ *-----------------------------------------------------------------------
+ */
+int
+Targ_PrintNode(void *gnp, void *passp)
+{
+ GNode *gn = (GNode *)gnp;
+ int pass = passp ? *(int *)passp : 0;
+
+ fprintf(debug_file, "# %s%s, flags %x, type %x, made %d\n",
+ gn->name, gn->cohort_num, gn->flags, gn->type, gn->made);
+ if (gn->flags == 0)
+ return 0;
+
+ if (!OP_NOP(gn->type)) {
+ fprintf(debug_file, "#\n");
+ if (gn == mainTarg) {
+ fprintf(debug_file, "# *** MAIN TARGET ***\n");
+ }
+ if (pass >= 2) {
+ if (gn->unmade) {
+ fprintf(debug_file, "# %d unmade children\n", gn->unmade);
+ } else {
+ fprintf(debug_file, "# No unmade children\n");
+ }
+ if (! (gn->type & (OP_JOIN|OP_USE|OP_USEBEFORE|OP_EXEC))) {
+ if (gn->mtime != 0) {
+ fprintf(debug_file, "# last modified %s: %s\n",
+ Targ_FmtTime(gn->mtime),
+ made_name(gn->made));
+ } else if (gn->made != UNMADE) {
+ fprintf(debug_file, "# non-existent (maybe): %s\n",
+ made_name(gn->made));
+ } else {
+ fprintf(debug_file, "# unmade\n");
+ }
+ }
+ if (!Lst_IsEmpty (gn->iParents)) {
+ fprintf(debug_file, "# implicit parents: ");
+ Lst_ForEach(gn->iParents, TargPrintName, NULL);
+ fprintf(debug_file, "\n");
+ }
+ } else {
+ if (gn->unmade)
+ fprintf(debug_file, "# %d unmade children\n", gn->unmade);
+ }
+ if (!Lst_IsEmpty (gn->parents)) {
+ fprintf(debug_file, "# parents: ");
+ Lst_ForEach(gn->parents, TargPrintName, NULL);
+ fprintf(debug_file, "\n");
+ }
+ if (!Lst_IsEmpty (gn->order_pred)) {
+ fprintf(debug_file, "# order_pred: ");
+ Lst_ForEach(gn->order_pred, TargPrintName, NULL);
+ fprintf(debug_file, "\n");
+ }
+ if (!Lst_IsEmpty (gn->order_succ)) {
+ fprintf(debug_file, "# order_succ: ");
+ Lst_ForEach(gn->order_succ, TargPrintName, NULL);
+ fprintf(debug_file, "\n");
+ }
+
+ fprintf(debug_file, "%-16s", gn->name);
+ switch (gn->type & OP_OPMASK) {
+ case OP_DEPENDS:
+ fprintf(debug_file, ": "); break;
+ case OP_FORCE:
+ fprintf(debug_file, "! "); break;
+ case OP_DOUBLEDEP:
+ fprintf(debug_file, ":: "); break;
+ }
+ Targ_PrintType(gn->type);
+ Lst_ForEach(gn->children, TargPrintName, NULL);
+ fprintf(debug_file, "\n");
+ Lst_ForEach(gn->commands, Targ_PrintCmd, NULL);
+ fprintf(debug_file, "\n\n");
+ if (gn->type & OP_DOUBLEDEP) {
+ Lst_ForEach(gn->cohorts, Targ_PrintNode, &pass);
+ }
+ }
+ return (0);
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * TargPrintOnlySrc --
+ * Print only those targets that are just a source.
+ *
+ * Results:
+ * 0.
+ *
+ * Side Effects:
+ * The name of each file is printed preceded by #\t
+ *
+ *-----------------------------------------------------------------------
+ */
+static int
+TargPrintOnlySrc(void *gnp, void *dummy MAKE_ATTR_UNUSED)
+{
+ GNode *gn = (GNode *)gnp;
+ if (!OP_NOP(gn->type))
+ return 0;
+
+ fprintf(debug_file, "#\t%s [%s] ",
+ gn->name, gn->path ? gn->path : gn->name);
+ Targ_PrintType(gn->type);
+ fprintf(debug_file, "\n");
+
+ return 0;
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * Targ_PrintGraph --
+ * print the entire graph. heh heh
+ *
+ * Input:
+ * pass Which pass this is. 1 => no processing
+ * 2 => processing done
+ *
+ * Results:
+ * none
+ *
+ * Side Effects:
+ * lots o' output
+ *-----------------------------------------------------------------------
+ */
+void
+Targ_PrintGraph(int pass)
+{
+ fprintf(debug_file, "#*** Input graph:\n");
+ Lst_ForEach(allTargets, Targ_PrintNode, &pass);
+ fprintf(debug_file, "\n\n");
+ fprintf(debug_file, "#\n# Files that are only sources:\n");
+ Lst_ForEach(allTargets, TargPrintOnlySrc, NULL);
+ fprintf(debug_file, "#*** Global Variables:\n");
+ Var_Dump(VAR_GLOBAL);
+ fprintf(debug_file, "#*** Command-line Variables:\n");
+ Var_Dump(VAR_CMD);
+ fprintf(debug_file, "\n");
+ Dir_PrintDirectories();
+ fprintf(debug_file, "\n");
+ Suff_PrintAll();
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * TargPropagateNode --
+ * Propagate information from a single node to related nodes if
+ * appropriate.
+ *
+ * Input:
+ * gnp The node that we are processing.
+ *
+ * Results:
+ * Always returns 0, for the benefit of Lst_ForEach().
+ *
+ * Side Effects:
+ * Information is propagated from this node to cohort or child
+ * nodes.
+ *
+ * If the node was defined with "::", then TargPropagateCohort()
+ * will be called for each cohort node.
+ *
+ * If the node has recursive predecessors, then
+ * TargPropagateRecpred() will be called for each recursive
+ * predecessor.
+ *-----------------------------------------------------------------------
+ */
+static int
+TargPropagateNode(void *gnp, void *junk MAKE_ATTR_UNUSED)
+{
+ GNode *gn = (GNode *)gnp;
+
+ if (gn->type & OP_DOUBLEDEP)
+ Lst_ForEach(gn->cohorts, TargPropagateCohort, gnp);
+ return (0);
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * TargPropagateCohort --
+ * Propagate some bits in the type mask from a node to
+ * a related cohort node.
+ *
+ * Input:
+ * cnp The node that we are processing.
+ * gnp Another node that has cnp as a cohort.
+ *
+ * Results:
+ * Always returns 0, for the benefit of Lst_ForEach().
+ *
+ * Side Effects:
+ * cnp's type bitmask is modified to incorporate some of the
+ * bits from gnp's type bitmask. (XXX need a better explanation.)
+ *-----------------------------------------------------------------------
+ */
+static int
+TargPropagateCohort(void *cgnp, void *pgnp)
+{
+ GNode *cgn = (GNode *)cgnp;
+ GNode *pgn = (GNode *)pgnp;
+
+ cgn->type |= pgn->type & ~OP_OPMASK;
+ return (0);
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * Targ_Propagate --
+ * Propagate information between related nodes. Should be called
+ * after the makefiles are parsed but before any action is taken.
+ *
+ * Results:
+ * none
+ *
+ * Side Effects:
+ * Information is propagated between related nodes throughout the
+ * graph.
+ *-----------------------------------------------------------------------
+ */
+void
+Targ_Propagate(void)
+{
+ Lst_ForEach(allTargets, TargPropagateNode, NULL);
+}
diff --git a/usr.bin/make/trace.c b/usr.bin/make/trace.c
new file mode 100644
index 0000000..267177f
--- /dev/null
+++ b/usr.bin/make/trace.c
@@ -0,0 +1,116 @@
+/* $NetBSD: trace.c,v 1.11 2008/12/28 18:31:51 christos Exp $ */
+
+/*-
+ * Copyright (c) 2000 The NetBSD Foundation, Inc.
+ * All rights reserved.
+ *
+ * This code is derived from software contributed to The NetBSD Foundation
+ * by Bill Sommerfeld
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
+ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+
+#ifndef MAKE_NATIVE
+static char rcsid[] = "$NetBSD: trace.c,v 1.11 2008/12/28 18:31:51 christos Exp $";
+#else
+#include <sys/cdefs.h>
+#ifndef lint
+__RCSID("$NetBSD: trace.c,v 1.11 2008/12/28 18:31:51 christos Exp $");
+#endif /* not lint */
+#endif
+
+/*-
+ * trace.c --
+ * handle logging of trace events generated by various parts of make.
+ *
+ * Interface:
+ * Trace_Init Initialize tracing (called once during
+ * the lifetime of the process)
+ *
+ * Trace_End Finalize tracing (called before make exits)
+ *
+ * Trace_Log Log an event about a particular make job.
+ */
+
+#include <sys/time.h>
+
+#include <stdio.h>
+#include <unistd.h>
+
+#include "make.h"
+#include "job.h"
+#include "trace.h"
+
+static FILE *trfile;
+static pid_t trpid;
+char *trwd;
+
+static const char *evname[] = {
+ "BEG",
+ "END",
+ "ERR",
+ "JOB",
+ "DON",
+ "INT",
+};
+
+void
+Trace_Init(const char *pathname)
+{
+ char *p1;
+ if (pathname != NULL) {
+ trpid = getpid();
+ trwd = Var_Value(".CURDIR", VAR_GLOBAL, &p1);
+
+ trfile = fopen(pathname, "a");
+ }
+}
+
+void
+Trace_Log(TrEvent event, Job *job)
+{
+ struct timeval rightnow;
+
+ if (trfile == NULL)
+ return;
+
+ gettimeofday(&rightnow, NULL);
+
+ fprintf(trfile, "%lld.%06ld %d %s %d %s",
+ (long long)rightnow.tv_sec, (long)rightnow.tv_usec,
+ jobTokensRunning,
+ evname[event], trpid, trwd);
+ if (job != NULL) {
+ fprintf(trfile, " %s %d %x %x", job->node->name,
+ job->pid, job->flags, job->node->type);
+ }
+ fputc('\n', trfile);
+ fflush(trfile);
+}
+
+void
+Trace_End(void)
+{
+ if (trfile != NULL)
+ fclose(trfile);
+}
diff --git a/usr.bin/make/trace.h b/usr.bin/make/trace.h
new file mode 100644
index 0000000..dc0fc6c
--- /dev/null
+++ b/usr.bin/make/trace.h
@@ -0,0 +1,49 @@
+/* $NetBSD: trace.h,v 1.3 2008/04/28 20:24:14 martin Exp $ */
+
+/*-
+ * Copyright (c) 2000 The NetBSD Foundation, Inc.
+ * All rights reserved.
+ *
+ * This code is derived from software contributed to The NetBSD Foundation
+ * by Bill Sommerfeld
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
+ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/*-
+ * trace.h --
+ * Definitions pertaining to the tracing of jobs in parallel mode.
+ */
+
+typedef enum {
+ MAKESTART,
+ MAKEEND,
+ MAKEERROR,
+ JOBSTART,
+ JOBEND,
+ MAKEINTR
+} TrEvent;
+
+void Trace_Init(const char *);
+void Trace_Log(TrEvent, Job *);
+void Trace_End(void);
+
diff --git a/usr.bin/make/unit-tests/Makefile b/usr.bin/make/unit-tests/Makefile
new file mode 100644
index 0000000..07aaceb
--- /dev/null
+++ b/usr.bin/make/unit-tests/Makefile
@@ -0,0 +1,139 @@
+# $NetBSD: Makefile,v 1.53 2018/05/24 00:25:44 christos Exp $
+#
+# Unit tests for make(1)
+# The main targets are:
+#
+# all: run all the tests
+# test: run 'all', and compare to expected results
+# accept: move generated output to expected results
+#
+# Adding a test case.
+# Each feature should get its own set of tests in its own suitably
+# named makefile (*.mk), with its own set of expected results (*.exp),
+# and it should be added to the TESTNAMES list.
+#
+
+.MAIN: all
+
+UNIT_TESTS:= ${.PARSEDIR}
+.PATH: ${UNIT_TESTS}
+
+# Each test is in a sub-makefile.
+# Keep the list sorted.
+TESTNAMES= \
+ comment \
+ cond1 \
+ cond2 \
+ error \
+ export \
+ export-all \
+ export-env \
+ doterror \
+ dotwait \
+ forloop \
+ forsubst \
+ hash \
+ misc \
+ moderrs \
+ modmatch \
+ modmisc \
+ modorder \
+ modts \
+ modword \
+ order \
+ posix \
+ qequals \
+ sunshcmd \
+ sysv \
+ ternary \
+ unexport \
+ unexport-env \
+ varcmd \
+ varmisc \
+ varquote \
+ varshell
+
+# these tests were broken by referting POSIX chanegs
+STRICT_POSIX_TESTS = \
+ escape \
+ impsrc \
+ phony-end \
+ posix1 \
+ suffixes
+
+# Override make flags for certain tests
+flags.doterror=
+flags.order=-j1
+
+OUTFILES= ${TESTNAMES:S/$/.out/}
+
+all: ${OUTFILES}
+
+CLEANFILES += *.rawout *.out *.status *.tmp *.core *.tmp
+CLEANFILES += obj*.[och] lib*.a # posix1.mk
+CLEANFILES += issue* .[ab]* # suffixes.mk
+CLEANRECURSIVE += dir dummy # posix1.mk
+
+clean:
+ rm -f ${CLEANFILES}
+.if !empty(CLEANRECURSIVE)
+ rm -rf ${CLEANRECURSIVE}
+.endif
+
+TEST_MAKE?= ${.MAKE}
+TOOL_SED?= sed
+
+# ensure consistent results from sort(1)
+LC_ALL= C
+LANG= C
+.export LANG LC_ALL
+
+# the tests are actually done with sub-makes.
+.SUFFIXES: .mk .rawout .out
+.mk.rawout:
+ @echo ${TEST_MAKE} ${flags.${.TARGET:R}:U-k} -f ${.IMPSRC}
+ -@cd ${.OBJDIR} && \
+ { ${TEST_MAKE} ${flags.${.TARGET:R}:U-k} -f ${.IMPSRC} \
+ 2>&1 ; echo $$? >${.TARGET:R}.status ; } > ${.TARGET}.tmp
+ @mv ${.TARGET}.tmp ${.TARGET}
+
+# We always pretend .MAKE was called 'make'
+# and strip ${.CURDIR}/ from the output
+# and replace anything after 'stopped in' with unit-tests
+# so the results can be compared.
+.rawout.out:
+ @echo postprocess ${.TARGET}
+ @${TOOL_SED} -e 's,^${TEST_MAKE:T:C/\./\\\./g}[][0-9]*:,make:,' \
+ -e 's,${TEST_MAKE:C/\./\\\./g},make,' \
+ -e '/stopped/s, /.*, unit-tests,' \
+ -e 's,${.CURDIR:C/\./\\\./g}/,,g' \
+ -e 's,${UNIT_TESTS:C/\./\\\./g}/,,g' \
+ < ${.IMPSRC} > ${.TARGET}.tmp
+ @echo "exit status `cat ${.TARGET:R}.status`" >> ${.TARGET}.tmp
+ @mv ${.TARGET}.tmp ${.TARGET}
+
+# Compare all output files
+test: ${OUTFILES} .PHONY
+ @failed= ; \
+ for test in ${TESTNAMES}; do \
+ diff -u ${UNIT_TESTS}/$${test}.exp $${test}.out \
+ || failed="$${failed}$${failed:+ }$${test}" ; \
+ done ; \
+ if [ -n "$${failed}" ]; then \
+ echo "Failed tests: $${failed}" ; false ; \
+ else \
+ echo "All tests passed" ; \
+ fi
+
+accept:
+ @for test in ${TESTNAMES}; do \
+ cmp -s ${UNIT_TESTS}/$${test}.exp $${test}.out \
+ || { echo "Replacing $${test}.exp" ; \
+ cp $${test}.out ${UNIT_TESTS}/$${test}.exp ; } \
+ done
+
+.if exists(${TEST_MAKE})
+${TESTNAMES:S/$/.rawout/}: ${TEST_MAKE}
+.endif
+
+.-include <bsd.obj.mk>
diff --git a/usr.bin/make/unit-tests/comment.exp b/usr.bin/make/unit-tests/comment.exp
new file mode 100644
index 0000000..9a97df0
--- /dev/null
+++ b/usr.bin/make/unit-tests/comment.exp
@@ -0,0 +1,5 @@
+comment testing start
+this is foo
+This is how a comment looks: # comment
+comment testing done
+exit status 0
diff --git a/usr.bin/make/unit-tests/comment.mk b/usr.bin/make/unit-tests/comment.mk
new file mode 100644
index 0000000..7dd7dbb
--- /dev/null
+++ b/usr.bin/make/unit-tests/comment.mk
@@ -0,0 +1,31 @@
+# This is a comment
+.if ${MACHINE_ARCH} == something
+FOO=bar
+.endif
+
+#\
+ Multiline comment
+
+BAR=# defined
+FOOBAR= # defined
+
+# This is an escaped comment \
+that keeps going until the end of this line
+
+# Another escaped comment \
+that \
+goes \
+on
+
+# This is NOT an escaped comment due to the double backslashes \\
+all: hi foo bar
+ @echo comment testing done
+
+hi:
+ @echo comment testing start
+
+foo:
+ @echo this is $@
+
+bar:
+ @echo This is how a comment looks: '# comment'
diff --git a/usr.bin/make/unit-tests/cond1.exp b/usr.bin/make/unit-tests/cond1.exp
new file mode 100644
index 0000000..701d504
--- /dev/null
+++ b/usr.bin/make/unit-tests/cond1.exp
@@ -0,0 +1,23 @@
+make: "cond1.mk" line 75: warning: extra else
+make: "cond1.mk" line 85: warning: extra else
+2 is prime
+A='other' B='unknown' C='clever' o='no,no'
+Passed:
+ var
+ ("var")
+ (var != var)
+ var != var
+ !((var != var) && defined(name))
+ var == quoted
+
+1 is not prime
+2 is prime
+3 is prime
+4 is not prime
+5 is prime
+
+make: warning: String comparison operator should be either == or !=
+make: Bad conditional expression `"0" > 0' in "0" > 0?OK:No
+
+OK
+exit status 0
diff --git a/usr.bin/make/unit-tests/cond1.mk b/usr.bin/make/unit-tests/cond1.mk
new file mode 100644
index 0000000..e361832
--- /dev/null
+++ b/usr.bin/make/unit-tests/cond1.mk
@@ -0,0 +1,109 @@
+# $Id: cond1.mk,v 1.1 2014/08/21 13:44:51 apb Exp $
+
+# hard code these!
+TEST_UNAME_S= NetBSD
+TEST_UNAME_M= sparc
+TEST_MACHINE= i386
+
+.if ${TEST_UNAME_S}
+Ok=var,
+.endif
+.if ("${TEST_UNAME_S}")
+Ok+=(\"var\"),
+.endif
+.if (${TEST_UNAME_M} != ${TEST_MACHINE})
+Ok+=(var != var),
+.endif
+.if ${TEST_UNAME_M} != ${TEST_MACHINE}
+Ok+= var != var,
+.endif
+.if !((${TEST_UNAME_M} != ${TEST_MACHINE}) && defined(X))
+Ok+= !((var != var) && defined(name)),
+.endif
+# from bsd.obj.mk
+MKOBJ?=no
+.if ${MKOBJ} == "no"
+o= no
+Ok+= var == "quoted",
+.else
+.if defined(notMAKEOBJDIRPREFIX) || defined(norMAKEOBJDIR)
+.if defined(notMAKEOBJDIRPREFIX)
+o=${MAKEOBJDIRPREFIX}${__curdir}
+.else
+o= ${MAKEOBJDIR}
+.endif
+.endif
+o= o
+.endif
+
+# repeat the above to check we get the same result
+.if ${MKOBJ} == "no"
+o2= no
+.else
+.if defined(notMAKEOBJDIRPREFIX) || defined(norMAKEOBJDIR)
+.if defined(notMAKEOBJDIRPREFIX)
+o2=${MAKEOBJDIRPREFIX}${__curdir}
+.else
+o2= ${MAKEOBJDIR}
+.endif
+.endif
+o2= o
+.endif
+
+PRIMES=2 3 5 7 11
+NUMBERS=1 2 3 4 5
+
+n=2
+.if ${PRIMES:M$n} == ""
+X=not
+.else
+X=
+.endif
+
+.if ${MACHINE_ARCH} == no-such
+A=one
+.else
+.if ${MACHINE_ARCH} == not-this
+.if ${MACHINE_ARCH} == something-else
+A=unlikely
+.else
+A=no
+.endif
+.endif
+A=other
+# We expect an extra else warning - we're not skipping here
+.else
+A=this should be an error
+.endif
+
+.if $X != ""
+.if $X == not
+B=one
+.else
+B=other
+# We expect an extra else warning - we are skipping here
+.else
+B=this should be an error
+.endif
+.else
+B=unknown
+.endif
+
+.if "quoted" == quoted
+C=clever
+.else
+C=dim
+.endif
+
+.if defined(nosuch) && ${nosuch:Mx} != ""
+# this should not happen
+.info nosuch is x
+.endif
+
+all:
+ @echo "$n is $X prime"
+ @echo "A='$A' B='$B' C='$C' o='$o,${o2}'"
+ @echo "Passed:${.newline} ${Ok:S/,/${.newline}/}"
+ @echo "${NUMBERS:@n@$n is ${("${PRIMES:M$n}" == ""):?not:} prime${.newline}@}"
+ @echo "${"${DoNotQuoteHere:U0}" > 0:?OK:No}"
+ @echo "${${NoSuchNumber:U42} > 0:?OK:No}"
diff --git a/usr.bin/make/unit-tests/cond2.exp b/usr.bin/make/unit-tests/cond2.exp
new file mode 100644
index 0000000..22e76a5
--- /dev/null
+++ b/usr.bin/make/unit-tests/cond2.exp
@@ -0,0 +1,7 @@
+make: Bad conditional expression ` == "empty"' in == "empty"?oops:ok
+make: "cond2.mk" line 13: Malformed conditional ({TEST_TYPO} == "Ok")
+TEST_NOT_SET is empty or not defined
+make: "cond2.mk" line 20: Malformed conditional (${TEST_NOT_SET} == "empty")
+make: Fatal errors encountered -- cannot continue
+make: stopped in unit-tests
+exit status 1
diff --git a/usr.bin/make/unit-tests/cond2.mk b/usr.bin/make/unit-tests/cond2.mk
new file mode 100644
index 0000000..aeb5a8c
--- /dev/null
+++ b/usr.bin/make/unit-tests/cond2.mk
@@ -0,0 +1,29 @@
+# $Id: cond2.mk,v 1.2 2015/12/02 00:28:24 sjg Exp $
+
+TEST_UNAME_S= NetBSD
+
+# this should be ok
+X:= ${${TEST_UNAME_S} == "NetBSD":?Ok:fail}
+.if $X == "Ok"
+Y= good
+.endif
+# expect: Bad conditional expression ` == "empty"' in == "empty"?oops:ok
+X:= ${${TEST_NOT_SET} == "empty":?oops:ok}
+# expect: Malformed conditional ({TEST_TYPO} == "Ok")
+.if {TEST_TYPO} == "Ok"
+Y= oops
+.endif
+.if empty(TEST_NOT_SET)
+Y!= echo TEST_NOT_SET is empty or not defined >&2; echo
+.endif
+# expect: Malformed conditional (${TEST_NOT_SET} == "empty")
+.if ${TEST_NOT_SET} == "empty"
+Y= oops
+.endif
+
+.if defined(.NDEF) && ${.NDEF} > 0
+Z= yes
+.endif
+
+all:
+ @echo $@
diff --git a/usr.bin/make/unit-tests/doterror.exp b/usr.bin/make/unit-tests/doterror.exp
new file mode 100644
index 0000000..5655644
--- /dev/null
+++ b/usr.bin/make/unit-tests/doterror.exp
@@ -0,0 +1,9 @@
+At first, I am
+happy
+and now: sad
+*** Error code 1
+
+Stop.
+make: stopped in unit-tests
+.ERROR: Looks like 'sad' is upset.
+exit status 1
diff --git a/usr.bin/make/unit-tests/doterror.mk b/usr.bin/make/unit-tests/doterror.mk
new file mode 100644
index 0000000..7f1c78f
--- /dev/null
+++ b/usr.bin/make/unit-tests/doterror.mk
@@ -0,0 +1,20 @@
+# $Id: doterror.mk,v 1.1 2014/08/21 13:44:51 apb Exp $
+
+
+.BEGIN:
+ @echo At first, I am
+
+.END:
+ @echo not reached
+
+.ERROR:
+ @echo "$@: Looks like '${.ERROR_TARGET}' is upset."
+
+all: happy sad
+
+happy:
+ @echo $@
+
+sad:
+ @echo and now: $@; exit 1
+
diff --git a/usr.bin/make/unit-tests/dotwait.exp b/usr.bin/make/unit-tests/dotwait.exp
new file mode 100644
index 0000000..bdc0a0e
--- /dev/null
+++ b/usr.bin/make/unit-tests/dotwait.exp
@@ -0,0 +1,30 @@
+simple.1
+simple.1
+simple.2
+simple.2
+recursive.1.1.*
+recursive.1.1.*
+recursive.1.1.*
+recursive.1.1.*
+recursive.1.99
+recursive.1.99
+recursive.2.1.*
+recursive.2.1.*
+recursive.2.1.*
+recursive.2.1.*
+recursive.2.99
+recursive.2.99
+shared.0
+shared.0
+shared.1.99
+shared.1.99
+shared.2.1
+shared.2.1
+shared.2.99
+shared.2.99
+cycle.1.99
+cycle.1.99
+make: Graph cycles through `cycle.2.99'
+make: Graph cycles through `cycle.2.98'
+make: Graph cycles through `cycle.2.97'
+exit status 0
diff --git a/usr.bin/make/unit-tests/dotwait.mk b/usr.bin/make/unit-tests/dotwait.mk
new file mode 100644
index 0000000..bab5993
--- /dev/null
+++ b/usr.bin/make/unit-tests/dotwait.mk
@@ -0,0 +1,61 @@
+# $NetBSD: dotwait.mk,v 1.2 2017/10/08 20:44:19 sjg Exp $
+
+THISMAKEFILE:= ${.PARSEDIR}/${.PARSEFILE}
+
+TESTS= simple recursive shared cycle
+PAUSE= sleep 1
+
+# Use a .for loop rather than dependencies here, to ensure
+# that the tests are run one by one, with parallelism
+# only within tests.
+# Ignore "--- target ---" lines printed by parallel make.
+all:
+.for t in ${TESTS}
+ @${.MAKE} -f ${THISMAKEFILE} -j4 $t 2>&1 | grep -v "^--- "
+.endfor
+
+#
+# Within each test, the names of the sub-targets follow these
+# conventions:
+# * If it's expected that two or more targets may be made in parallel,
+# then the target names will differ only in an alphabetic component
+# such as ".a" or ".b".
+# * If it's expected that two or more targets should be made in sequence
+# then the target names will differ in numeric components, such that
+# lexical ordering of the target names matches the expected order
+# in which the targets should be made.
+#
+# Targets may echo ${PARALLEL_TARG} to print a modified version
+# of their own name, in which alphabetic components like ".a" or ".b"
+# are converted to ".*". Two targets that are expected to
+# be made in parallel will thus print the same strings, so that the
+# output is independent of the order in which these targets are made.
+#
+PARALLEL_TARG= ${.TARGET:C/\.[a-z]/.*/g:Q}
+.DEFAULT:
+ @echo ${PARALLEL_TARG}; ${PAUSE}; echo ${PARALLEL_TARG}
+_ECHOUSE: .USE
+ @echo ${PARALLEL_TARG}; ${PAUSE}; echo ${PARALLEL_TARG}
+
+# simple: no recursion, no cycles
+simple: simple.1 .WAIT simple.2
+
+# recursive: all children of the left hand side of the .WAIT
+# must be made before any child of the right hand side.
+recursive: recursive.1.99 .WAIT recursive.2.99
+recursive.1.99: recursive.1.1.a recursive.1.1.b _ECHOUSE
+recursive.2.99: recursive.2.1.a recursive.2.1.b _ECHOUSE
+
+# shared: both shared.1.99 and shared.2.99 depend on shared.0.
+# shared.0 must be made first, even though it is a child of
+# the right hand side of the .WAIT.
+shared: shared.1.99 .WAIT shared.2.99
+shared.1.99: shared.0 _ECHOUSE
+shared.2.99: shared.2.1 shared.0 _ECHOUSE
+
+# cycle: the cyclic dependency must not cause infinite recursion
+# leading to stack overflow and a crash.
+cycle: cycle.1.99 .WAIT cycle.2.99
+cycle.2.99: cycle.2.98 _ECHOUSE
+cycle.2.98: cycle.2.97 _ECHOUSE
+cycle.2.97: cycle.2.99 _ECHOUSE
diff --git a/usr.bin/make/unit-tests/error.exp b/usr.bin/make/unit-tests/error.exp
new file mode 100644
index 0000000..a2bf71b
--- /dev/null
+++ b/usr.bin/make/unit-tests/error.exp
@@ -0,0 +1,4 @@
+make: "error.mk" line 3: just FYI
+make: "error.mk" line 4: warning: this could be serious
+make: "error.mk" line 5: this is fatal
+exit status 1
diff --git a/usr.bin/make/unit-tests/error.mk b/usr.bin/make/unit-tests/error.mk
new file mode 100644
index 0000000..721ed50
--- /dev/null
+++ b/usr.bin/make/unit-tests/error.mk
@@ -0,0 +1,10 @@
+# $Id: error.mk,v 1.1 2014/08/21 13:44:51 apb Exp $
+
+.info just FYI
+.warning this could be serious
+.error this is fatal
+
+all:
+
+.info.html:
+ @echo this should be ignored
diff --git a/usr.bin/make/unit-tests/escape.exp b/usr.bin/make/unit-tests/escape.exp
new file mode 100644
index 0000000..6238e27
--- /dev/null
+++ b/usr.bin/make/unit-tests/escape.exp
@@ -0,0 +1,104 @@
+var-1bs
+printf "%s=:%s:\n" VAR1BS 111\\111; printf "%s=:%s:\n" VAR1BSa 111\\aaa; printf "%s=:%s:\n" VAR1BSA 111\\aaa; printf "%s=:%s:\n" VAR1BSda 111\\\$\{a\}; printf "%s=:%s:\n" VAR1BSdA 111\\\$\{A\}; printf "%s=:%s:\n" VAR1BSc 111\#\ backslash\ escapes\ comment\ char,\ so\ this\ is\ part\ of\ the\ value; printf "%s=:%s:\n" VAR1BSsc 111\\\ ;
+VAR1BS=:111\111:
+VAR1BSa=:111\aaa:
+VAR1BSA=:111\aaa:
+VAR1BSda=:111\${a}:
+VAR1BSdA=:111\${A}:
+VAR1BSc=:111# backslash escapes comment char, so this is part of the value:
+VAR1BSsc=:111\ :
+var-2bs
+printf "%s=:%s:\n" VAR2BS 222\\\\222; printf "%s=:%s:\n" VAR2BSa 222\\\\aaa; printf "%s=:%s:\n" VAR2BSA 222\\\\aaa; printf "%s=:%s:\n" VAR2BSda 222\\\\\$\{a\}; printf "%s=:%s:\n" VAR2BSdA 222\\\\\$\{A\}; printf "%s=:%s:\n" VAR2BSc 222\\\\; printf "%s=:%s:\n" VAR2BSsc 222\\\\;
+VAR2BS=:222\\222:
+VAR2BSa=:222\\aaa:
+VAR2BSA=:222\\aaa:
+VAR2BSda=:222\\${a}:
+VAR2BSdA=:222\\${A}:
+VAR2BSc=:222\\:
+VAR2BSsc=:222\\:
+var-1bsnl
+printf "%s=:%s:\n" VAR1BSNL 111\ 111; printf "%s=:%s:\n" VAR1BSNLa 111\ aaa; printf "%s=:%s:\n" VAR1BSNLA 111\ aaa; printf "%s=:%s:\n" VAR1BSNLda 111\ \$\{a\}; printf "%s=:%s:\n" VAR1BSNLdA 111\ \$\{A\}; printf "%s=:%s:\n" VAR1BSNLc 111; printf "%s=:%s:\n" VAR1BSNLsc 111;
+VAR1BSNL=:111 111:
+VAR1BSNLa=:111 aaa:
+VAR1BSNLA=:111 aaa:
+VAR1BSNLda=:111 ${a}:
+VAR1BSNLdA=:111 ${A}:
+VAR1BSNLc=:111:
+VAR1BSNLsc=:111:
+var-2bsnl
+printf "%s=:%s:\n" VAR2BSNL 222\\\\; printf "%s=:%s:\n" VAR2BSNLa 222\\\\; printf "%s=:%s:\n" VAR2BSNLA 222\\\\; printf "%s=:%s:\n" VAR2BSNLda 222\\\\; printf "%s=:%s:\n" VAR2BSNLdA 222\\\\; printf "%s=:%s:\n" VAR2BSNLc 222\\\\; printf "%s=:%s:\n" VAR2BSNLsc 222\\\\;
+VAR2BSNL=:222\\:
+VAR2BSNLa=:222\\:
+VAR2BSNLA=:222\\:
+VAR2BSNLda=:222\\:
+VAR2BSNLdA=:222\\:
+VAR2BSNLc=:222\\:
+VAR2BSNLsc=:222\\:
+var-3bsnl
+printf "%s=:%s:\n" VAR3BSNL 333\\\\\ 333=; printf "%s=:%s:\n" VAR3BSNLa 333\\\\\ aaa=; printf "%s=:%s:\n" VAR3BSNLA 333\\\\\ aaa=; printf "%s=:%s:\n" VAR3BSNLda 333\\\\\ \$\{a\}=; printf "%s=:%s:\n" VAR3BSNLdA 333\\\\\ \$\{A\}=; printf "%s=:%s:\n" VAR3BSNLc 333\\\\; printf "%s=:%s:\n" VAR3BSNLsc 333\\\\;
+VAR3BSNL=:333\\ 333=:
+VAR3BSNLa=:333\\ aaa=:
+VAR3BSNLA=:333\\ aaa=:
+VAR3BSNLda=:333\\ ${a}=:
+VAR3BSNLdA=:333\\ ${A}=:
+VAR3BSNLc=:333\\:
+VAR3BSNLsc=:333\\:
+var-1bsnl-space
+printf "%s=:%s:\n" VAR1BSNL00 first\ line; printf "%s=:%s:\n" VAR1BSNL0 first\ line\ no\ space\ on\ second\ line; printf "%s=:%s:\n" VAR1BSNLs first\ line\ one\ space\ on\ second\ line; printf "%s=:%s:\n" VAR1BSNLss first\ line\ two\ spaces\ on\ second\ line; printf "%s=:%s:\n" VAR1BSNLt first\ line\ one\ tab\ on\ second\ line; printf "%s=:%s:\n" VAR1BSNLtt first\ line\ two\ tabs\ on\ second\ line; printf "%s=:%s:\n" VAR1BSNLxx first\ line\ many\ spaces\ and\ tabs\ \[\ \ \ \ \]\ on\ second\ line;
+VAR1BSNL00=:first line:
+VAR1BSNL0=:first line no space on second line:
+VAR1BSNLs=:first line one space on second line:
+VAR1BSNLss=:first line two spaces on second line:
+VAR1BSNLt=:first line one tab on second line:
+VAR1BSNLtt=:first line two tabs on second line:
+VAR1BSNLxx=:first line many spaces and tabs [ ] on second line:
+cmd-1bsnl
+echo :'first line\
+#second line without space\
+third line':
+:first line\
+#second line without space\
+third line:
+echo :'first line\
+ second line spaces should be retained':
+:first line\
+ second line spaces should be retained:
+echo :'first line\
+second line tab should be elided':
+:first line\
+second line tab should be elided:
+echo :'first line\
+ only one tab should be elided, second tab remains'
+:first line\
+ only one tab should be elided, second tab remains
+cmd-1bsnl-eof
+echo :'command ending with backslash-newline'; \
+
+:command ending with backslash-newline
+cmd-2bsnl
+echo take one\\
+take one\
+echo take two\\
+take two\
+echo take three\\
+take three\
+cmd-3bsnl
+echo :'first line\\\
+#second line without space\\\
+third line':
+:first line\\\
+#second line without space\\\
+third line:
+echo :'first line\\\
+ second line spaces should be retained':
+:first line\\\
+ second line spaces should be retained:
+echo :'first line\\\
+second line tab should be elided':
+:first line\\\
+second line tab should be elided:
+echo :'first line\\\
+ only one tab should be elided, second tab remains'
+:first line\\\
+ only one tab should be elided, second tab remains
+exit status 0
diff --git a/usr.bin/make/unit-tests/escape.mk b/usr.bin/make/unit-tests/escape.mk
new file mode 100644
index 0000000..829403d
--- /dev/null
+++ b/usr.bin/make/unit-tests/escape.mk
@@ -0,0 +1,246 @@
+# $Id: escape.mk,v 1.10 2014/09/09 10:22:27 apb Exp $
+#
+# Test backslash escaping.
+
+# Extracts from the POSIX 2008 specification
+# <http://pubs.opengroup.org/onlinepubs/9699919799/utilities/make.html>:
+#
+# Comments start with a <number-sign> ( '#' ) and continue until an
+# unescaped <newline> is reached.
+#
+# When an escaped <newline> (one preceded by a <backslash>) is found
+# anywhere in the makefile except in a command line, an include
+# line, or a line immediately preceding an include line, it shall
+# be replaced, along with any leading white space on the following
+# line, with a single <space>.
+#
+# When an escaped <newline> is found in a command line in a
+# makefile, the command line shall contain the <backslash>, the
+# <newline>, and the next line, except that the first character of
+# the next line shall not be included if it is a <tab>.
+#
+# When an escaped <newline> is found in an include line or in a
+# line immediately preceding an include line, the behavior is
+# unspecified.
+#
+# Notice that the behaviour of <backslash><backslash> or
+# <backslash><anything other than newline> is not mentioned. I think
+# this implies that <backslash> should be taken literally everywhere
+# except before <newline>.
+#
+# Our practice, despite what POSIX might say, is that "\#"
+# in a variable assignment stores "#" as part of the value.
+# The "\" is not taken literally, and the "#" does not begin a comment.
+#
+# Also, our practice is that an even number of backslashes before a
+# newline in a variable assignment simply stores the backslashes as part
+# of the value, and treats the newline as though it was not escaped.
+# Similarly, ann even number of backslashes before a newline in a
+# command simply uses the backslashes as part of the command test, but
+# does not escape the newline. This is compatible with GNU make.
+
+all: .PHONY
+# We will add dependencies like "all: yet-another-test" later.
+
+# Some variables to be expanded in tests
+#
+a = aaa
+A = ${a}
+
+# Backslash at end of line in a comment\
+should continue the comment. \
+# This is also tested in comment.mk.
+
+__printvars: .USE .MADE
+ @echo ${.TARGET}
+ ${.ALLSRC:@v@ printf "%s=:%s:\n" ${v:Q} ${${v}:Q}; @}
+
+# Embedded backslash in variable should be taken literally.
+#
+VAR1BS = 111\111
+VAR1BSa = 111\${a}
+VAR1BSA = 111\${A}
+VAR1BSda = 111\$${a}
+VAR1BSdA = 111\$${A}
+VAR1BSc = 111\# backslash escapes comment char, so this is part of the value
+VAR1BSsc = 111\ # This is a comment. Value ends with <backslash><space>
+
+all: var-1bs
+var-1bs: .PHONY __printvars VAR1BS VAR1BSa VAR1BSA VAR1BSda VAR1BSdA \
+ VAR1BSc VAR1BSsc
+
+# Double backslash in variable should be taken as two literal backslashes.
+#
+VAR2BS = 222\\222
+VAR2BSa = 222\\${a}
+VAR2BSA = 222\\${A}
+VAR2BSda = 222\\$${a}
+VAR2BSdA = 222\\$${A}
+VAR2BSc = 222\\# backslash does not escape comment char, so this is a comment
+VAR2BSsc = 222\\ # This is a comment. Value ends with <backslash><backslash>
+
+all: var-2bs
+var-2bs: .PHONY __printvars VAR2BS VAR2BSa VAR2BSA VAR2BSda VAR2BSdA \
+ VAR2BSc VAR2BSsc
+
+# Backslash-newline in a variable setting is replaced by a single space.
+#
+VAR1BSNL = 111\
+111
+VAR1BSNLa = 111\
+${a}
+VAR1BSNLA = 111\
+${A}
+VAR1BSNLda = 111\
+$${a}
+VAR1BSNLdA = 111\
+$${A}
+VAR1BSNLc = 111\
+# this should be processed as a comment
+VAR1BSNLsc = 111\
+ # this should be processed as a comment
+
+all: var-1bsnl
+var-1bsnl: .PHONY
+var-1bsnl: .PHONY __printvars \
+ VAR1BSNL VAR1BSNLa VAR1BSNLA VAR1BSNLda VAR1BSNLdA \
+ VAR1BSNLc VAR1BSNLsc
+
+# Double-backslash-newline in a variable setting.
+# Both backslashes should be taken literally, and the newline is NOT escaped.
+#
+# The second lines below each end with '=' so that they will not
+# generate syntax errors regardless of whether or not they are
+# treated as part of the value.
+#
+VAR2BSNL = 222\\
+222=
+VAR2BSNLa = 222\\
+${a}=
+VAR2BSNLA = 222\\
+${A}=
+VAR2BSNLda = 222\\
+$${a}=
+VAR2BSNLdA = 222\\
+$${A}=
+VAR2BSNLc = 222\\
+# this should be processed as a comment
+VAR2BSNLsc = 222\\
+ # this should be processed as a comment
+
+all: var-2bsnl
+var-2bsnl: .PHONY __printvars \
+ VAR2BSNL VAR2BSNLa VAR2BSNLA VAR2BSNLda VAR2BSNLdA \
+ VAR2BSNLc VAR2BSNLsc
+
+# Triple-backslash-newline in a variable setting.
+# First two should be taken literally, and last should escape the newline.
+#
+# The second lines below each end with '=' so that they will not
+# generate syntax errors regardless of whether or not they are
+# treated as part of the value.
+#
+VAR3BSNL = 333\\\
+333=
+VAR3BSNLa = 333\\\
+${a}=
+VAR3BSNLA = 333\\\
+${A}=
+VAR3BSNLda = 333\\\
+$${a}=
+VAR3BSNLdA = 333\\\
+$${A}=
+VAR3BSNLc = 333\\\
+# this should be processed as a comment
+VAR3BSNLsc = 333\\\
+ # this should be processed as a comment
+
+all: var-3bsnl
+var-3bsnl: .PHONY __printvars \
+ VAR3BSNL VAR3BSNLa VAR3BSNLA VAR3BSNLda VAR3BSNLdA \
+ VAR3BSNLc VAR3BSNLsc
+
+# Backslash-newline in a variable setting, plus any amount of white space
+# on the next line, is replaced by a single space.
+#
+VAR1BSNL00= first line\
+
+# above line is entirely empty, and this is a comment
+VAR1BSNL0= first line\
+no space on second line
+VAR1BSNLs= first line\
+ one space on second line
+VAR1BSNLss= first line\
+ two spaces on second line
+VAR1BSNLt= first line\
+ one tab on second line
+VAR1BSNLtt= first line\
+ two tabs on second line
+VAR1BSNLxx= first line\
+ many spaces and tabs [ ] on second line
+
+all: var-1bsnl-space
+var-1bsnl-space: .PHONY __printvars \
+ VAR1BSNL00 VAR1BSNL0 VAR1BSNLs VAR1BSNLss VAR1BSNLt VAR1BSNLtt \
+ VAR1BSNLxx
+
+# Backslash-newline in a command is retained.
+#
+# The "#" in "# second line without space" makes it a comment instead
+# of a syntax error if the preceding line is parsed incorretly.
+# The ":" in "third line':" makes it look like the start of a
+# target instead of a syntax error if the first line is parsed incorrectly.
+#
+all: cmd-1bsnl
+cmd-1bsnl: .PHONY
+ @echo ${.TARGET}
+ echo :'first line\
+#second line without space\
+third line':
+ echo :'first line\
+ second line spaces should be retained':
+ echo :'first line\
+ second line tab should be elided':
+ echo :'first line\
+ only one tab should be elided, second tab remains'
+
+# When backslash-newline appears at the end of a command script,
+# both the backslash and the newline should be passed to the shell.
+# The shell should elide the backslash-newline.
+#
+all: cmd-1bsnl-eof
+cmd-1bsnl-eof:
+ @echo ${.TARGET}
+ echo :'command ending with backslash-newline'; \
+
+# above line must be blank
+
+# Double-backslash-newline in a command.
+# Both backslashes are retained, but the newline is not escaped.
+# XXX: This may differ from POSIX, but matches gmake.
+#
+# When make passes two backslashes to the shell, the shell will pass one
+# backslash to the echo commant.
+#
+all: cmd-2bsnl
+cmd-2bsnl: .PHONY
+ @echo ${.TARGET}
+ echo take one\\
+# this should be a comment
+ echo take two\\
+ echo take three\\
+
+# Triple-backslash-newline in a command is retained.
+#
+all: cmd-3bsnl
+cmd-3bsnl: .PHONY
+ @echo ${.TARGET}
+ echo :'first line\\\
+#second line without space\\\
+third line':
+ echo :'first line\\\
+ second line spaces should be retained':
+ echo :'first line\\\
+ second line tab should be elided':
+ echo :'first line\\\
+ only one tab should be elided, second tab remains'
diff --git a/usr.bin/make/unit-tests/export-all.exp b/usr.bin/make/unit-tests/export-all.exp
new file mode 100644
index 0000000..e3aefd4
--- /dev/null
+++ b/usr.bin/make/unit-tests/export-all.exp
@@ -0,0 +1,12 @@
+UT_ALL=even this gets exported
+UT_BADDIR=unit-tests
+UT_DOLLAR=This is $UT_FU
+UT_F=fine
+UT_FOO=foobar is fubar
+UT_FU=fubar
+UT_NO=all
+UT_OK=good
+UT_OKDIR=unit-tests
+UT_TEST=export-all
+UT_ZOO=hoopie
+exit status 0
diff --git a/usr.bin/make/unit-tests/export-all.mk b/usr.bin/make/unit-tests/export-all.mk
new file mode 100644
index 0000000..576487b
--- /dev/null
+++ b/usr.bin/make/unit-tests/export-all.mk
@@ -0,0 +1,23 @@
+# $Id: export-all.mk,v 1.2 2015/04/10 20:41:59 sjg Exp $
+
+UT_OK=good
+UT_F=fine
+
+# the old way to do :tA
+M_tAbad = C,.*,cd & \&\& 'pwd',:sh
+# the new
+M_tA = tA
+
+here := ${.PARSEDIR}
+
+# this will cause trouble (recursing if we let it)
+UT_BADDIR = ${${here}/../${here:T}:L:${M_tAbad}:T}
+# this will be ok
+UT_OKDIR = ${${here}/../${here:T}:L:${M_tA}:T}
+
+.export
+
+.include "export.mk"
+
+UT_TEST=export-all
+UT_ALL=even this gets exported
diff --git a/usr.bin/make/unit-tests/export-env.exp b/usr.bin/make/unit-tests/export-env.exp
new file mode 100644
index 0000000..8a779e6
--- /dev/null
+++ b/usr.bin/make/unit-tests/export-env.exp
@@ -0,0 +1,11 @@
+make:
+UT_TEST=export-env.mk
+UT_ENV=not-exported
+UT_EXP=not-exported
+UT_LIT=literal export-env.mk
+env:
+UT_TEST=export-env.mk
+UT_ENV=exported
+UT_EXP=exported
+UT_LIT=literal ${UT_TEST}
+exit status 0
diff --git a/usr.bin/make/unit-tests/export-env.mk b/usr.bin/make/unit-tests/export-env.mk
new file mode 100644
index 0000000..c4d3e75
--- /dev/null
+++ b/usr.bin/make/unit-tests/export-env.mk
@@ -0,0 +1,27 @@
+# $Id: export-env.mk,v 1.2 2016/02/18 20:25:08 sjg Exp $
+
+# our normal .export, subsequent changes affect the environment
+UT_TEST=this
+.export UT_TEST
+UT_TEST:= ${.PARSEFILE}
+
+# not so with .export-env
+UT_ENV=exported
+.export-env UT_ENV
+UT_ENV=not-exported
+
+# gmake style export goes further; affects nothing but the environment
+UT_EXP=before-export
+export UT_EXP=exported
+UT_EXP=not-exported
+
+UT_LIT= literal ${UT_TEST}
+.export-literal UT_LIT
+
+all:
+ @echo make:; ${UT_TEST UT_ENV UT_EXP UT_LIT:L:@v@echo $v=${$v};@}
+ @echo env:; ${UT_TEST UT_ENV UT_EXP UT_LIT:L:@v@echo $v=$${$v};@}
+
+
+
+
diff --git a/usr.bin/make/unit-tests/export.exp b/usr.bin/make/unit-tests/export.exp
new file mode 100644
index 0000000..143771c
--- /dev/null
+++ b/usr.bin/make/unit-tests/export.exp
@@ -0,0 +1,6 @@
+UT_DOLLAR=This is $UT_FU
+UT_FOO=foobar is fubar
+UT_FU=fubar
+UT_TEST=export
+UT_ZOO=hoopie
+exit status 0
diff --git a/usr.bin/make/unit-tests/export.mk b/usr.bin/make/unit-tests/export.mk
new file mode 100644
index 0000000..1b4ee72
--- /dev/null
+++ b/usr.bin/make/unit-tests/export.mk
@@ -0,0 +1,22 @@
+# $Id: export.mk,v 1.1 2014/08/21 13:44:51 apb Exp $
+
+UT_TEST=export
+UT_FOO=foo${BAR}
+UT_FU=fubar
+UT_ZOO=hoopie
+UT_NO=all
+# belive it or not, we expect this one to come out with $UT_FU unexpanded.
+UT_DOLLAR= This is $$UT_FU
+
+.export UT_FU UT_FOO
+.export UT_DOLLAR
+# this one will be ignored
+.export .MAKE.PID
+
+BAR=bar is ${UT_FU}
+
+.MAKE.EXPORTED+= UT_ZOO UT_TEST
+
+all:
+ @env | grep '^UT_' | sort
+
diff --git a/usr.bin/make/unit-tests/forloop.exp b/usr.bin/make/unit-tests/forloop.exp
new file mode 100644
index 0000000..df14b75
--- /dev/null
+++ b/usr.bin/make/unit-tests/forloop.exp
@@ -0,0 +1,19 @@
+x=one
+x="two and three"
+x=four
+x="five"
+x=-I/this
+x=-I"This or that"
+x=-Ithat
+x="-DTHIS=\"this and that\""
+cfl=-I/this -I"This or that" -Ithat "-DTHIS=\"this and that\""
+a=one b="two and three"
+a=four b="five"
+a=ONE b="TWO AND THREE"
+a=FOUR b="FIVE"
+We expect an error next:
+make: "forloop.mk" line 38: Wrong number of words (9) in .for substitution list with 2 vars
+make: Fatal errors encountered -- cannot continue
+make: stopped in unit-tests
+OK
+exit status 0
diff --git a/usr.bin/make/unit-tests/forloop.mk b/usr.bin/make/unit-tests/forloop.mk
new file mode 100644
index 0000000..e0399f3
--- /dev/null
+++ b/usr.bin/make/unit-tests/forloop.mk
@@ -0,0 +1,45 @@
+# $Id: forloop.mk,v 1.1 2014/08/21 13:44:51 apb Exp $
+
+all: for-loop
+
+LIST = one "two and three" four "five"
+
+.if make(for-fail)
+for-fail:
+
+XTRA_LIST = xtra
+.else
+
+.for x in ${LIST}
+X!= echo 'x=$x' >&2; echo
+.endfor
+
+CFL = -I/this -I"This or that" -Ithat "-DTHIS=\"this and that\""
+cfl=
+.for x in ${CFL}
+X!= echo 'x=$x' >&2; echo
+.if empty(cfl)
+cfl= $x
+.else
+cfl+= $x
+.endif
+.endfor
+X!= echo 'cfl=${cfl}' >&2; echo
+
+.if ${cfl} != ${CFL}
+.error ${.newline}'${cfl}' != ${.newline}'${CFL}'
+.endif
+
+.for a b in ${EMPTY}
+X!= echo 'a=$a b=$b' >&2; echo
+.endfor
+.endif
+
+.for a b in ${LIST} ${LIST:tu} ${XTRA_LIST}
+X!= echo 'a=$a b=$b' >&2; echo
+.endfor
+
+for-loop:
+ @echo We expect an error next:
+ @(cd ${.CURDIR} && ${.MAKE} -f ${MAKEFILE} for-fail) && \
+ { echo "Oops that should have failed!"; exit 1; } || echo OK
diff --git a/usr.bin/make/unit-tests/forsubst.exp b/usr.bin/make/unit-tests/forsubst.exp
new file mode 100644
index 0000000..0a98c00
--- /dev/null
+++ b/usr.bin/make/unit-tests/forsubst.exp
@@ -0,0 +1,2 @@
+.for with :S;... OK
+exit status 0
diff --git a/usr.bin/make/unit-tests/forsubst.mk b/usr.bin/make/unit-tests/forsubst.mk
new file mode 100644
index 0000000..00cd9b6
--- /dev/null
+++ b/usr.bin/make/unit-tests/forsubst.mk
@@ -0,0 +1,10 @@
+# $Id: forsubst.mk,v 1.1 2014/08/21 13:44:51 apb Exp $
+
+all: for-subst
+
+here := ${.PARSEDIR}
+# this should not run foul of the parser
+.for file in ${.PARSEFILE}
+for-subst: ${file:S;^;${here}/;g}
+ @echo ".for with :S;... OK"
+.endfor
diff --git a/usr.bin/make/unit-tests/hash.exp b/usr.bin/make/unit-tests/hash.exp
new file mode 100644
index 0000000..0a24234
--- /dev/null
+++ b/usr.bin/make/unit-tests/hash.exp
@@ -0,0 +1,9 @@
+b2af338b
+3360ac65
+7747f046
+9ca87054
+880fe816
+208fcbd3
+d5d376eb
+de41416c
+exit status 0
diff --git a/usr.bin/make/unit-tests/hash.mk b/usr.bin/make/unit-tests/hash.mk
new file mode 100644
index 0000000..1ed84e7
--- /dev/null
+++ b/usr.bin/make/unit-tests/hash.mk
@@ -0,0 +1,18 @@
+STR1=
+STR2= a
+STR3= ab
+STR4= abc
+STR5= abcd
+STR6= abcde
+STR7= abcdef
+STR8= abcdefghijklmnopqrstuvwxyz
+
+all:
+ @echo ${STR1:hash}
+ @echo ${STR2:hash}
+ @echo ${STR3:hash}
+ @echo ${STR4:hash}
+ @echo ${STR5:hash}
+ @echo ${STR6:hash}
+ @echo ${STR7:hash}
+ @echo ${STR8:hash}
diff --git a/usr.bin/make/unit-tests/impsrc.exp b/usr.bin/make/unit-tests/impsrc.exp
new file mode 100644
index 0000000..23e8347
--- /dev/null
+++ b/usr.bin/make/unit-tests/impsrc.exp
@@ -0,0 +1,13 @@
+expected: source4
+actual: source4
+expected: target1.x
+actual: target1.x
+expected: target1.y
+actual: target1.y
+expected: source1
+actual: source1
+expected: source2
+actual: source2
+expected: source1
+actual: source1
+exit status 0
diff --git a/usr.bin/make/unit-tests/impsrc.mk b/usr.bin/make/unit-tests/impsrc.mk
new file mode 100644
index 0000000..95ae0c3
--- /dev/null
+++ b/usr.bin/make/unit-tests/impsrc.mk
@@ -0,0 +1,43 @@
+# $NetBSD: impsrc.mk,v 1.2 2014/08/30 22:21:07 sjg Exp $
+
+# Does ${.IMPSRC} work properly?
+# It should be set, in order of precedence, to ${.TARGET} of:
+# 1) the implied source of a transformation rule,
+# 2) the first prerequisite from the dependency line of an explicit rule, or
+# 3) the first prerequisite of an explicit rule.
+#
+
+all: target1.z target2 target3 target4
+
+.SUFFIXES: .x .y .z
+
+.x.y: source1
+ @echo 'expected: target1.x'
+ @echo 'actual: $<'
+
+.y.z: source2
+ @echo 'expected: target1.y'
+ @echo 'actual: $<'
+
+target1.y: source3
+
+target1.x: source4
+ @echo 'expected: source4'
+ @echo 'actual: $<'
+
+target2: source1 source2
+ @echo 'expected: source1'
+ @echo 'actual: $<'
+
+target3: source1
+target3: source2 source3
+ @echo 'expected: source2'
+ @echo 'actual: $<'
+
+target4: source1
+target4:
+ @echo 'expected: source1'
+ @echo 'actual: $<'
+
+source1 source2 source3 source4:
+
diff --git a/usr.bin/make/unit-tests/misc.exp b/usr.bin/make/unit-tests/misc.exp
new file mode 100644
index 0000000..39a9383
--- /dev/null
+++ b/usr.bin/make/unit-tests/misc.exp
@@ -0,0 +1 @@
+exit status 0
diff --git a/usr.bin/make/unit-tests/misc.mk b/usr.bin/make/unit-tests/misc.mk
new file mode 100644
index 0000000..2773e30
--- /dev/null
+++ b/usr.bin/make/unit-tests/misc.mk
@@ -0,0 +1,16 @@
+# $Id: misc.mk,v 1.1 2014/08/21 13:44:51 apb Exp $
+
+.if !exists(${.CURDIR}/)
+.warning ${.CURDIR}/ doesn't exist ?
+.endif
+
+.if !exists(${.CURDIR}/.)
+.warning ${.CURDIR}/. doesn't exist ?
+.endif
+
+.if !exists(${.CURDIR}/..)
+.warning ${.CURDIR}/.. doesn't exist ?
+.endif
+
+all:
+ @: all is well
diff --git a/usr.bin/make/unit-tests/moderrs.exp b/usr.bin/make/unit-tests/moderrs.exp
new file mode 100644
index 0000000..cb51aa0
--- /dev/null
+++ b/usr.bin/make/unit-tests/moderrs.exp
@@ -0,0 +1,16 @@
+Expect: Unknown modifier 'Z'
+make: Unknown modifier 'Z'
+VAR:Z=
+Expect: Unknown modifier 'Z'
+make: Unknown modifier 'Z'
+VAR:Z=
+Expect: Unclosed variable specification for VAR
+make: Unclosed variable specification (expecting '}') for "VAR" (value "Thevariable") modifier S
+VAR:S,V,v,=Thevariable
+Expect: Unclosed variable specification for VAR
+make: Unclosed variable specification after complex modifier (expecting '}') for VAR
+VAR:S,V,v,=Thevariable
+Expect: Unclosed substitution for VAR (, missing)
+make: Unclosed substitution for VAR (, missing)
+VAR:S,V,v=
+exit status 0
diff --git a/usr.bin/make/unit-tests/moderrs.mk b/usr.bin/make/unit-tests/moderrs.mk
new file mode 100644
index 0000000..825e6c7
--- /dev/null
+++ b/usr.bin/make/unit-tests/moderrs.mk
@@ -0,0 +1,31 @@
+# $Id: moderrs.mk,v 1.1 2014/08/21 13:44:51 apb Exp $
+#
+# various modifier error tests
+
+VAR=TheVariable
+# incase we have to change it ;-)
+MOD_UNKN=Z
+MOD_TERM=S,V,v
+MOD_S:= ${MOD_TERM},
+
+all: modunkn modunknV varterm vartermV modtermV
+
+modunkn:
+ @echo "Expect: Unknown modifier 'Z'"
+ @echo "VAR:Z=${VAR:Z}"
+
+modunknV:
+ @echo "Expect: Unknown modifier 'Z'"
+ @echo "VAR:${MOD_UNKN}=${VAR:${MOD_UNKN}}"
+
+varterm:
+ @echo "Expect: Unclosed variable specification for VAR"
+ @echo VAR:S,V,v,=${VAR:S,V,v,
+
+vartermV:
+ @echo "Expect: Unclosed variable specification for VAR"
+ @echo VAR:${MOD_TERM},=${VAR:${MOD_S}
+
+modtermV:
+ @echo "Expect: Unclosed substitution for VAR (, missing)"
+ -@echo "VAR:${MOD_TERM}=${VAR:${MOD_TERM}}"
diff --git a/usr.bin/make/unit-tests/modmatch.exp b/usr.bin/make/unit-tests/modmatch.exp
new file mode 100644
index 0000000..a7bf8b7
--- /dev/null
+++ b/usr.bin/make/unit-tests/modmatch.exp
@@ -0,0 +1,20 @@
+LIB=a X_LIBS:M${LIB${LIB:tu}} is "/tmp/liba.a"
+LIB=a X_LIBS:M*/lib${LIB}.a is "/tmp/liba.a"
+LIB=a X_LIBS:M*/lib${LIB}.a:tu is "/TMP/LIBA.A"
+LIB=b X_LIBS:M${LIB${LIB:tu}} is ""
+LIB=b X_LIBS:M*/lib${LIB}.a is ""
+LIB=b X_LIBS:M*/lib${LIB}.a:tu is ""
+LIB=c X_LIBS:M${LIB${LIB:tu}} is ""
+LIB=c X_LIBS:M*/lib${LIB}.a is ""
+LIB=c X_LIBS:M*/lib${LIB}.a:tu is ""
+LIB=d X_LIBS:M${LIB${LIB:tu}} is "/tmp/libd.a"
+LIB=d X_LIBS:M*/lib${LIB}.a is "/tmp/libd.a"
+LIB=d X_LIBS:M*/lib${LIB}.a:tu is "/TMP/LIBD.A"
+LIB=e X_LIBS:M${LIB${LIB:tu}} is "/tmp/libe.a"
+LIB=e X_LIBS:M*/lib${LIB}.a is "/tmp/libe.a"
+LIB=e X_LIBS:M*/lib${LIB}.a:tu is "/TMP/LIBE.A"
+Mscanner=OK
+Upper=One Two Three Four
+Lower=five six seven
+nose=One Three five
+exit status 0
diff --git a/usr.bin/make/unit-tests/modmatch.mk b/usr.bin/make/unit-tests/modmatch.mk
new file mode 100644
index 0000000..4519928
--- /dev/null
+++ b/usr.bin/make/unit-tests/modmatch.mk
@@ -0,0 +1,34 @@
+
+X=a b c d e
+
+.for x in $X
+LIB${x:tu}=/tmp/lib$x.a
+.endfor
+
+X_LIBS= ${LIBA} ${LIBD} ${LIBE}
+
+LIB?=a
+
+var = head
+res = no
+.if !empty(var:M${:Uhead\:tail:C/:.*//})
+res = OK
+.endif
+
+all: show-libs check-cclass
+
+show-libs:
+ @for x in $X; do ${.MAKE} -f ${MAKEFILE} show LIB=$$x; done
+ @echo "Mscanner=${res}"
+
+show:
+ @echo 'LIB=${LIB} X_LIBS:M$${LIB$${LIB:tu}} is "${X_LIBS:M${LIB${LIB:tu}}}"'
+ @echo 'LIB=${LIB} X_LIBS:M*/lib$${LIB}.a is "${X_LIBS:M*/lib${LIB}.a}"'
+ @echo 'LIB=${LIB} X_LIBS:M*/lib$${LIB}.a:tu is "${X_LIBS:M*/lib${LIB}.a:tu}"'
+
+LIST= One Two Three Four five six seven
+
+check-cclass:
+ @echo Upper=${LIST:M[A-Z]*}
+ @echo Lower=${LIST:M[^A-Z]*}
+ @echo nose=${LIST:M[^s]*[ex]}
diff --git a/usr.bin/make/unit-tests/modmisc.exp b/usr.bin/make/unit-tests/modmisc.exp
new file mode 100644
index 0000000..e406647
--- /dev/null
+++ b/usr.bin/make/unit-tests/modmisc.exp
@@ -0,0 +1,10 @@
+path=':/bin:/tmp::/:.:/no/such/dir:.'
+path='/bin:/tmp:/:/no/such/dir'
+path='/bin:/tmp:/:/no/such/dir'
+path='/bin':'/tmp':'/':'/no/such/dir'
+path='/bin':'/tmp':'/':'/no/such/dir'
+path_/usr/xbin=/opt/xbin/
+paths=/bin /tmp / /no/such/dir /opt/xbin
+PATHS=/BIN /TMP / /NO/SUCH/DIR /OPT/XBIN
+The answer is 42
+exit status 0
diff --git a/usr.bin/make/unit-tests/modmisc.mk b/usr.bin/make/unit-tests/modmisc.mk
new file mode 100644
index 0000000..a292b96
--- /dev/null
+++ b/usr.bin/make/unit-tests/modmisc.mk
@@ -0,0 +1,38 @@
+# $Id: modmisc.mk,v 1.1 2014/08/21 13:44:51 apb Exp $
+#
+# miscellaneous modifier tests
+
+# do not put any dirs in this list which exist on some
+# but not all target systems - an exists() check is below.
+path=:/bin:/tmp::/:.:/no/such/dir:.
+# strip cwd from path.
+MOD_NODOT=S/:/ /g:N.:ts:
+# and decorate, note that $'s need to be doubled. Also note that
+# the modifier_variable can be used with other modifiers.
+MOD_NODOTX=S/:/ /g:N.:@d@'$$d'@
+# another mod - pretend it is more interesting
+MOD_HOMES=S,/home/,/homes/,
+MOD_OPT=@d@$${exists($$d):?$$d:$${d:S,/usr,/opt,}}@
+MOD_SEP=S,:, ,g
+
+all: modvar modvarloop modsysv
+
+modsysv:
+ @echo "The answer is ${libfoo.a:L:libfoo.a=42}"
+
+modvar:
+ @echo "path='${path}'"
+ @echo "path='${path:${MOD_NODOT}}'"
+ @echo "path='${path:S,home,homes,:${MOD_NODOT}}'"
+ @echo "path=${path:${MOD_NODOTX}:ts:}"
+ @echo "path=${path:${MOD_HOMES}:${MOD_NODOTX}:ts:}"
+
+.for d in ${path:${MOD_SEP}:N.} /usr/xbin
+path_$d?= ${d:${MOD_OPT}:${MOD_HOMES}}/
+paths+= ${d:${MOD_OPT}:${MOD_HOMES}}
+.endfor
+
+modvarloop:
+ @echo "path_/usr/xbin=${path_/usr/xbin}"
+ @echo "paths=${paths}"
+ @echo "PATHS=${paths:tu}"
diff --git a/usr.bin/make/unit-tests/modorder.exp b/usr.bin/make/unit-tests/modorder.exp
new file mode 100644
index 0000000..4117427
--- /dev/null
+++ b/usr.bin/make/unit-tests/modorder.exp
@@ -0,0 +1,11 @@
+LIST = one two three four five six seven eight nine ten
+LIST:O = eight five four nine one seven six ten three two
+LIST:Ox = Ok
+LIST:O:Ox = Ok
+LISTX = Ok
+LISTSX = Ok
+make: Bad modifier `:OX' for LIST
+BADMOD 1 = }
+make: Bad modifier `:OxXX' for LIST
+BADMOD 2 = XX}
+exit status 0
diff --git a/usr.bin/make/unit-tests/modorder.mk b/usr.bin/make/unit-tests/modorder.mk
new file mode 100644
index 0000000..bc24d33
--- /dev/null
+++ b/usr.bin/make/unit-tests/modorder.mk
@@ -0,0 +1,22 @@
+# $NetBSD: modorder.mk,v 1.1 2014/08/21 13:44:51 apb Exp $
+
+LIST= one two three four five six seven eight nine ten
+LISTX= ${LIST:Ox}
+LISTSX:= ${LIST:Ox}
+TEST_RESULT= && echo Ok || echo Failed
+
+# unit-tests have to produce the same results on each run
+# so we cannot actually include :Ox output.
+all:
+ @echo "LIST = ${LIST}"
+ @echo "LIST:O = ${LIST:O}"
+ # Note that 1 in every 10! trials two independently generated
+ # randomized orderings will be the same. The test framework doesn't
+ # support checking probabilistic output, so we accept that the test
+ # will incorrectly fail with probability 2.8E-7.
+ @echo "LIST:Ox = `test '${LIST:Ox}' != '${LIST:Ox}' ${TEST_RESULT}`"
+ @echo "LIST:O:Ox = `test '${LIST:O:Ox}' != '${LIST:O:Ox}' ${TEST_RESULT}`"
+ @echo "LISTX = `test '${LISTX}' != '${LISTX}' ${TEST_RESULT}`"
+ @echo "LISTSX = `test '${LISTSX}' = '${LISTSX}' ${TEST_RESULT}`"
+ @echo "BADMOD 1 = ${LIST:OX}"
+ @echo "BADMOD 2 = ${LIST:OxXX}"
diff --git a/usr.bin/make/unit-tests/modts.exp b/usr.bin/make/unit-tests/modts.exp
new file mode 100644
index 0000000..3389649
--- /dev/null
+++ b/usr.bin/make/unit-tests/modts.exp
@@ -0,0 +1,39 @@
+LIST="one two three four five six"
+LIST:ts,="one,two,three,four,five,six"
+LIST:ts/:tu="ONE/TWO/THREE/FOUR/FIVE/SIX"
+LIST:ts::tu="ONE:TWO:THREE:FOUR:FIVE:SIX"
+LIST:ts:tu="ONETWOTHREEFOURFIVESIX"
+LIST:tu:ts/="ONE/TWO/THREE/FOUR/FIVE/SIX"
+LIST:ts:="one:two:three:four:five:six"
+LIST:ts="onetwothreefourfivesix"
+LIST:ts:S/two/2/="one2threefourfivesix"
+LIST:S/two/2/:ts="one2threefourfivesix"
+LIST:ts/:S/two/2/="one/2/three/four/five/six"
+Pretend the '/' in '/n' etc. below are back-slashes.
+LIST:ts/n="one
+two
+three
+four
+five
+six"
+LIST:ts/t="one two three four five six"
+LIST:ts/012:tu="ONE
+TWO
+THREE
+FOUR
+FIVE
+SIX"
+LIST:ts/xa:tu="ONE
+TWO
+THREE
+FOUR
+FIVE
+SIX"
+make: Bad modifier `:tx' for LIST
+LIST:tx="}"
+make: Bad modifier `:ts\X' for LIST
+LIST:ts/x:tu="\X:tu}"
+FU_mod-ts="a/b/cool"
+FU_mod-ts:ts:T="cool" == cool?
+B.${AAA:ts}="Baaa" == Baaa?
+exit status 0
diff --git a/usr.bin/make/unit-tests/modts.mk b/usr.bin/make/unit-tests/modts.mk
new file mode 100644
index 0000000..254aa42
--- /dev/null
+++ b/usr.bin/make/unit-tests/modts.mk
@@ -0,0 +1,44 @@
+
+LIST= one two three
+LIST+= four five six
+
+FU_mod-ts = a / b / cool
+
+AAA= a a a
+B.aaa= Baaa
+
+all: mod-ts
+
+# Use print or printf iff they are builtin.
+# XXX note that this causes problems, when make decides
+# there is no need to use a shell, so avoid where possible.
+.if ${type print 2> /dev/null || echo:L:sh:Mbuiltin} != ""
+PRINT= print -r --
+.elif ${type printf 2> /dev/null || echo:L:sh:Mbuiltin} != ""
+PRINT= printf '%s\n'
+.else
+PRINT= echo
+.endif
+
+mod-ts:
+ @echo 'LIST="${LIST}"'
+ @echo 'LIST:ts,="${LIST:ts,}"'
+ @echo 'LIST:ts/:tu="${LIST:ts/:tu}"'
+ @echo 'LIST:ts::tu="${LIST:ts::tu}"'
+ @echo 'LIST:ts:tu="${LIST:ts:tu}"'
+ @echo 'LIST:tu:ts/="${LIST:tu:ts/}"'
+ @echo 'LIST:ts:="${LIST:ts:}"'
+ @echo 'LIST:ts="${LIST:ts}"'
+ @echo 'LIST:ts:S/two/2/="${LIST:ts:S/two/2/}"'
+ @echo 'LIST:S/two/2/:ts="${LIST:S/two/2/:ts}"'
+ @echo 'LIST:ts/:S/two/2/="${LIST:ts/:S/two/2/}"'
+ @echo "Pretend the '/' in '/n' etc. below are back-slashes."
+ @${PRINT} 'LIST:ts/n="${LIST:ts\n}"'
+ @${PRINT} 'LIST:ts/t="${LIST:ts\t}"'
+ @${PRINT} 'LIST:ts/012:tu="${LIST:ts\012:tu}"'
+ @${PRINT} 'LIST:ts/xa:tu="${LIST:ts\xa:tu}"'
+ @${PRINT} 'LIST:tx="${LIST:tx}"'
+ @${PRINT} 'LIST:ts/x:tu="${LIST:ts\X:tu}"'
+ @${PRINT} 'FU_$@="${FU_${@:ts}:ts}"'
+ @${PRINT} 'FU_$@:ts:T="${FU_${@:ts}:ts:T}" == cool?'
+ @${PRINT} 'B.$${AAA:ts}="${B.${AAA:ts}}" == Baaa?'
diff --git a/usr.bin/make/unit-tests/modword.exp b/usr.bin/make/unit-tests/modword.exp
new file mode 100644
index 0000000..258d7ea
--- /dev/null
+++ b/usr.bin/make/unit-tests/modword.exp
@@ -0,0 +1,122 @@
+make: Bad modifier `:[]' for LIST
+LIST:[]="" is an error
+LIST:[0]="one two three four five six"
+LIST:[0x0]="one two three four five six"
+LIST:[000]="one two three four five six"
+LIST:[*]="one two three four five six"
+LIST:[@]="one two three four five six"
+LIST:[0]:C/ /,/="one,two three four five six"
+LIST:[0]:C/ /,/g="one,two,three,four,five,six"
+LIST:[0]:C/ /,/1g="one,two,three,four,five,six"
+LIST:[*]:C/ /,/="one,two three four five six"
+LIST:[*]:C/ /,/g="one,two,three,four,five,six"
+LIST:[*]:C/ /,/1g="one,two,three,four,five,six"
+LIST:[@]:C/ /,/="one two three four five six"
+LIST:[@]:C/ /,/g="one two three four five six"
+LIST:[@]:C/ /,/1g="one two three four five six"
+LIST:[@]:[0]:C/ /,/="one,two three four five six"
+LIST:[0]:[@]:C/ /,/="one two three four five six"
+LIST:[@]:[*]:C/ /,/="one,two three four five six"
+LIST:[*]:[@]:C/ /,/="one two three four five six"
+EMPTY=""
+EMPTY:[#]="1" == 1 ?
+ESCAPEDSPACE="\ "
+ESCAPEDSPACE:[#]="1" == 1 ?
+REALLYSPACE=" "
+REALLYSPACE:[#]="1" == 1 ?
+LIST:[#]="6"
+LIST:[0]:[#]="1" == 1 ?
+LIST:[*]:[#]="1" == 1 ?
+LIST:[@]:[#]="6"
+LIST:[1]:[#]="1"
+LIST:[1..3]:[#]="3"
+EMPTY:[1]=""
+ESCAPEDSPACE="\ "
+ESCAPEDSPACE:[1]="\ "
+REALLYSPACE=" "
+REALLYSPACE:[1]="" == "" ?
+REALLYSPACE:[*]:[1]=" " == " " ?
+LIST:[1]="one"
+make: Bad modifier `:[1.]' for LIST
+LIST:[1.]="" is an error
+make: Bad modifier `:[1].' for LIST
+LIST:[1].="}" is an error
+LIST:[2]="two"
+LIST:[6]="six"
+LIST:[7]=""
+LIST:[999]=""
+make: Bad modifier `:[-]' for LIST
+LIST:[-]="" is an error
+make: Bad modifier `:[--]' for LIST
+LIST:[--]="" is an error
+LIST:[-1]="six"
+LIST:[-2]="five"
+LIST:[-6]="one"
+LIST:[-7]=""
+LIST:[-999]=""
+LONGLIST:[17]="17"
+LONGLIST:[0x11]="17"
+LONGLIST:[021]="17"
+LIST:[0]:[1]="one two three four five six"
+LIST:[*]:[1]="one two three four five six"
+LIST:[@]:[1]="one"
+LIST:[0]:[2]=""
+LIST:[*]:[2]=""
+LIST:[@]:[2]="two"
+LIST:[*]:C/ /,/:[2]=""
+LIST:[*]:C/ /,/:[*]:[2]=""
+LIST:[*]:C/ /,/:[@]:[2]="three"
+make: Bad modifier `:[1.]' for LIST
+LIST:[1.]="" is an error
+make: Bad modifier `:[1..]' for LIST
+LIST:[1..]="" is an error
+LIST:[1..1]="one"
+make: Bad modifier `:[1..1.]' for LIST
+LIST:[1..1.]="" is an error
+LIST:[1..2]="one two"
+LIST:[2..1]="two one"
+LIST:[3..-2]="three four five"
+LIST:[-4..4]="three four"
+make: Bad modifier `:[0..1]' for LIST
+LIST:[0..1]="" is an error
+make: Bad modifier `:[-1..0]' for LIST
+LIST:[-1..0]="" is an error
+LIST:[-1..1]="six five four three two one"
+LIST:[0..0]="one two three four five six"
+LIST:[3..99]="three four five six"
+LIST:[-3..-99]="four three two one"
+LIST:[-99..-3]="one two three four"
+HASH="#" == "#" ?
+LIST:[${HASH}]="6"
+LIST:[${ZERO}]="one two three four five six"
+LIST:[${ZERO}x${ONE}]="one"
+LIST:[${ONE}]="one"
+LIST:[${MINUSONE}]="six"
+LIST:[${STAR}]="one two three four five six"
+LIST:[${AT}]="one two three four five six"
+make: Bad modifier `:[${EMPTY' for LIST
+LIST:[${EMPTY}]="" is an error
+LIST:[${LONGLIST:[21]:S/2//}]="one"
+LIST:[${LIST:[#]}]="six"
+LIST:[${LIST:[${HASH}]}]="six"
+LIST:S/ /,/="one two three four five six"
+LIST:S/ /,/W="one,two three four five six"
+LIST:S/ /,/gW="one,two,three,four,five,six"
+EMPTY:S/^/,/=","
+EMPTY:S/^/,/W=","
+LIST:C/ /,/="one two three four five six"
+LIST:C/ /,/W="one,two three four five six"
+LIST:C/ /,/gW="one,two,three,four,five,six"
+EMPTY:C/^/,/=","
+EMPTY:C/^/,/W=","
+LIST:tW="one two three four five six"
+LIST:tw="one two three four five six"
+LIST:tW:C/ /,/="one,two three four five six"
+LIST:tW:C/ /,/g="one,two,three,four,five,six"
+LIST:tW:C/ /,/1g="one,two,three,four,five,six"
+LIST:tw:C/ /,/="one two three four five six"
+LIST:tw:C/ /,/g="one two three four five six"
+LIST:tw:C/ /,/1g="one two three four five six"
+LIST:tw:tW:C/ /,/="one,two three four five six"
+LIST:tW:tw:C/ /,/="one two three four five six"
+exit status 0
diff --git a/usr.bin/make/unit-tests/modword.mk b/usr.bin/make/unit-tests/modword.mk
new file mode 100644
index 0000000..00a56de
--- /dev/null
+++ b/usr.bin/make/unit-tests/modword.mk
@@ -0,0 +1,151 @@
+# $Id: modword.mk,v 1.1 2014/08/21 13:44:51 apb Exp $
+#
+# Test behaviour of new :[] modifier
+
+all: mod-squarebrackets mod-S-W mod-C-W mod-tW-tw
+
+LIST= one two three
+LIST+= four five six
+LONGLIST= 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
+
+EMPTY= # the space should be ignored
+ESCAPEDSPACE=\ # escaped space before the '#'
+REALLYSPACE:=${EMPTY:C/^/ /W}
+HASH= \#
+AT= @
+STAR= *
+ZERO= 0
+ONE= 1
+MINUSONE= -1
+
+mod-squarebrackets: mod-squarebrackets-0-star-at \
+ mod-squarebrackets-hash \
+ mod-squarebrackets-n \
+ mod-squarebrackets-start-end \
+ mod-squarebrackets-nested
+
+mod-squarebrackets-0-star-at:
+ @echo 'LIST:[]="${LIST:[]}" is an error'
+ @echo 'LIST:[0]="${LIST:[0]}"'
+ @echo 'LIST:[0x0]="${LIST:[0x0]}"'
+ @echo 'LIST:[000]="${LIST:[000]}"'
+ @echo 'LIST:[*]="${LIST:[*]}"'
+ @echo 'LIST:[@]="${LIST:[@]}"'
+ @echo 'LIST:[0]:C/ /,/="${LIST:[0]:C/ /,/}"'
+ @echo 'LIST:[0]:C/ /,/g="${LIST:[0]:C/ /,/g}"'
+ @echo 'LIST:[0]:C/ /,/1g="${LIST:[0]:C/ /,/1g}"'
+ @echo 'LIST:[*]:C/ /,/="${LIST:[*]:C/ /,/}"'
+ @echo 'LIST:[*]:C/ /,/g="${LIST:[*]:C/ /,/g}"'
+ @echo 'LIST:[*]:C/ /,/1g="${LIST:[*]:C/ /,/1g}"'
+ @echo 'LIST:[@]:C/ /,/="${LIST:[@]:C/ /,/}"'
+ @echo 'LIST:[@]:C/ /,/g="${LIST:[@]:C/ /,/g}"'
+ @echo 'LIST:[@]:C/ /,/1g="${LIST:[@]:C/ /,/1g}"'
+ @echo 'LIST:[@]:[0]:C/ /,/="${LIST:[@]:[0]:C/ /,/}"'
+ @echo 'LIST:[0]:[@]:C/ /,/="${LIST:[0]:[@]:C/ /,/}"'
+ @echo 'LIST:[@]:[*]:C/ /,/="${LIST:[@]:[*]:C/ /,/}"'
+ @echo 'LIST:[*]:[@]:C/ /,/="${LIST:[*]:[@]:C/ /,/}"'
+
+mod-squarebrackets-hash:
+ @echo 'EMPTY="${EMPTY}"'
+ @echo 'EMPTY:[#]="${EMPTY:[#]}" == 1 ?'
+ @echo 'ESCAPEDSPACE="${ESCAPEDSPACE}"'
+ @echo 'ESCAPEDSPACE:[#]="${ESCAPEDSPACE:[#]}" == 1 ?'
+ @echo 'REALLYSPACE="${REALLYSPACE}"'
+ @echo 'REALLYSPACE:[#]="${REALLYSPACE:[#]}" == 1 ?'
+ @echo 'LIST:[#]="${LIST:[#]}"'
+ @echo 'LIST:[0]:[#]="${LIST:[0]:[#]}" == 1 ?'
+ @echo 'LIST:[*]:[#]="${LIST:[*]:[#]}" == 1 ?'
+ @echo 'LIST:[@]:[#]="${LIST:[@]:[#]}"'
+ @echo 'LIST:[1]:[#]="${LIST:[1]:[#]}"'
+ @echo 'LIST:[1..3]:[#]="${LIST:[1..3]:[#]}"'
+
+mod-squarebrackets-n:
+ @echo 'EMPTY:[1]="${EMPTY:[1]}"'
+ @echo 'ESCAPEDSPACE="${ESCAPEDSPACE}"'
+ @echo 'ESCAPEDSPACE:[1]="${ESCAPEDSPACE:[1]}"'
+ @echo 'REALLYSPACE="${REALLYSPACE}"'
+ @echo 'REALLYSPACE:[1]="${REALLYSPACE:[1]}" == "" ?'
+ @echo 'REALLYSPACE:[*]:[1]="${REALLYSPACE:[*]:[1]}" == " " ?'
+ @echo 'LIST:[1]="${LIST:[1]}"'
+ @echo 'LIST:[1.]="${LIST:[1.]}" is an error'
+ @echo 'LIST:[1].="${LIST:[1].}" is an error'
+ @echo 'LIST:[2]="${LIST:[2]}"'
+ @echo 'LIST:[6]="${LIST:[6]}"'
+ @echo 'LIST:[7]="${LIST:[7]}"'
+ @echo 'LIST:[999]="${LIST:[999]}"'
+ @echo 'LIST:[-]="${LIST:[-]}" is an error'
+ @echo 'LIST:[--]="${LIST:[--]}" is an error'
+ @echo 'LIST:[-1]="${LIST:[-1]}"'
+ @echo 'LIST:[-2]="${LIST:[-2]}"'
+ @echo 'LIST:[-6]="${LIST:[-6]}"'
+ @echo 'LIST:[-7]="${LIST:[-7]}"'
+ @echo 'LIST:[-999]="${LIST:[-999]}"'
+ @echo 'LONGLIST:[17]="${LONGLIST:[17]}"'
+ @echo 'LONGLIST:[0x11]="${LONGLIST:[0x11]}"'
+ @echo 'LONGLIST:[021]="${LONGLIST:[021]}"'
+ @echo 'LIST:[0]:[1]="${LIST:[0]:[1]}"'
+ @echo 'LIST:[*]:[1]="${LIST:[*]:[1]}"'
+ @echo 'LIST:[@]:[1]="${LIST:[@]:[1]}"'
+ @echo 'LIST:[0]:[2]="${LIST:[0]:[2]}"'
+ @echo 'LIST:[*]:[2]="${LIST:[*]:[2]}"'
+ @echo 'LIST:[@]:[2]="${LIST:[@]:[2]}"'
+ @echo 'LIST:[*]:C/ /,/:[2]="${LIST:[*]:C/ /,/:[2]}"'
+ @echo 'LIST:[*]:C/ /,/:[*]:[2]="${LIST:[*]:C/ /,/:[*]:[2]}"'
+ @echo 'LIST:[*]:C/ /,/:[@]:[2]="${LIST:[*]:C/ /,/:[@]:[2]}"'
+
+mod-squarebrackets-start-end:
+ @echo 'LIST:[1.]="${LIST:[1.]}" is an error'
+ @echo 'LIST:[1..]="${LIST:[1..]}" is an error'
+ @echo 'LIST:[1..1]="${LIST:[1..1]}"'
+ @echo 'LIST:[1..1.]="${LIST:[1..1.]}" is an error'
+ @echo 'LIST:[1..2]="${LIST:[1..2]}"'
+ @echo 'LIST:[2..1]="${LIST:[2..1]}"'
+ @echo 'LIST:[3..-2]="${LIST:[3..-2]}"'
+ @echo 'LIST:[-4..4]="${LIST:[-4..4]}"'
+ @echo 'LIST:[0..1]="${LIST:[0..1]}" is an error'
+ @echo 'LIST:[-1..0]="${LIST:[-1..0]}" is an error'
+ @echo 'LIST:[-1..1]="${LIST:[-1..1]}"'
+ @echo 'LIST:[0..0]="${LIST:[0..0]}"'
+ @echo 'LIST:[3..99]="${LIST:[3..99]}"'
+ @echo 'LIST:[-3..-99]="${LIST:[-3..-99]}"'
+ @echo 'LIST:[-99..-3]="${LIST:[-99..-3]}"'
+
+mod-squarebrackets-nested:
+ @echo 'HASH="${HASH}" == "#" ?'
+ @echo 'LIST:[$${HASH}]="${LIST:[${HASH}]}"'
+ @echo 'LIST:[$${ZERO}]="${LIST:[${ZERO}]}"'
+ @echo 'LIST:[$${ZERO}x$${ONE}]="${LIST:[${ZERO}x${ONE}]}"'
+ @echo 'LIST:[$${ONE}]="${LIST:[${ONE}]}"'
+ @echo 'LIST:[$${MINUSONE}]="${LIST:[${MINUSONE}]}"'
+ @echo 'LIST:[$${STAR}]="${LIST:[${STAR}]}"'
+ @echo 'LIST:[$${AT}]="${LIST:[${AT}]}"'
+ @echo 'LIST:[$${EMPTY}]="${LIST:[${EMPTY}]}" is an error'
+ @echo 'LIST:[$${LONGLIST:[21]:S/2//}]="${LIST:[${LONGLIST:[21]:S/2//}]}"'
+ @echo 'LIST:[$${LIST:[#]}]="${LIST:[${LIST:[#]}]}"'
+ @echo 'LIST:[$${LIST:[$${HASH}]}]="${LIST:[${LIST:[${HASH}]}]}"'
+
+mod-C-W:
+ @echo 'LIST:C/ /,/="${LIST:C/ /,/}"'
+ @echo 'LIST:C/ /,/W="${LIST:C/ /,/W}"'
+ @echo 'LIST:C/ /,/gW="${LIST:C/ /,/gW}"'
+ @echo 'EMPTY:C/^/,/="${EMPTY:C/^/,/}"'
+ @echo 'EMPTY:C/^/,/W="${EMPTY:C/^/,/W}"'
+
+mod-S-W:
+ @echo 'LIST:S/ /,/="${LIST:S/ /,/}"'
+ @echo 'LIST:S/ /,/W="${LIST:S/ /,/W}"'
+ @echo 'LIST:S/ /,/gW="${LIST:S/ /,/gW}"'
+ @echo 'EMPTY:S/^/,/="${EMPTY:S/^/,/}"'
+ @echo 'EMPTY:S/^/,/W="${EMPTY:S/^/,/W}"'
+
+mod-tW-tw:
+ @echo 'LIST:tW="${LIST:tW}"'
+ @echo 'LIST:tw="${LIST:tw}"'
+ @echo 'LIST:tW:C/ /,/="${LIST:tW:C/ /,/}"'
+ @echo 'LIST:tW:C/ /,/g="${LIST:tW:C/ /,/g}"'
+ @echo 'LIST:tW:C/ /,/1g="${LIST:tW:C/ /,/1g}"'
+ @echo 'LIST:tw:C/ /,/="${LIST:tw:C/ /,/}"'
+ @echo 'LIST:tw:C/ /,/g="${LIST:tw:C/ /,/g}"'
+ @echo 'LIST:tw:C/ /,/1g="${LIST:tw:C/ /,/1g}"'
+ @echo 'LIST:tw:tW:C/ /,/="${LIST:tw:tW:C/ /,/}"'
+ @echo 'LIST:tW:tw:C/ /,/="${LIST:tW:tw:C/ /,/}"'
diff --git a/usr.bin/make/unit-tests/order.exp b/usr.bin/make/unit-tests/order.exp
new file mode 100644
index 0000000..d876914
--- /dev/null
+++ b/usr.bin/make/unit-tests/order.exp
@@ -0,0 +1,4 @@
+Making the.c
+Making the.h
+Making the.o from the.h the.c
+exit status 0
diff --git a/usr.bin/make/unit-tests/order.mk b/usr.bin/make/unit-tests/order.mk
new file mode 100644
index 0000000..f90b627
--- /dev/null
+++ b/usr.bin/make/unit-tests/order.mk
@@ -0,0 +1,20 @@
+# $NetBSD: order.mk,v 1.1 2014/08/21 13:44:51 apb Exp $
+
+# Test that .ORDER is handled correctly.
+# The explicit dependency the.o: the.h will make us examine the.h
+# the .ORDER will prevent us building it immediately,
+# we should then examine the.c rather than stop.
+
+all: the.o
+
+.ORDER: the.c the.h
+
+the.c the.h:
+ @echo Making $@
+
+.SUFFIXES: .o .c
+
+.c.o:
+ @echo Making $@ from $?
+
+the.o: the.h
diff --git a/usr.bin/make/unit-tests/phony-end.exp b/usr.bin/make/unit-tests/phony-end.exp
new file mode 100644
index 0000000..c3c517c
--- /dev/null
+++ b/usr.bin/make/unit-tests/phony-end.exp
@@ -0,0 +1,6 @@
+.TARGET="phony" .PREFIX="phony" .IMPSRC=""
+.TARGET="all" .PREFIX="all" .IMPSRC="phony"
+.TARGET="ok" .PREFIX="ok" .IMPSRC=""
+.TARGET="also.ok" .PREFIX="also.ok" .IMPSRC=""
+.TARGET="bug" .PREFIX="bug" .IMPSRC=""
+exit status 0
diff --git a/usr.bin/make/unit-tests/phony-end.mk b/usr.bin/make/unit-tests/phony-end.mk
new file mode 100644
index 0000000..92cc0e6
--- /dev/null
+++ b/usr.bin/make/unit-tests/phony-end.mk
@@ -0,0 +1,9 @@
+# $Id: phony-end.mk,v 1.1 2014/08/21 13:44:51 apb Exp $
+
+all ok also.ok bug phony:
+ @echo '${.TARGET .PREFIX .IMPSRC:L:@v@$v="${$v}"@}'
+
+.END: ok also.ok bug
+
+phony bug: .PHONY
+all: phony
diff --git a/usr.bin/make/unit-tests/posix.exp b/usr.bin/make/unit-tests/posix.exp
new file mode 100644
index 0000000..7e74cab
--- /dev/null
+++ b/usr.bin/make/unit-tests/posix.exp
@@ -0,0 +1,23 @@
+Posix says we should execute the command as if run by system(3)
+Expect 'Hello,' and 'World!'
+Hello,
+World!
+a command
+a command prefixed by '+' executes even with -n
+another command
+make -n
+echo a command
+echo "a command prefixed by '+' executes even with -n"
+a command prefixed by '+' executes even with -n
+echo another command
+make -n -j1
+{ echo a command
+} || exit $?
+echo "a command prefixed by '+' executes even with -n"
+a command prefixed by '+' executes even with -n
+{ echo another command
+} || exit $?
+Now we expect an error...
+*** Error code 1 (continuing)
+`all' not remade because of errors.
+exit status 0
diff --git a/usr.bin/make/unit-tests/posix.mk b/usr.bin/make/unit-tests/posix.mk
new file mode 100644
index 0000000..a73e2e5
--- /dev/null
+++ b/usr.bin/make/unit-tests/posix.mk
@@ -0,0 +1,24 @@
+# $Id: posix.mk,v 1.1 2014/08/21 13:44:51 apb Exp $
+
+all: x plus subs err
+
+x:
+ @echo "Posix says we should execute the command as if run by system(3)"
+ @echo "Expect 'Hello,' and 'World!'"
+ @echo Hello,; false; echo "World!"
+
+plus:
+ @echo a command
+ +@echo "a command prefixed by '+' executes even with -n"
+ @echo another command
+
+subs:
+ @echo make -n
+ @${.MAKE} -f ${MAKEFILE} -n plus
+ @echo make -n -j1
+ @${.MAKE} -f ${MAKEFILE} -n -j1 plus
+
+err:
+ @(echo Now we expect an error...; exit 1)
+ @echo "Oops! you shouldn't see this!"
+
diff --git a/usr.bin/make/unit-tests/posix1.exp b/usr.bin/make/unit-tests/posix1.exp
new file mode 100644
index 0000000..fa1f15d
--- /dev/null
+++ b/usr.bin/make/unit-tests/posix1.exp
@@ -0,0 +1,186 @@
+${VAR} = "foo bar baz"
+a
+b
+c
+foo baR baz, bar baz, foo bar baz, fooadd baradd bazadd
+mkdir -p 'dir'
+touch 'dir/obj_1.h'
+mkdir -p 'dir'
+printf '#include "obj_1.h"\nconst char* obj_1 = "dir/obj_1.c";\n' \
+ >'dir/obj_1.c'
+Local variables
+ ${@}="dir/obj_1.o" ${<}="dir/obj_1.c"
+ ${*}="dir/obj_1" ${?}="dir/obj_1.h dir/obj_1.c"
+ ${%}=""
+
+Directory and filename parts of local variables
+ ${@D}="dir" ${@F}="obj_1.o"
+ ${<D}="dir" ${<F}="obj_1.c"
+ ${*D}="dir" ${*F}="obj_1"
+ ${?D}="dir dir" ${?F}="obj_1.h obj_1.c"
+ ${%D}="" ${%F}=""
+
+Local variable substitutions
+ ${@:.o=}="dir/obj_1" ${<:.c=.C}="dir/obj_1.C"
+ ${*:=.h}="dir/obj_1.h" ${?:.h=.H}="dir/obj_1.H dir/obj_1.c"
+ ${%:=}=""
+
+Target with suffix transformations
+ ${@D:=append}="dirappend"
+ ${@F:.o=.O}="obj_1.O"
+
+ Implied source with suffix transformations
+ ${<D:r=rr}="dirr"
+ ${<F:.c=.C}="obj_1.C"
+
+ Suffixless target with suffix transformations
+ ${*D:.=dot}="dir"
+ ${*F:.a=}="obj_1"
+
+ Out-of-date dependencies with suffix transformations
+ ${?D:ir=}="d d"
+ ${?F:.h=.H}="obj_1.H obj_1.c"
+
+ Member with suffix transformations
+ ${%D:.=}=""
+ ${%F:${VAR2}=${VAR}}=""
+
+cc -c -o 'dir/obj_1.o' 'dir/obj_1.c'
+mkdir -p '.'
+touch 'dummy'
+Local variables
+ ${@}="lib.a" ${<}="dir/obj_1.o"
+ ${*}="obj1" ${?}="dir/obj_1.o dummy"
+ ${%}="obj1.o"
+
+Directory and filename parts of local variables
+ ${@D}="." ${@F}="lib.a"
+ ${<D}="dir" ${<F}="obj_1.o"
+ ${*D}="." ${*F}="obj1"
+ ${?D}="dir ." ${?F}="obj_1.o dummy"
+ ${%D}="." ${%F}="obj1.o"
+
+Local variable substitutions
+ ${@:.o=}="lib.a" ${<:.c=.C}="dir/obj_1.o"
+ ${*:=.h}="obj1.h" ${?:.h=.H}="dir/obj_1.o dummy"
+ ${%:=}="obj1.o"
+
+Target with suffix transformations
+ ${@D:=append}=".append"
+ ${@F:.o=.O}="lib.a"
+
+ Implied source with suffix transformations
+ ${<D:r=rr}="dirr"
+ ${<F:.c=.C}="obj_1.o"
+
+ Suffixless target with suffix transformations
+ ${*D:.=dot}="dot"
+ ${*F:.a=}="obj1"
+
+ Out-of-date dependencies with suffix transformations
+ ${?D:ir=}="d ."
+ ${?F:.h=.H}="obj_1.o dummy"
+
+ Member with suffix transformations
+ ${%D:.=}=""
+ ${%F:${VAR2}=${VAR}}="obj1foo bar baz"
+
+cp 'dir/obj_1.o' 'obj1.o'
+ar -rcv 'lib.a' 'obj1.o'
+a - obj1.o
+rm -f 'obj1.o'
+mkdir -p '.'
+printf '#include "obj_2.h"\nconst char* obj_2 = "obj_2.c";\n' \
+ >'obj_2.c'
+mkdir -p '.'
+touch 'obj_2.h'
+Local variables
+ ${@}="obj2.o" ${<}="obj_2.c"
+ ${*}="obj2" ${?}="obj_2.c obj_2.h dir/obj_1.h"
+ ${%}=""
+
+Directory and filename parts of local variables
+ ${@D}="." ${@F}="obj2.o"
+ ${<D}="." ${<F}="obj_2.c"
+ ${*D}="." ${*F}="obj2"
+ ${?D}=". . dir" ${?F}="obj_2.c obj_2.h obj_1.h"
+ ${%D}="" ${%F}=""
+
+Local variable substitutions
+ ${@:.o=}="obj2" ${<:.c=.C}="obj_2.C"
+ ${*:=.h}="obj2.h" ${?:.h=.H}="obj_2.c obj_2.H dir/obj_1.H"
+ ${%:=}=""
+
+Target with suffix transformations
+ ${@D:=append}=".append"
+ ${@F:.o=.O}="obj2.O"
+
+ Implied source with suffix transformations
+ ${<D:r=rr}="."
+ ${<F:.c=.C}="obj_2.C"
+
+ Suffixless target with suffix transformations
+ ${*D:.=dot}="dot"
+ ${*F:.a=}="obj2"
+
+ Out-of-date dependencies with suffix transformations
+ ${?D:ir=}=". . d"
+ ${?F:.h=.H}="obj_2.c obj_2.H obj_1.H"
+
+ Member with suffix transformations
+ ${%D:.=}=""
+ ${%F:${VAR2}=${VAR}}=""
+
+cc -c -o 'obj2.o' 'obj_2.c'
+ar -rcv 'lib.a' 'obj2.o'
+a - obj2.o
+mkdir -p '.'
+touch 'obj3.h'
+mkdir -p 'dir'
+touch 'dir/dummy'
+mkdir -p '.'
+printf '#include "obj3.h"\nconst char* obj3 = "obj3.c";\n' \
+ >'obj3.c'
+Local variables
+ ${@}="lib.a" ${<}="obj3.c"
+ ${*}="obj3" ${?}="obj3.h dir/dummy obj3.c"
+ ${%}="obj3.o"
+
+Directory and filename parts of local variables
+ ${@D}="." ${@F}="lib.a"
+ ${<D}="." ${<F}="obj3.c"
+ ${*D}="." ${*F}="obj3"
+ ${?D}=". dir ." ${?F}="obj3.h dummy obj3.c"
+ ${%D}="." ${%F}="obj3.o"
+
+Local variable substitutions
+ ${@:.o=}="lib.a" ${<:.c=.C}="obj3.C"
+ ${*:=.h}="obj3.h" ${?:.h=.H}="obj3.H dir/dummy obj3.c"
+ ${%:=}="obj3.o"
+
+Target with suffix transformations
+ ${@D:=append}=".append"
+ ${@F:.o=.O}="lib.a"
+
+ Implied source with suffix transformations
+ ${<D:r=rr}="."
+ ${<F:.c=.C}="obj3.C"
+
+ Suffixless target with suffix transformations
+ ${*D:.=dot}="dot"
+ ${*F:.a=}="obj3"
+
+ Out-of-date dependencies with suffix transformations
+ ${?D:ir=}=". d ."
+ ${?F:.h=.H}="obj3.H dummy obj3.c"
+
+ Member with suffix transformations
+ ${%D:.=}=""
+ ${%F:${VAR2}=${VAR}}="obj3foo bar baz"
+
+cc -c -o 'obj3.o' 'obj3.c'
+ar -rcv 'lib.a' 'obj3.o'
+a - obj3.o
+rm -f 'obj3.o'
+ar -s 'lib.a'
+exit status 0
diff --git a/usr.bin/make/unit-tests/posix1.mk b/usr.bin/make/unit-tests/posix1.mk
new file mode 100644
index 0000000..1bf6a56
--- /dev/null
+++ b/usr.bin/make/unit-tests/posix1.mk
@@ -0,0 +1,184 @@
+# $NetBSD: posix1.mk,v 1.3 2014/08/30 22:21:08 sjg Exp $
+
+# Keep the default suffixes from interfering, just in case.
+.SUFFIXES:
+
+all: line-continuations suffix-substitution localvars
+
+# we need to clean for repeatable results
+.BEGIN: clean
+clean:
+ @rm -f lib.a dir/* dummy obj*
+
+#
+# Line continuations
+#
+
+# Escaped newlines and leading whitespace from the next line are replaced
+# with single space, except in commands, where the escape and the newline
+# are retained, but a single leading tab (if any) from the next line is
+# removed. (PR 49085)
+# Expect:
+# ${VAR} = "foo bar baz"
+# a
+# b
+# c
+VAR = foo\
+\
+ bar\
+ baz
+
+line-continuations:
+ @echo '$${VAR} = "${VAR}"'
+ @echo 'aXbXc' | sed -e 's/X/\
+ /g'
+
+
+#
+# Suffix substitution
+#
+
+# The only variable modifier accepted by POSIX.
+# ${VAR:s1=s2}: replace s1, if found, with s2 at end of each word in
+# ${VAR}. s1 and s2 may contain macro expansions.
+# Expect: foo baR baz, bar baz, foo bar baz, fooadd baradd bazadd
+suffix-substitution:
+ @echo '${VAR:r=R}, ${VAR:foo=}, ${VAR:not_there=wrong}, ${VAR:=add}'
+
+
+#
+# Local variables: regular forms, D/F forms and suffix substitution.
+#
+
+# In the past substitutions did not work with the D/F forms and those
+# forms were not available for $?. (PR 49085)
+
+ARFLAGS = -rcv
+
+localvars: lib.a
+
+# $@ = target or archive name $< = implied source
+# $* = target without suffix $? = sources newer than target
+# $% = archive member name
+LOCALS = \
+ "Local variables\n\
+ \$${@}=\"${@}\" \$${<}=\"${<}\"\n\
+ \$${*}=\"${*}\" \$${?}=\"${?}\"\n\
+ \$${%%}=\"${%}\"\n\n"
+
+# $XD = directory part of X $XF = file part of X
+# X is one of the local variables.
+LOCAL_ALTERNATIVES = \
+ "Directory and filename parts of local variables\n\
+ \$${@D}=\"${@D}\" \$${@F}=\"${@F}\"\n\
+ \$${<D}=\"${<D}\" \$${<F}=\"${<F}\"\n\
+ \$${*D}=\"${*D}\" \$${*F}=\"${*F}\"\n\
+ \$${?D}=\"${?D}\" \$${?F}=\"${?F}\"\n\
+ \$${%%D}=\"${%D}\" \$${%%F}=\"${%F}\"\n\n"
+
+# Do all kinds of meaningless substitutions on local variables to see
+# if they work. Add, remove and replace things.
+VAR2 = .o
+VAR3 = foo
+LOCAL_SUBSTITUTIONS = \
+ "Local variable substitutions\n\
+ \$${@:.o=}=\"${@:.o=}\" \$${<:.c=.C}=\"${<:.c=.C}\"\n\
+ \$${*:=.h}=\"${*:=.h}\" \$${?:.h=.H}=\"${?:.h=.H}\"\n\
+ \$${%%:=}=\"${%:=}\"\n\n"
+
+LOCAL_ALTERNATIVE_SUBSTITUTIONS = \
+ "Target with suffix transformations\n\
+ \$${@D:=append}=\"${@D:=append}\"\n\
+ \$${@F:.o=.O}=\"${@F:.o=.O}\"\n\
+ \n\
+ Implied source with suffix transformations\n\
+ \$${<D:r=rr}=\"${<D:r=rr}\"\n\
+ \$${<F:.c=.C}=\"${<F:.c=.C}\"\n\
+ \n\
+ Suffixless target with suffix transformations\n\
+ \$${*D:.=dot}=\"${*D:.=dot}\"\n\
+ \$${*F:.a=}=\"${*F:.a=}\"\n\
+ \n\
+ Out-of-date dependencies with suffix transformations\n\
+ \$${?D:ir=}=\"${?D:ir=}\"\n\
+ \$${?F:.h=.H}=\"${?F:.h=.H}\"\n\
+ \n\
+ Member with suffix transformations\n\
+ \$${%%D:.=}=\"${%D:.=}\"\n\
+ \$${%%F:\$${VAR2}=\$${VAR}}=\"${%F:${VAR2}=${VAR}}\"\n\n"
+
+.SUFFIXES: .c .o .a
+
+# The system makefiles make the .c.a rule .PRECIOUS with a special source,
+# but such a thing is not POSIX compatible. It's also somewhat useless
+# in a test makefile.
+.c.a:
+ @printf ${LOCALS}
+ @printf ${LOCAL_ALTERNATIVES}
+ @printf ${LOCAL_SUBSTITUTIONS}
+ @printf ${LOCAL_ALTERNATIVE_SUBSTITUTIONS}
+ cc -c -o '${%}' '${<}'
+ ar ${ARFLAGS} '${@}' '${%}'
+ rm -f '${%}'
+
+.c.o:
+ @printf ${LOCALS}
+ @printf ${LOCAL_ALTERNATIVES}
+ @printf ${LOCAL_SUBSTITUTIONS}
+ @printf ${LOCAL_ALTERNATIVE_SUBSTITUTIONS}
+ cc -c -o '${@}' '${<}'
+
+# Some of these rules are padded with useless extra dependencies just so
+# that ${?} has more than one file.
+
+lib.a: lib.a(obj1.o) lib.a(obj2.o) lib.a(obj3.o)
+ ar -s '${@}'
+
+# Explicit rule where the dependency is an inferred file. The dependency
+# object's name differs from the member's because there was a bug which
+# forced a dependency on member even when no such dependency was specified
+# (PR 49086).
+lib.a(obj1.o): dir/obj_1.o dummy
+ @printf ${LOCALS}
+ @printf ${LOCAL_ALTERNATIVES}
+ @printf ${LOCAL_SUBSTITUTIONS}
+ @printf ${LOCAL_ALTERNATIVE_SUBSTITUTIONS}
+ cp 'dir/obj_1.o' '$%'
+ ar ${ARFLAGS} '${@}' '$%'
+ rm -f '$%'
+
+# Excplicit rule where the dependency also has an explicit rule.
+lib.a(obj2.o): obj2.o
+ ar ${ARFLAGS} '${@}' '${%}'
+
+# Use .c.a inference with an extra dependency.
+lib.a(obj3.o): obj3.h dir/dummy
+
+# Use .c.o inference with an extra dependency.
+dir/obj_1.o: dir/obj_1.h
+
+# According to POSIX, $* is only required for inference rules and $<'s
+# value is unspecified outside of inference rules. Strictly speaking
+# we shouldn't be expanding them here but who cares. At least we get
+# to check that the program does nothing stupid (like crash) with them.
+# The C file is named differently from the object file because there
+# was a bug which forced dependencies based on inference rules on all
+# applicable targets (PR 49086).
+obj2.o: obj_2.c obj_2.h dir/obj_1.h
+ @printf ${LOCALS}
+ @printf ${LOCAL_ALTERNATIVES}
+ @printf ${LOCAL_SUBSTITUTIONS}
+ @printf ${LOCAL_ALTERNATIVE_SUBSTITUTIONS}
+ cc -c -o '${@}' 'obj_2.c'
+
+# Hey, this is make, we can make our own test data setup! obj1.c
+# and obj2.c are not used, so they should not get created. They're here
+# as a bait for a regression into the forced dependencies discussed earlier.
+obj1.c dir/obj_1.c obj2.c obj_2.c obj3.c:
+ mkdir -p '${@D}'
+ printf '#include "${@F:.c=.h}"\nconst char* ${@F:.c=} = "${@}";\n' \
+ >'${@}'
+
+dir/obj_1.h obj_2.h obj3.h dummy dir/dummy:
+ mkdir -p '${@D}'
+ touch '${@}'
diff --git a/usr.bin/make/unit-tests/qequals.exp b/usr.bin/make/unit-tests/qequals.exp
new file mode 100644
index 0000000..6b2f4dc
--- /dev/null
+++ b/usr.bin/make/unit-tests/qequals.exp
@@ -0,0 +1,2 @@
+V.i386 ?= OK
+exit status 0
diff --git a/usr.bin/make/unit-tests/qequals.mk b/usr.bin/make/unit-tests/qequals.mk
new file mode 100644
index 0000000..db6d9c3
--- /dev/null
+++ b/usr.bin/make/unit-tests/qequals.mk
@@ -0,0 +1,8 @@
+# $Id: qequals.mk,v 1.1 2014/08/21 13:44:51 apb Exp $
+
+M= i386
+V.i386= OK
+V.$M ?= bug
+
+all:
+ @echo 'V.$M ?= ${V.$M}'
diff --git a/usr.bin/make/unit-tests/suffixes.exp b/usr.bin/make/unit-tests/suffixes.exp
new file mode 100644
index 0000000..2a46e1c
--- /dev/null
+++ b/usr.bin/make/unit-tests/suffixes.exp
@@ -0,0 +1,35 @@
+make: don't know how to make issue3 (continuing)
+There should be no text after the colon:
+touch .a
+There should be no text after the colon:
+touch .a.b
+There should be no text after the colon:
+touch .b.a
+touch issue5a.c
+first set
+cp issue5a.c issue5a.d
+touch issue5b.d
+first set
+cp issue5b.d issue5b.c
+touch issue5c.d
+first set
+cp issue5c.d issue5c
+touch issue5d.d
+first set
+cp issue5d.d issue5d.e
+touch issue5e.e
+first set
+cp issue5e.e issue5e.d
+make: don't know how to make issue6.f (continuing)
+touch issue10.d
+first set
+cp issue10.d issue10.e
+touch issue11.h
+touch issue11.first
+.ALLSRC: issue11.h issue11.first
+cp issue11.h issue11.i
+touch issue11.second
+.ALLSRC: issue11.i issue11.second
+cp issue11.i issue11.j
+`all' not remade because of errors.
+exit status 0
diff --git a/usr.bin/make/unit-tests/suffixes.mk b/usr.bin/make/unit-tests/suffixes.mk
new file mode 100644
index 0000000..113484a
--- /dev/null
+++ b/usr.bin/make/unit-tests/suffixes.mk
@@ -0,0 +1,89 @@
+# $NetBSD: suffixes.mk,v 1.3 2014/08/30 22:21:08 sjg Exp $
+
+# Issues from PR 49086
+
+# Issue 3: single suffix rules remain active after .SUFFIXES is cleared
+#
+# There's a rule for issue3.a, but .a is no longer a known suffix when
+# targets are being made, so issue3 should not get made.
+all: issue3
+
+# Issue 4: suffix rules do not become regular rules when .SUFFIXES is cleared
+#
+# When the rules were encountered, .a and .b were known suffices, but later
+# on they were forgotten. These should get created as regular targets.
+all: .a .a.b .b.a
+
+# Issue 5: adding more suffixes does not make existing rules into suffix rules
+#
+# When the targets .c.d, .d.c, .d, .d.e, and .e.d were encountered, only .a,
+# .b and .c were known suffixes, so all of them were regular rules. Later
+# rest of the suffixes were made known, so they should all be suffix
+# transformation rules.
+all: issue5a.d issue5b.c issue5c issue5d.e issue5e.d
+
+# Issue 6: transformation search can end up in an infinite loop
+#
+# There is no file or target from which issue6.f could be made from so
+# this should fail. The bug was that because rules .e.f, .d.e and .e.d
+# exist, make would try to make .f from .e and then infinitely try
+# to do .e from .d and vice versa.
+all: issue6.f
+
+# Issue 10: explicit dependencies affect transformation rule selection
+#
+# If issue10.e is wanted and both issue10.d and issue10.f are available,
+# make should choose the .d.e rule, because .d is before .f in .SUFFIXES.
+# The bug was that if issue10.d had an explicit dependency on issue10.f,
+# it would choose .f.e instead.
+all: issue10.e
+
+# Issue 11: sources from transformation rules are expanded incorrectly
+#
+# issue11.j should depend on issue11.i and issue11.second and issue11.i
+# should depend on issue11.h and issue11.first. The bug was that
+# the dynamic sources were expanded before ${.PREFIX} and ${.TARGET} were
+# available, so they would have expanded to a null string.
+all: issue11.j
+
+# we need to clean for repeatable results
+.BEGIN: clean
+clean:
+ @rm -f issue* .[ab]*
+
+.SUFFIXES: .a .b .c
+
+.a .a.b .b.a:
+ @echo 'There should be no text after the colon: ${.IMPSRC}'
+ touch ${.TARGET}
+
+.c.d .d.c .d .d.e .e.d:
+ @echo 'first set'
+ cp ${.IMPSRC} ${.TARGET}
+
+.SUFFIXES:
+.SUFFIXES: .c .d .e .f .g
+
+.e .e.f .f.e:
+ @echo 'second set'
+ cp ${.IMPSRC} ${.TARGET}
+
+issue3.a:
+ @echo 'There is a bug if you see this.'
+ touch ${.TARGET}
+
+issue5a.c issue5b.d issue5c.d issue5d.d issue5e.e issue10.d issue10.f:
+ touch ${.TARGET}
+
+.SUFFIXES: .h .i .j
+
+.h.i: ${.PREFIX}.first
+ @echo '.ALLSRC: ${.ALLSRC}'
+ cp ${.IMPSRC} ${.TARGET}
+
+.i.j: ${.PREFIX}.second
+ @echo '.ALLSRC: ${.ALLSRC}'
+ cp ${.IMPSRC} ${.TARGET}
+
+issue11.h issue11.first issue11.second:
+ touch ${.TARGET}
diff --git a/usr.bin/make/unit-tests/sunshcmd.exp b/usr.bin/make/unit-tests/sunshcmd.exp
new file mode 100644
index 0000000..b14f6b6
--- /dev/null
+++ b/usr.bin/make/unit-tests/sunshcmd.exp
@@ -0,0 +1,4 @@
+TEST1=hello
+TEST2=bye
+TEST3=later
+exit status 0
diff --git a/usr.bin/make/unit-tests/sunshcmd.mk b/usr.bin/make/unit-tests/sunshcmd.mk
new file mode 100644
index 0000000..e3baf90
--- /dev/null
+++ b/usr.bin/make/unit-tests/sunshcmd.mk
@@ -0,0 +1,10 @@
+BYECMD = echo bye
+LATERCMD = echo later
+TEST1 :sh = echo hello
+TEST2 :sh = ${BYECMD}
+TEST3 = ${LATERCMD:sh}
+
+all:
+ @echo "TEST1=${TEST1}"
+ @echo "TEST2=${TEST2}"
+ @echo "TEST3=${TEST3}"
diff --git a/usr.bin/make/unit-tests/sysv.exp b/usr.bin/make/unit-tests/sysv.exp
new file mode 100644
index 0000000..4cce2de
--- /dev/null
+++ b/usr.bin/make/unit-tests/sysv.exp
@@ -0,0 +1,7 @@
+FOOBAR =
+FOOBAR = foobar fubar
+fun
+fun
+fun
+In the Sun
+exit status 0
diff --git a/usr.bin/make/unit-tests/sysv.mk b/usr.bin/make/unit-tests/sysv.mk
new file mode 100644
index 0000000..3651eda
--- /dev/null
+++ b/usr.bin/make/unit-tests/sysv.mk
@@ -0,0 +1,26 @@
+# $Id: sysv.mk,v 1.2 2014/08/30 22:21:08 sjg Exp $
+
+FOO ?=
+FOOBAR = ${FOO:=bar}
+
+_this := ${.PARSEDIR}/${.PARSEFILE}
+
+B = /b
+S = /
+FUN = ${B}${S}fun
+SUN = the Sun
+
+# we expect nothing when FOO is empty
+all: foo fun
+
+foo:
+ @echo FOOBAR = ${FOOBAR}
+.if empty(FOO)
+ @FOO="foo fu" ${.MAKE} -f ${_this} foo
+.endif
+
+fun:
+ @echo ${FUN:T}
+ @echo ${FUN:${B}${S}fun=fun}
+ @echo ${FUN:${B}${S}%=%}
+ @echo ${In:L:%=% ${SUN}}
diff --git a/usr.bin/make/unit-tests/ternary.exp b/usr.bin/make/unit-tests/ternary.exp
new file mode 100644
index 0000000..ed9c1bd
--- /dev/null
+++ b/usr.bin/make/unit-tests/ternary.exp
@@ -0,0 +1,10 @@
+The answer is unknown
+The answer is unknown
+The answer is empty
+The answer is known
+The answer is
+The answer is empty
+The answer is known
+The answer is 42
+The answer is 42
+exit status 0
diff --git a/usr.bin/make/unit-tests/ternary.mk b/usr.bin/make/unit-tests/ternary.mk
new file mode 100644
index 0000000..77f8349
--- /dev/null
+++ b/usr.bin/make/unit-tests/ternary.mk
@@ -0,0 +1,8 @@
+
+all:
+ @for x in "" A= A=42; do ${.MAKE} -f ${MAKEFILE} show $$x; done
+
+show:
+ @echo "The answer is ${A:?known:unknown}"
+ @echo "The answer is ${A:?$A:unknown}"
+ @echo "The answer is ${empty(A):?empty:$A}"
diff --git a/usr.bin/make/unit-tests/unexport-env.exp b/usr.bin/make/unit-tests/unexport-env.exp
new file mode 100644
index 0000000..6d43cab
--- /dev/null
+++ b/usr.bin/make/unit-tests/unexport-env.exp
@@ -0,0 +1,2 @@
+UT_TEST=unexport-env
+exit status 0
diff --git a/usr.bin/make/unit-tests/unexport-env.mk b/usr.bin/make/unit-tests/unexport-env.mk
new file mode 100644
index 0000000..b8192f1
--- /dev/null
+++ b/usr.bin/make/unit-tests/unexport-env.mk
@@ -0,0 +1,14 @@
+# $Id: unexport-env.mk,v 1.1 2014/08/21 13:44:52 apb Exp $
+
+# pick up a bunch of exported vars
+.include "export.mk"
+
+# an example of setting up a minimal environment.
+PATH = /bin:/usr/bin:/sbin:/usr/sbin
+
+# now clobber the environment to just PATH and UT_TEST
+UT_TEST = unexport-env
+
+# this removes everything
+.unexport-env
+.export PATH UT_TEST
diff --git a/usr.bin/make/unit-tests/unexport.exp b/usr.bin/make/unit-tests/unexport.exp
new file mode 100644
index 0000000..7b16ea3
--- /dev/null
+++ b/usr.bin/make/unit-tests/unexport.exp
@@ -0,0 +1,4 @@
+UT_DOLLAR=This is $UT_FU
+UT_FU=fubar
+UT_TEST=unexport
+exit status 0
diff --git a/usr.bin/make/unit-tests/unexport.mk b/usr.bin/make/unit-tests/unexport.mk
new file mode 100644
index 0000000..b3d7d34
--- /dev/null
+++ b/usr.bin/make/unit-tests/unexport.mk
@@ -0,0 +1,8 @@
+# $Id: unexport.mk,v 1.1 2014/08/21 13:44:52 apb Exp $
+
+# pick up a bunch of exported vars
+.include "export.mk"
+
+.unexport UT_ZOO UT_FOO
+
+UT_TEST = unexport
diff --git a/usr.bin/make/unit-tests/varcmd.exp b/usr.bin/make/unit-tests/varcmd.exp
new file mode 100644
index 0000000..7803c2b
--- /dev/null
+++ b/usr.bin/make/unit-tests/varcmd.exp
@@ -0,0 +1,11 @@
+default FU=<v>fu</v> FOO=<v>foo</v> VAR=<v></v>
+two FU=<v>bar</v> FOO=<v>goo</v> VAR=<v></v>
+immutable FU='bar'
+immutable FOO='goo'
+three FU=<v>bar</v> FOO=<v>goo</v> VAR=<v></v>
+four FU=<v>bar</v> FOO=<v>goo</v> VAR=<v>Internal</v>
+five FU=<v>bar</v> FOO=<v>goo</v> VAR=<v>Internal</v>
+five v=is x k=is x
+six v=is y k=is y
+show-v v=override k=override
+exit status 0
diff --git a/usr.bin/make/unit-tests/varcmd.mk b/usr.bin/make/unit-tests/varcmd.mk
new file mode 100644
index 0000000..005ed47
--- /dev/null
+++ b/usr.bin/make/unit-tests/varcmd.mk
@@ -0,0 +1,60 @@
+# $Id: varcmd.mk,v 1.3 2017/12/08 03:36:42 sjg Exp $
+#
+# Test behaviour of recursive make and vars set on command line.
+
+FU=fu
+FOO?=foo
+.if !empty(.TARGETS)
+TAG=${.TARGETS}
+.endif
+TAG?=default
+
+all: one
+
+show:
+ @echo "${TAG} FU=<v>${FU}</v> FOO=<v>${FOO}</v> VAR=<v>${VAR}</v>"
+
+one: show
+ @${.MAKE} -f ${MAKEFILE} FU=bar FOO+=goo two
+
+two: show
+ @${.MAKE} -f ${MAKEFILE} three
+
+three: show
+ @${.MAKE} -f ${MAKEFILE} four
+
+
+.ifmake two
+# this should not work
+FU+= oops
+FOO+= oops
+_FU:= ${FU}
+_FOO:= ${FOO}
+two: immutable
+immutable:
+ @echo "$@ FU='${_FU}'"
+ @echo "$@ FOO='${_FOO}'"
+.endif
+.ifmake four
+VAR=Internal
+.MAKEOVERRIDES+= VAR
+.endif
+
+four: show
+ @${.MAKE} -f ${MAKEFILE} five
+
+M = x
+V.y = is y
+V.x = is x
+V := ${V.$M}
+K := ${V}
+
+show-v:
+ @echo '${TAG} v=${V} k=${K}'
+
+five: show show-v
+ @${.MAKE} -f ${MAKEFILE} M=y six
+
+six: show-v
+ @${.MAKE} -f ${MAKEFILE} V=override show-v
+
diff --git a/usr.bin/make/unit-tests/varmisc.exp b/usr.bin/make/unit-tests/varmisc.exp
new file mode 100644
index 0000000..ffe8f8b
--- /dev/null
+++ b/usr.bin/make/unit-tests/varmisc.exp
@@ -0,0 +1,25 @@
+
+:D expanded when var set
+true
+TRUE
+:U expanded when var undef
+true
+TRUE
+:D skipped if var undef
+
+:U skipped when var set
+is set
+:? only lhs when value true
+true
+TRUE
+:? only rhs when value false
+false
+FALSE
+do not evaluate or expand :? if discarding
+is set
+year=2016 month=04 day=01
+date=20160401
+Version=123.456.789 == 123456789
+Literal=3.4.5 == 3004005
+We have target specific vars
+exit status 0
diff --git a/usr.bin/make/unit-tests/varmisc.mk b/usr.bin/make/unit-tests/varmisc.mk
new file mode 100644
index 0000000..34d32cc
--- /dev/null
+++ b/usr.bin/make/unit-tests/varmisc.mk
@@ -0,0 +1,62 @@
+# $Id: varmisc.mk,v 1.8 2017/01/31 18:56:35 sjg Exp $
+#
+# Miscellaneous variable tests.
+
+all: unmatched_var_paren D_true U_true D_false U_false Q_lhs Q_rhs NQ_none \
+ strftime cmpv
+
+unmatched_var_paren:
+ @echo ${foo::=foo-text}
+
+True = ${echo true >&2:L:sh}TRUE
+False= ${echo false >&2:L:sh}FALSE
+
+VSET= is set
+.undef UNDEF
+
+U_false:
+ @echo :U skipped when var set
+ @echo ${VSET:U${False}}
+
+D_false:
+ @echo :D skipped if var undef
+ @echo ${UNDEF:D${False}}
+
+U_true:
+ @echo :U expanded when var undef
+ @echo ${UNDEF:U${True}}
+
+D_true:
+ @echo :D expanded when var set
+ @echo ${VSET:D${True}}
+
+Q_lhs:
+ @echo :? only lhs when value true
+ @echo ${1:L:?${True}:${False}}
+
+Q_rhs:
+ @echo :? only rhs when value false
+ @echo ${0:L:?${True}:${False}}
+
+NQ_none:
+ @echo do not evaluate or expand :? if discarding
+ @echo ${VSET:U${1:L:?${True}:${False}}}
+
+April1= 1459494000
+
+# slightly contorted syntax to use utc via variable
+strftime:
+ @echo ${year=%Y month=%m day=%d:L:gmtime=1459494000}
+ @echo date=${%Y%m%d:L:${gmtime=${April1}:L}}
+
+# big jumps to handle 3 digits per step
+M_cmpv.units = 1 1000 1000000
+M_cmpv = S,., ,g:_:range:@i@+ $${_:[-$$i]} \* $${M_cmpv.units:[$$i]}@:S,^,expr 0 ,1:sh
+
+Version = 123.456.789
+cmpv.only = target specific vars
+
+cmpv:
+ @echo Version=${Version} == ${Version:${M_cmpv}}
+ @echo Literal=3.4.5 == ${3.4.5:L:${M_cmpv}}
+ @echo We have ${${.TARGET:T}.only}
diff --git a/usr.bin/make/unit-tests/varquote.exp b/usr.bin/make/unit-tests/varquote.exp
new file mode 100644
index 0000000..63107bf
--- /dev/null
+++ b/usr.bin/make/unit-tests/varquote.exp
@@ -0,0 +1,3 @@
+-fdebug-prefix-map=$NETBSDSRCDIR=/usr/src -fdebug-regex-map=/usr/src/(.*)/obj$=/usr/obj/\1
+-fdebug-prefix-map=$NETBSDSRCDIR=/usr/src -fdebug-regex-map=/usr/src/(.*)/obj$=/usr/obj/\1
+exit status 0
diff --git a/usr.bin/make/unit-tests/varquote.mk b/usr.bin/make/unit-tests/varquote.mk
new file mode 100644
index 0000000..fb8b106
--- /dev/null
+++ b/usr.bin/make/unit-tests/varquote.mk
@@ -0,0 +1,14 @@
+# $NetBSD: varquote.mk,v 1.4 2018/12/16 18:53:34 christos Exp $
+#
+# Test VAR:q modifier
+
+.if !defined(REPROFLAGS)
+REPROFLAGS+= -fdebug-prefix-map=\$$NETBSDSRCDIR=/usr/src
+REPROFLAGS+= -fdebug-regex-map='/usr/src/(.*)/obj$$=/usr/obj/\1'
+all:
+ @${MAKE} -f ${MAKEFILE} REPROFLAGS=${REPROFLAGS:S/\$/&&/g:Q}
+ @${MAKE} -f ${MAKEFILE} REPROFLAGS=${REPROFLAGS:q}
+.else
+all:
+ @printf "%s %s\n" ${REPROFLAGS}
+.endif
diff --git a/usr.bin/make/unit-tests/varshell.exp b/usr.bin/make/unit-tests/varshell.exp
new file mode 100644
index 0000000..6ac8c88
--- /dev/null
+++ b/usr.bin/make/unit-tests/varshell.exp
@@ -0,0 +1,12 @@
+sh: /bin/no/such/command: not found
+make: "varshell.mk" line 5: warning: "/bin/no/such/command" returned non-zero status
+make: "varshell.mk" line 6: warning: "kill -14 $$" exited on a signal
+make: "varshell.mk" line 7: warning: "false" returned non-zero status
+make: "varshell.mk" line 8: warning: "echo "output before the error"; false" returned non-zero status
+EXEC_FAILED=''
+TERMINATED_BY_SIGNAL=''
+ERROR_NO_OUTPUT=''
+ERROR_WITH_OUTPUT='output before the error'
+NO_ERROR_NO_OUTPUT=''
+NO_ERROR_WITH_OUTPUT='this is good'
+exit status 0
diff --git a/usr.bin/make/unit-tests/varshell.mk b/usr.bin/make/unit-tests/varshell.mk
new file mode 100644
index 0000000..a006736
--- /dev/null
+++ b/usr.bin/make/unit-tests/varshell.mk
@@ -0,0 +1,18 @@
+# $Id: varshell.mk,v 1.2 2015/04/10 20:41:59 sjg Exp $
+#
+# Test VAR != shell command
+
+EXEC_FAILED != /bin/no/such/command
+TERMINATED_BY_SIGNAL != kill -14 $$$$
+ERROR_NO_OUTPUT != false
+ERROR_WITH_OUTPUT != echo "output before the error"; false
+NO_ERROR_NO_OUTPUT != true
+NO_ERROR_WITH_OUTPUT != echo "this is good"
+
+allvars= EXEC_FAILED TERMINATED_BY_SIGNAL ERROR_NO_OUTPUT ERROR_WITH_OUTPUT \
+ NO_ERROR_NO_OUTPUT NO_ERROR_WITH_OUTPUT
+
+all:
+.for v in ${allvars}
+ @echo ${v}=\'${${v}}\'
+.endfor
diff --git a/usr.bin/make/util.c b/usr.bin/make/util.c
new file mode 100644
index 0000000..506fb4d
--- /dev/null
+++ b/usr.bin/make/util.c
@@ -0,0 +1,494 @@
+/* $NetBSD: util.c,v 1.54 2013/11/26 13:44:41 joerg Exp $ */
+
+/*
+ * Missing stuff from OS's
+ */
+#if defined(__MINT__) || defined(__linux__)
+#include <signal.h>
+#endif
+
+#ifndef MAKE_NATIVE
+static char rcsid[] = "$NetBSD: util.c,v 1.54 2013/11/26 13:44:41 joerg Exp $";
+#else
+#include <sys/cdefs.h>
+#ifndef lint
+__RCSID("$NetBSD: util.c,v 1.54 2013/11/26 13:44:41 joerg Exp $");
+#endif
+#endif
+
+#include <sys/param.h>
+
+#include <errno.h>
+#include <stdio.h>
+#include <time.h>
+#include <signal.h>
+
+#include "make.h"
+
+#if !defined(MAKE_NATIVE) && !defined(HAVE_STRERROR)
+extern int errno, sys_nerr;
+extern char *sys_errlist[];
+
+char *
+strerror(int e)
+{
+ static char buf[100];
+ if (e < 0 || e >= sys_nerr) {
+ snprintf(buf, sizeof(buf), "Unknown error %d", e);
+ return buf;
+ }
+ else
+ return sys_errlist[e];
+}
+#endif
+
+#if !defined(MAKE_NATIVE) && !defined(HAVE_SETENV)
+extern char **environ;
+
+static char *
+findenv(const char *name, int *offset)
+{
+ size_t i, len;
+ char *p, *q;
+
+ len = strlen(name);
+ for (i = 0; (q = environ[i]); i++) {
+ p = strchr(q, '=');
+ if (p == NULL || p - q != len)
+ continue;
+ if (strncmp(name, q, len) == 0) {
+ *offset = i;
+ return q + len + 1;
+ }
+ }
+ *offset = i;
+ return NULL;
+}
+
+char *
+getenv(const char *name)
+{
+ int offset;
+
+ return(findenv(name, &offset));
+}
+
+int
+unsetenv(const char *name)
+{
+ char **p;
+ int offset;
+
+ if (name == NULL || *name == '\0' || strchr(name, '=') != NULL) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ while (findenv(name, &offset)) { /* if set multiple times */
+ for (p = &environ[offset];; ++p)
+ if (!(*p = *(p + 1)))
+ break;
+ }
+ return 0;
+}
+
+int
+setenv(const char *name, const char *value, int rewrite)
+{
+ char *c, **newenv;
+ const char *cc;
+ size_t l_value, size;
+ int offset;
+
+ if (name == NULL || value == NULL) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ if (*value == '=') /* no `=' in value */
+ ++value;
+ l_value = strlen(value);
+
+ /* find if already exists */
+ if ((c = findenv(name, &offset))) {
+ if (!rewrite)
+ return 0;
+ if (strlen(c) >= l_value) /* old larger; copy over */
+ goto copy;
+ } else { /* create new slot */
+ size = sizeof(char *) * (offset + 2);
+ if (savedEnv == environ) { /* just increase size */
+ if ((newenv = realloc(savedEnv, size)) == NULL)
+ return -1;
+ savedEnv = newenv;
+ } else { /* get new space */
+ /*
+ * We don't free here because we don't know if
+ * the first allocation is valid on all OS's
+ */
+ if ((savedEnv = malloc(size)) == NULL)
+ return -1;
+ (void)memcpy(savedEnv, environ, size - sizeof(char *));
+ }
+ environ = savedEnv;
+ environ[offset + 1] = NULL;
+ }
+ for (cc = name; *cc && *cc != '='; ++cc) /* no `=' in name */
+ continue;
+ size = cc - name;
+ /* name + `=' + value */
+ if ((environ[offset] = malloc(size + l_value + 2)) == NULL)
+ return -1;
+ c = environ[offset];
+ (void)memcpy(c, name, size);
+ c += size;
+ *c++ = '=';
+copy:
+ (void)memcpy(c, value, l_value + 1);
+ return 0;
+}
+
+#ifdef TEST
+int
+main(int argc, char *argv[])
+{
+ setenv(argv[1], argv[2], 0);
+ printf("%s\n", getenv(argv[1]));
+ unsetenv(argv[1]);
+ printf("%s\n", getenv(argv[1]));
+ return 0;
+}
+#endif
+
+#endif
+
+#if defined(__hpux__) || defined(__hpux)
+/* strrcpy():
+ * Like strcpy, going backwards and returning the new pointer
+ */
+static char *
+strrcpy(char *ptr, char *str)
+{
+ int len = strlen(str);
+
+ while (len)
+ *--ptr = str[--len];
+
+ return (ptr);
+} /* end strrcpy */
+
+char *sys_siglist[] = {
+ "Signal 0",
+ "Hangup", /* SIGHUP */
+ "Interrupt", /* SIGINT */
+ "Quit", /* SIGQUIT */
+ "Illegal instruction", /* SIGILL */
+ "Trace/BPT trap", /* SIGTRAP */
+ "IOT trap", /* SIGIOT */
+ "EMT trap", /* SIGEMT */
+ "Floating point exception", /* SIGFPE */
+ "Killed", /* SIGKILL */
+ "Bus error", /* SIGBUS */
+ "Segmentation fault", /* SIGSEGV */
+ "Bad system call", /* SIGSYS */
+ "Broken pipe", /* SIGPIPE */
+ "Alarm clock", /* SIGALRM */
+ "Terminated", /* SIGTERM */
+ "User defined signal 1", /* SIGUSR1 */
+ "User defined signal 2", /* SIGUSR2 */
+ "Child exited", /* SIGCLD */
+ "Power-fail restart", /* SIGPWR */
+ "Virtual timer expired", /* SIGVTALRM */
+ "Profiling timer expired", /* SIGPROF */
+ "I/O possible", /* SIGIO */
+ "Window size changes", /* SIGWINDOW */
+ "Stopped (signal)", /* SIGSTOP */
+ "Stopped", /* SIGTSTP */
+ "Continued", /* SIGCONT */
+ "Stopped (tty input)", /* SIGTTIN */
+ "Stopped (tty output)", /* SIGTTOU */
+ "Urgent I/O condition", /* SIGURG */
+ "Remote lock lost (NFS)", /* SIGLOST */
+ "Signal 31", /* reserved */
+ "DIL signal" /* SIGDIL */
+};
+#endif /* __hpux__ || __hpux */
+
+#if defined(__hpux__) || defined(__hpux)
+#include <sys/types.h>
+#include <sys/syscall.h>
+#include <sys/signal.h>
+#include <sys/stat.h>
+#include <dirent.h>
+#include <sys/time.h>
+#include <unistd.h>
+
+int
+killpg(int pid, int sig)
+{
+ return kill(-pid, sig);
+}
+
+#if !defined(__hpux__) && !defined(__hpux)
+void
+srandom(long seed)
+{
+ srand48(seed);
+}
+
+long
+random(void)
+{
+ return lrand48();
+}
+#endif
+
+#if !defined(__hpux__) && !defined(__hpux)
+int
+utimes(char *file, struct timeval tvp[2])
+{
+ struct utimbuf t;
+
+ t.actime = tvp[0].tv_sec;
+ t.modtime = tvp[1].tv_sec;
+ return(utime(file, &t));
+}
+#endif
+
+#if !defined(BSD) && !defined(d_fileno)
+# define d_fileno d_ino
+#endif
+
+#ifndef DEV_DEV_COMPARE
+# define DEV_DEV_COMPARE(a, b) ((a) == (b))
+#endif
+#define ISDOT(c) ((c)[0] == '.' && (((c)[1] == '\0') || ((c)[1] == '/')))
+#define ISDOTDOT(c) ((c)[0] == '.' && ISDOT(&((c)[1])))
+
+char *
+getwd(char *pathname)
+{
+ DIR *dp;
+ struct dirent *d;
+ extern int errno;
+
+ struct stat st_root, st_cur, st_next, st_dotdot;
+ char pathbuf[MAXPATHLEN], nextpathbuf[MAXPATHLEN * 2];
+ char *pathptr, *nextpathptr, *cur_name_add;
+
+ /* find the inode of root */
+ if (stat("/", &st_root) == -1) {
+ (void)sprintf(pathname,
+ "getwd: Cannot stat \"/\" (%s)", strerror(errno));
+ return NULL;
+ }
+ pathbuf[MAXPATHLEN - 1] = '\0';
+ pathptr = &pathbuf[MAXPATHLEN - 1];
+ nextpathbuf[MAXPATHLEN - 1] = '\0';
+ cur_name_add = nextpathptr = &nextpathbuf[MAXPATHLEN - 1];
+
+ /* find the inode of the current directory */
+ if (lstat(".", &st_cur) == -1) {
+ (void)sprintf(pathname,
+ "getwd: Cannot stat \".\" (%s)", strerror(errno));
+ return NULL;
+ }
+ nextpathptr = strrcpy(nextpathptr, "../");
+
+ /* Descend to root */
+ for (;;) {
+
+ /* look if we found root yet */
+ if (st_cur.st_ino == st_root.st_ino &&
+ DEV_DEV_COMPARE(st_cur.st_dev, st_root.st_dev)) {
+ (void)strcpy(pathname, *pathptr != '/' ? "/" : pathptr);
+ return (pathname);
+ }
+
+ /* open the parent directory */
+ if (stat(nextpathptr, &st_dotdot) == -1) {
+ (void)sprintf(pathname,
+ "getwd: Cannot stat directory \"%s\" (%s)",
+ nextpathptr, strerror(errno));
+ return NULL;
+ }
+ if ((dp = opendir(nextpathptr)) == NULL) {
+ (void)sprintf(pathname,
+ "getwd: Cannot open directory \"%s\" (%s)",
+ nextpathptr, strerror(errno));
+ return NULL;
+ }
+
+ /* look in the parent for the entry with the same inode */
+ if (DEV_DEV_COMPARE(st_dotdot.st_dev, st_cur.st_dev)) {
+ /* Parent has same device. No need to stat every member */
+ for (d = readdir(dp); d != NULL; d = readdir(dp))
+ if (d->d_fileno == st_cur.st_ino)
+ break;
+ }
+ else {
+ /*
+ * Parent has a different device. This is a mount point so we
+ * need to stat every member
+ */
+ for (d = readdir(dp); d != NULL; d = readdir(dp)) {
+ if (ISDOT(d->d_name) || ISDOTDOT(d->d_name))
+ continue;
+ (void)strcpy(cur_name_add, d->d_name);
+ if (lstat(nextpathptr, &st_next) == -1) {
+ (void)sprintf(pathname,
+ "getwd: Cannot stat \"%s\" (%s)",
+ d->d_name, strerror(errno));
+ (void)closedir(dp);
+ return NULL;
+ }
+ /* check if we found it yet */
+ if (st_next.st_ino == st_cur.st_ino &&
+ DEV_DEV_COMPARE(st_next.st_dev, st_cur.st_dev))
+ break;
+ }
+ }
+ if (d == NULL) {
+ (void)sprintf(pathname,
+ "getwd: Cannot find \".\" in \"..\"");
+ (void)closedir(dp);
+ return NULL;
+ }
+ st_cur = st_dotdot;
+ pathptr = strrcpy(pathptr, d->d_name);
+ pathptr = strrcpy(pathptr, "/");
+ nextpathptr = strrcpy(nextpathptr, "../");
+ (void)closedir(dp);
+ *cur_name_add = '\0';
+ }
+} /* end getwd */
+#endif /* __hpux */
+
+/* force posix signals */
+void (*
+bmake_signal(int s, void (*a)(int)))(int)
+{
+ struct sigaction sa, osa;
+
+ sa.sa_handler = a;
+ sigemptyset(&sa.sa_mask);
+ sa.sa_flags = SA_RESTART;
+
+ if (sigaction(s, &sa, &osa) == -1)
+ return SIG_ERR;
+ else
+ return osa.sa_handler;
+}
+
+#if !defined(MAKE_NATIVE) && !defined(HAVE_VSNPRINTF)
+#include <stdarg.h>
+
+#if !defined(__osf__)
+#ifdef _IOSTRG
+#define STRFLAG (_IOSTRG|_IOWRT) /* no _IOWRT: avoid stdio bug */
+#else
+#if 0
+#define STRFLAG (_IOREAD) /* XXX: Assume svr4 stdio */
+#endif
+#endif /* _IOSTRG */
+#endif /* __osf__ */
+
+int
+vsnprintf(char *s, size_t n, const char *fmt, va_list args)
+{
+#ifdef STRFLAG
+ FILE fakebuf;
+
+ fakebuf._flag = STRFLAG;
+ /*
+ * Some os's are char * _ptr, others are unsigned char *_ptr...
+ * We cast to void * to make everyone happy.
+ */
+ fakebuf._ptr = (void *)s;
+ fakebuf._cnt = n-1;
+ fakebuf._file = -1;
+ _doprnt(fmt, args, &fakebuf);
+ fakebuf._cnt++;
+ putc('\0', &fakebuf);
+ if (fakebuf._cnt<0)
+ fakebuf._cnt = 0;
+ return (n-fakebuf._cnt-1);
+#else
+ (void)vsprintf(s, fmt, args);
+ return strlen(s);
+#endif
+}
+
+int
+snprintf(char *s, size_t n, const char *fmt, ...)
+{
+ va_list ap;
+ int rv;
+
+ va_start(ap, fmt);
+ rv = vsnprintf(s, n, fmt, ap);
+ va_end(ap);
+ return rv;
+}
+
+#if !defined(MAKE_NATIVE) && !defined(HAVE_STRFTIME)
+size_t
+strftime(char *buf, size_t len, const char *fmt, const struct tm *tm)
+{
+ static char months[][4] = {
+ "Jan", "Feb", "Mar", "Apr", "May", "Jun",
+ "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
+ };
+
+ size_t s;
+ char *b = buf;
+
+ while (*fmt) {
+ if (len == 0)
+ return buf - b;
+ if (*fmt != '%') {
+ *buf++ = *fmt++;
+ len--;
+ continue;
+ }
+ switch (*fmt++) {
+ case '%':
+ *buf++ = '%';
+ len--;
+ if (len == 0) return buf - b;
+ /*FALLTHROUGH*/
+ case '\0':
+ *buf = '%';
+ s = 1;
+ break;
+ case 'k':
+ s = snprintf(buf, len, "%d", tm->tm_hour);
+ break;
+ case 'M':
+ s = snprintf(buf, len, "%02d", tm->tm_min);
+ break;
+ case 'S':
+ s = snprintf(buf, len, "%02d", tm->tm_sec);
+ break;
+ case 'b':
+ if (tm->tm_mon >= 12)
+ return buf - b;
+ s = snprintf(buf, len, "%s", months[tm->tm_mon]);
+ break;
+ case 'd':
+ s = snprintf(buf, len, "%02d", tm->tm_mday);
+ break;
+ case 'Y':
+ s = snprintf(buf, len, "%d", 1900 + tm->tm_year);
+ break;
+ default:
+ s = snprintf(buf, len, "Unsupported format %c",
+ fmt[-1]);
+ break;
+ }
+ buf += s;
+ len -= s;
+ }
+}
+#endif
+#endif
diff --git a/usr.bin/make/var.c b/usr.bin/make/var.c
new file mode 100644
index 0000000..547e7fd
--- /dev/null
+++ b/usr.bin/make/var.c
@@ -0,0 +1,4355 @@
+/* $NetBSD: var.c,v 1.221 2018/12/21 05:50:19 sjg Exp $ */
+
+/*
+ * Copyright (c) 1988, 1989, 1990, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Adam de Boor.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+/*
+ * Copyright (c) 1989 by Berkeley Softworks
+ * All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Adam de Boor.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. All advertising materials mentioning features or use of this software
+ * must display the following acknowledgement:
+ * This product includes software developed by the University of
+ * California, Berkeley and its contributors.
+ * 4. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#ifndef MAKE_NATIVE
+static char rcsid[] = "$NetBSD: var.c,v 1.221 2018/12/21 05:50:19 sjg Exp $";
+#else
+#include <sys/cdefs.h>
+#ifndef lint
+#if 0
+static char sccsid[] = "@(#)var.c 8.3 (Berkeley) 3/19/94";
+#else
+__RCSID("$NetBSD: var.c,v 1.221 2018/12/21 05:50:19 sjg Exp $");
+#endif
+#endif /* not lint */
+#endif
+
+/*-
+ * var.c --
+ * Variable-handling functions
+ *
+ * Interface:
+ * Var_Set Set the value of a variable in the given
+ * context. The variable is created if it doesn't
+ * yet exist. The value and variable name need not
+ * be preserved.
+ *
+ * Var_Append Append more characters to an existing variable
+ * in the given context. The variable needn't
+ * exist already -- it will be created if it doesn't.
+ * A space is placed between the old value and the
+ * new one.
+ *
+ * Var_Exists See if a variable exists.
+ *
+ * Var_Value Return the value of a variable in a context or
+ * NULL if the variable is undefined.
+ *
+ * Var_Subst Substitute named variable, or all variables if
+ * NULL in a string using
+ * the given context as the top-most one. If the
+ * third argument is non-zero, Parse_Error is
+ * called if any variables are undefined.
+ *
+ * Var_Parse Parse a variable expansion from a string and
+ * return the result and the number of characters
+ * consumed.
+ *
+ * Var_Delete Delete a variable in a context.
+ *
+ * Var_Init Initialize this module.
+ *
+ * Debugging:
+ * Var_Dump Print out all variables defined in the given
+ * context.
+ *
+ * XXX: There's a lot of duplication in these functions.
+ */
+
+#include <sys/stat.h>
+#ifndef NO_REGEX
+#include <sys/types.h>
+#include <regex.h>
+#endif
+#include <ctype.h>
+#include <inttypes.h>
+#include <stdlib.h>
+#include <limits.h>
+#include <time.h>
+
+#include "make.h"
+#include "buf.h"
+#include "dir.h"
+#include "job.h"
+#include "metachar.h"
+
+extern int makelevel;
+/*
+ * This lets us tell if we have replaced the original environ
+ * (which we cannot free).
+ */
+char **savedEnv = NULL;
+
+/*
+ * This is a harmless return value for Var_Parse that can be used by Var_Subst
+ * to determine if there was an error in parsing -- easier than returning
+ * a flag, as things outside this module don't give a hoot.
+ */
+char var_Error[] = "";
+
+/*
+ * Similar to var_Error, but returned when the 'VARF_UNDEFERR' flag for
+ * Var_Parse is not set. Why not just use a constant? Well, gcc likes
+ * to condense identical string instances...
+ */
+static char varNoError[] = "";
+
+/*
+ * Traditionally we consume $$ during := like any other expansion.
+ * Other make's do not.
+ * This knob allows controlling the behavior.
+ * FALSE for old behavior.
+ * TRUE for new compatible.
+ */
+#define SAVE_DOLLARS ".MAKE.SAVE_DOLLARS"
+static Boolean save_dollars = TRUE;
+
+/*
+ * Internally, variables are contained in four different contexts.
+ * 1) the environment. They may not be changed. If an environment
+ * variable is appended-to, the result is placed in the global
+ * context.
+ * 2) the global context. Variables set in the Makefile are located in
+ * the global context. It is the penultimate context searched when
+ * substituting.
+ * 3) the command-line context. All variables set on the command line
+ * are placed in this context. They are UNALTERABLE once placed here.
+ * 4) the local context. Each target has associated with it a context
+ * list. On this list are located the structures describing such
+ * local variables as $(@) and $(*)
+ * The four contexts are searched in the reverse order from which they are
+ * listed.
+ */
+GNode *VAR_INTERNAL; /* variables from make itself */
+GNode *VAR_GLOBAL; /* variables from the makefile */
+GNode *VAR_CMD; /* variables defined on the command-line */
+
+#define FIND_CMD 0x1 /* look in VAR_CMD when searching */
+#define FIND_GLOBAL 0x2 /* look in VAR_GLOBAL as well */
+#define FIND_ENV 0x4 /* look in the environment also */
+
+typedef struct Var {
+ char *name; /* the variable's name */
+ Buffer val; /* its value */
+ int flags; /* miscellaneous status flags */
+#define VAR_IN_USE 1 /* Variable's value currently being used.
+ * Used to avoid recursion */
+#define VAR_FROM_ENV 2 /* Variable comes from the environment */
+#define VAR_JUNK 4 /* Variable is a junk variable that
+ * should be destroyed when done with
+ * it. Used by Var_Parse for undefined,
+ * modified variables */
+#define VAR_KEEP 8 /* Variable is VAR_JUNK, but we found
+ * a use for it in some modifier and
+ * the value is therefore valid */
+#define VAR_EXPORTED 16 /* Variable is exported */
+#define VAR_REEXPORT 32 /* Indicate if var needs re-export.
+ * This would be true if it contains $'s
+ */
+#define VAR_FROM_CMD 64 /* Variable came from command line */
+} Var;
+
+/*
+ * Exporting vars is expensive so skip it if we can
+ */
+#define VAR_EXPORTED_NONE 0
+#define VAR_EXPORTED_YES 1
+#define VAR_EXPORTED_ALL 2
+static int var_exportedVars = VAR_EXPORTED_NONE;
+/*
+ * We pass this to Var_Export when doing the initial export
+ * or after updating an exported var.
+ */
+#define VAR_EXPORT_PARENT 1
+/*
+ * We pass this to Var_Export1 to tell it to leave the value alone.
+ */
+#define VAR_EXPORT_LITERAL 2
+
+/* Var*Pattern flags */
+#define VAR_SUB_GLOBAL 0x01 /* Apply substitution globally */
+#define VAR_SUB_ONE 0x02 /* Apply substitution to one word */
+#define VAR_SUB_MATCHED 0x04 /* There was a match */
+#define VAR_MATCH_START 0x08 /* Match at start of word */
+#define VAR_MATCH_END 0x10 /* Match at end of word */
+#define VAR_NOSUBST 0x20 /* don't expand vars in VarGetPattern */
+
+/* Var_Set flags */
+#define VAR_NO_EXPORT 0x01 /* do not export */
+
+typedef struct {
+ /*
+ * The following fields are set by Var_Parse() when it
+ * encounters modifiers that need to keep state for use by
+ * subsequent modifiers within the same variable expansion.
+ */
+ Byte varSpace; /* Word separator in expansions */
+ Boolean oneBigWord; /* TRUE if we will treat the variable as a
+ * single big word, even if it contains
+ * embedded spaces (as opposed to the
+ * usual behaviour of treating it as
+ * several space-separated words). */
+} Var_Parse_State;
+
+/* struct passed as 'void *' to VarSubstitute() for ":S/lhs/rhs/",
+ * to VarSYSVMatch() for ":lhs=rhs". */
+typedef struct {
+ const char *lhs; /* String to match */
+ int leftLen; /* Length of string */
+ const char *rhs; /* Replacement string (w/ &'s removed) */
+ int rightLen; /* Length of replacement */
+ int flags;
+} VarPattern;
+
+/* struct passed as 'void *' to VarLoopExpand() for ":@tvar@str@" */
+typedef struct {
+ GNode *ctxt; /* variable context */
+ char *tvar; /* name of temp var */
+ int tvarLen;
+ char *str; /* string to expand */
+ int strLen;
+ int errnum; /* errnum for not defined */
+} VarLoop_t;
+
+#ifndef NO_REGEX
+/* struct passed as 'void *' to VarRESubstitute() for ":C///" */
+typedef struct {
+ regex_t re;
+ int nsub;
+ regmatch_t *matches;
+ char *replace;
+ int flags;
+} VarREPattern;
+#endif
+
+/* struct passed to VarSelectWords() for ":[start..end]" */
+typedef struct {
+ int start; /* first word to select */
+ int end; /* last word to select */
+} VarSelectWords_t;
+
+static Var *VarFind(const char *, GNode *, int);
+static void VarAdd(const char *, const char *, GNode *);
+static Boolean VarHead(GNode *, Var_Parse_State *,
+ char *, Boolean, Buffer *, void *);
+static Boolean VarTail(GNode *, Var_Parse_State *,
+ char *, Boolean, Buffer *, void *);
+static Boolean VarSuffix(GNode *, Var_Parse_State *,
+ char *, Boolean, Buffer *, void *);
+static Boolean VarRoot(GNode *, Var_Parse_State *,
+ char *, Boolean, Buffer *, void *);
+static Boolean VarMatch(GNode *, Var_Parse_State *,
+ char *, Boolean, Buffer *, void *);
+#ifdef SYSVVARSUB
+static Boolean VarSYSVMatch(GNode *, Var_Parse_State *,
+ char *, Boolean, Buffer *, void *);
+#endif
+static Boolean VarNoMatch(GNode *, Var_Parse_State *,
+ char *, Boolean, Buffer *, void *);
+#ifndef NO_REGEX
+static void VarREError(int, regex_t *, const char *);
+static Boolean VarRESubstitute(GNode *, Var_Parse_State *,
+ char *, Boolean, Buffer *, void *);
+#endif
+static Boolean VarSubstitute(GNode *, Var_Parse_State *,
+ char *, Boolean, Buffer *, void *);
+static Boolean VarLoopExpand(GNode *, Var_Parse_State *,
+ char *, Boolean, Buffer *, void *);
+static char *VarGetPattern(GNode *, Var_Parse_State *,
+ int, const char **, int, int *, int *,
+ VarPattern *);
+static char *VarQuote(char *, Boolean);
+static char *VarHash(char *);
+static char *VarModify(GNode *, Var_Parse_State *,
+ const char *,
+ Boolean (*)(GNode *, Var_Parse_State *, char *, Boolean, Buffer *, void *),
+ void *);
+static char *VarOrder(const char *, const char);
+static char *VarUniq(const char *);
+static int VarWordCompare(const void *, const void *);
+static void VarPrintVar(void *);
+
+#define BROPEN '{'
+#define BRCLOSE '}'
+#define PROPEN '('
+#define PRCLOSE ')'
+
+/*-
+ *-----------------------------------------------------------------------
+ * VarFind --
+ * Find the given variable in the given context and any other contexts
+ * indicated.
+ *
+ * Input:
+ * name name to find
+ * ctxt context in which to find it
+ * flags FIND_GLOBAL set means to look in the
+ * VAR_GLOBAL context as well. FIND_CMD set means
+ * to look in the VAR_CMD context also. FIND_ENV
+ * set means to look in the environment
+ *
+ * Results:
+ * A pointer to the structure describing the desired variable or
+ * NULL if the variable does not exist.
+ *
+ * Side Effects:
+ * None
+ *-----------------------------------------------------------------------
+ */
+static Var *
+VarFind(const char *name, GNode *ctxt, int flags)
+{
+ Hash_Entry *var;
+ Var *v;
+
+ /*
+ * If the variable name begins with a '.', it could very well be one of
+ * the local ones. We check the name against all the local variables
+ * and substitute the short version in for 'name' if it matches one of
+ * them.
+ */
+ if (*name == '.' && isupper((unsigned char) name[1]))
+ switch (name[1]) {
+ case 'A':
+ if (!strcmp(name, ".ALLSRC"))
+ name = ALLSRC;
+ if (!strcmp(name, ".ARCHIVE"))
+ name = ARCHIVE;
+ break;
+ case 'I':
+ if (!strcmp(name, ".IMPSRC"))
+ name = IMPSRC;
+ break;
+ case 'M':
+ if (!strcmp(name, ".MEMBER"))
+ name = MEMBER;
+ break;
+ case 'O':
+ if (!strcmp(name, ".OODATE"))
+ name = OODATE;
+ break;
+ case 'P':
+ if (!strcmp(name, ".PREFIX"))
+ name = PREFIX;
+ break;
+ case 'T':
+ if (!strcmp(name, ".TARGET"))
+ name = TARGET;
+ break;
+ }
+#ifdef notyet
+ /* for compatibility with gmake */
+ if (name[0] == '^' && name[1] == '\0')
+ name = ALLSRC;
+#endif
+
+ /*
+ * First look for the variable in the given context. If it's not there,
+ * look for it in VAR_CMD, VAR_GLOBAL and the environment, in that order,
+ * depending on the FIND_* flags in 'flags'
+ */
+ var = Hash_FindEntry(&ctxt->context, name);
+
+ if ((var == NULL) && (flags & FIND_CMD) && (ctxt != VAR_CMD)) {
+ var = Hash_FindEntry(&VAR_CMD->context, name);
+ }
+ if (!checkEnvFirst && (var == NULL) && (flags & FIND_GLOBAL) &&
+ (ctxt != VAR_GLOBAL))
+ {
+ var = Hash_FindEntry(&VAR_GLOBAL->context, name);
+ if ((var == NULL) && (ctxt != VAR_INTERNAL)) {
+ /* VAR_INTERNAL is subordinate to VAR_GLOBAL */
+ var = Hash_FindEntry(&VAR_INTERNAL->context, name);
+ }
+ }
+ if ((var == NULL) && (flags & FIND_ENV)) {
+ char *env;
+
+ if ((env = getenv(name)) != NULL) {
+ int len;
+
+ v = bmake_malloc(sizeof(Var));
+ v->name = bmake_strdup(name);
+
+ len = strlen(env);
+
+ Buf_Init(&v->val, len + 1);
+ Buf_AddBytes(&v->val, len, env);
+
+ v->flags = VAR_FROM_ENV;
+ return (v);
+ } else if (checkEnvFirst && (flags & FIND_GLOBAL) &&
+ (ctxt != VAR_GLOBAL))
+ {
+ var = Hash_FindEntry(&VAR_GLOBAL->context, name);
+ if ((var == NULL) && (ctxt != VAR_INTERNAL)) {
+ var = Hash_FindEntry(&VAR_INTERNAL->context, name);
+ }
+ if (var == NULL) {
+ return NULL;
+ } else {
+ return ((Var *)Hash_GetValue(var));
+ }
+ } else {
+ return NULL;
+ }
+ } else if (var == NULL) {
+ return NULL;
+ } else {
+ return ((Var *)Hash_GetValue(var));
+ }
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * VarFreeEnv --
+ * If the variable is an environment variable, free it
+ *
+ * Input:
+ * v the variable
+ * destroy true if the value buffer should be destroyed.
+ *
+ * Results:
+ * 1 if it is an environment variable 0 ow.
+ *
+ * Side Effects:
+ * The variable is free'ed if it is an environent variable.
+ *-----------------------------------------------------------------------
+ */
+static Boolean
+VarFreeEnv(Var *v, Boolean destroy)
+{
+ if ((v->flags & VAR_FROM_ENV) == 0)
+ return FALSE;
+ free(v->name);
+ Buf_Destroy(&v->val, destroy);
+ free(v);
+ return TRUE;
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * VarAdd --
+ * Add a new variable of name name and value val to the given context
+ *
+ * Input:
+ * name name of variable to add
+ * val value to set it to
+ * ctxt context in which to set it
+ *
+ * Results:
+ * None
+ *
+ * Side Effects:
+ * The new variable is placed at the front of the given context
+ * The name and val arguments are duplicated so they may
+ * safely be freed.
+ *-----------------------------------------------------------------------
+ */
+static void
+VarAdd(const char *name, const char *val, GNode *ctxt)
+{
+ Var *v;
+ int len;
+ Hash_Entry *h;
+
+ v = bmake_malloc(sizeof(Var));
+
+ len = val ? strlen(val) : 0;
+ Buf_Init(&v->val, len+1);
+ Buf_AddBytes(&v->val, len, val);
+
+ v->flags = 0;
+
+ h = Hash_CreateEntry(&ctxt->context, name, NULL);
+ Hash_SetValue(h, v);
+ v->name = h->name;
+ if (DEBUG(VAR) && (ctxt->flags & INTERNAL) == 0) {
+ fprintf(debug_file, "%s:%s = %s\n", ctxt->name, name, val);
+ }
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * Var_Delete --
+ * Remove a variable from a context.
+ *
+ * Results:
+ * None.
+ *
+ * Side Effects:
+ * The Var structure is removed and freed.
+ *
+ *-----------------------------------------------------------------------
+ */
+void
+Var_Delete(const char *name, GNode *ctxt)
+{
+ Hash_Entry *ln;
+ char *cp;
+
+ if (strchr(name, '$')) {
+ cp = Var_Subst(NULL, name, VAR_GLOBAL, VARF_WANTRES);
+ } else {
+ cp = (char *)name;
+ }
+ ln = Hash_FindEntry(&ctxt->context, cp);
+ if (DEBUG(VAR)) {
+ fprintf(debug_file, "%s:delete %s%s\n",
+ ctxt->name, cp, ln ? "" : " (not found)");
+ }
+ if (cp != name) {
+ free(cp);
+ }
+ if (ln != NULL) {
+ Var *v;
+
+ v = (Var *)Hash_GetValue(ln);
+ if ((v->flags & VAR_EXPORTED)) {
+ unsetenv(v->name);
+ }
+ if (strcmp(MAKE_EXPORTED, v->name) == 0) {
+ var_exportedVars = VAR_EXPORTED_NONE;
+ }
+ if (v->name != ln->name)
+ free(v->name);
+ Hash_DeleteEntry(&ctxt->context, ln);
+ Buf_Destroy(&v->val, TRUE);
+ free(v);
+ }
+}
+
+
+/*
+ * Export a var.
+ * We ignore make internal variables (those which start with '.')
+ * Also we jump through some hoops to avoid calling setenv
+ * more than necessary since it can leak.
+ * We only manipulate flags of vars if 'parent' is set.
+ */
+static int
+Var_Export1(const char *name, int flags)
+{
+ char tmp[BUFSIZ];
+ Var *v;
+ char *val = NULL;
+ int n;
+ int parent = (flags & VAR_EXPORT_PARENT);
+
+ if (*name == '.')
+ return 0; /* skip internals */
+ if (!name[1]) {
+ /*
+ * A single char.
+ * If it is one of the vars that should only appear in
+ * local context, skip it, else we can get Var_Subst
+ * into a loop.
+ */
+ switch (name[0]) {
+ case '@':
+ case '%':
+ case '*':
+ case '!':
+ return 0;
+ }
+ }
+ v = VarFind(name, VAR_GLOBAL, 0);
+ if (v == NULL) {
+ return 0;
+ }
+ if (!parent &&
+ (v->flags & (VAR_EXPORTED|VAR_REEXPORT)) == VAR_EXPORTED) {
+ return 0; /* nothing to do */
+ }
+ val = Buf_GetAll(&v->val, NULL);
+ if ((flags & VAR_EXPORT_LITERAL) == 0 && strchr(val, '$')) {
+ if (parent) {
+ /*
+ * Flag this as something we need to re-export.
+ * No point actually exporting it now though,
+ * the child can do it at the last minute.
+ */
+ v->flags |= (VAR_EXPORTED|VAR_REEXPORT);
+ return 1;
+ }
+ if (v->flags & VAR_IN_USE) {
+ /*
+ * We recursed while exporting in a child.
+ * This isn't going to end well, just skip it.
+ */
+ return 0;
+ }
+ n = snprintf(tmp, sizeof(tmp), "${%s}", name);
+ if (n < (int)sizeof(tmp)) {
+ val = Var_Subst(NULL, tmp, VAR_GLOBAL, VARF_WANTRES);
+ setenv(name, val, 1);
+ free(val);
+ }
+ } else {
+ if (parent) {
+ v->flags &= ~VAR_REEXPORT; /* once will do */
+ }
+ if (parent || !(v->flags & VAR_EXPORTED)) {
+ setenv(name, val, 1);
+ }
+ }
+ /*
+ * This is so Var_Set knows to call Var_Export again...
+ */
+ if (parent) {
+ v->flags |= VAR_EXPORTED;
+ }
+ return 1;
+}
+
+/*
+ * This gets called from our children.
+ */
+void
+Var_ExportVars(void)
+{
+ char tmp[BUFSIZ];
+ Hash_Entry *var;
+ Hash_Search state;
+ Var *v;
+ char *val;
+ int n;
+
+ /*
+ * Several make's support this sort of mechanism for tracking
+ * recursion - but each uses a different name.
+ * We allow the makefiles to update MAKELEVEL and ensure
+ * children see a correctly incremented value.
+ */
+ snprintf(tmp, sizeof(tmp), "%d", makelevel + 1);
+ setenv(MAKE_LEVEL_ENV, tmp, 1);
+
+ if (VAR_EXPORTED_NONE == var_exportedVars)
+ return;
+
+ if (VAR_EXPORTED_ALL == var_exportedVars) {
+ /*
+ * Ouch! This is crazy...
+ */
+ for (var = Hash_EnumFirst(&VAR_GLOBAL->context, &state);
+ var != NULL;
+ var = Hash_EnumNext(&state)) {
+ v = (Var *)Hash_GetValue(var);
+ Var_Export1(v->name, 0);
+ }
+ return;
+ }
+ /*
+ * We have a number of exported vars,
+ */
+ n = snprintf(tmp, sizeof(tmp), "${" MAKE_EXPORTED ":O:u}");
+ if (n < (int)sizeof(tmp)) {
+ char **av;
+ char *as;
+ int ac;
+ int i;
+
+ val = Var_Subst(NULL, tmp, VAR_GLOBAL, VARF_WANTRES);
+ if (*val) {
+ av = brk_string(val, &ac, FALSE, &as);
+ for (i = 0; i < ac; i++) {
+ Var_Export1(av[i], 0);
+ }
+ free(as);
+ free(av);
+ }
+ free(val);
+ }
+}
+
+/*
+ * This is called when .export is seen or
+ * .MAKE.EXPORTED is modified.
+ * It is also called when any exported var is modified.
+ */
+void
+Var_Export(char *str, int isExport)
+{
+ char *name;
+ char *val;
+ char **av;
+ char *as;
+ int flags;
+ int ac;
+ int i;
+
+ if (isExport && (!str || !str[0])) {
+ var_exportedVars = VAR_EXPORTED_ALL; /* use with caution! */
+ return;
+ }
+
+ flags = 0;
+ if (strncmp(str, "-env", 4) == 0) {
+ str += 4;
+ } else if (strncmp(str, "-literal", 8) == 0) {
+ str += 8;
+ flags |= VAR_EXPORT_LITERAL;
+ } else {
+ flags |= VAR_EXPORT_PARENT;
+ }
+ val = Var_Subst(NULL, str, VAR_GLOBAL, VARF_WANTRES);
+ if (*val) {
+ av = brk_string(val, &ac, FALSE, &as);
+ for (i = 0; i < ac; i++) {
+ name = av[i];
+ if (!name[1]) {
+ /*
+ * A single char.
+ * If it is one of the vars that should only appear in
+ * local context, skip it, else we can get Var_Subst
+ * into a loop.
+ */
+ switch (name[0]) {
+ case '@':
+ case '%':
+ case '*':
+ case '!':
+ continue;
+ }
+ }
+ if (Var_Export1(name, flags)) {
+ if (VAR_EXPORTED_ALL != var_exportedVars)
+ var_exportedVars = VAR_EXPORTED_YES;
+ if (isExport && (flags & VAR_EXPORT_PARENT)) {
+ Var_Append(MAKE_EXPORTED, name, VAR_GLOBAL);
+ }
+ }
+ }
+ free(as);
+ free(av);
+ }
+ free(val);
+}
+
+
+/*
+ * This is called when .unexport[-env] is seen.
+ */
+extern char **environ;
+
+void
+Var_UnExport(char *str)
+{
+ char tmp[BUFSIZ];
+ char *vlist;
+ char *cp;
+ Boolean unexport_env;
+ int n;
+
+ if (!str || !str[0]) {
+ return; /* assert? */
+ }
+
+ vlist = NULL;
+
+ str += 8;
+ unexport_env = (strncmp(str, "-env", 4) == 0);
+ if (unexport_env) {
+ char **newenv;
+
+ cp = getenv(MAKE_LEVEL_ENV); /* we should preserve this */
+ if (environ == savedEnv) {
+ /* we have been here before! */
+ newenv = bmake_realloc(environ, 2 * sizeof(char *));
+ } else {
+ if (savedEnv) {
+ free(savedEnv);
+ savedEnv = NULL;
+ }
+ newenv = bmake_malloc(2 * sizeof(char *));
+ }
+ if (!newenv)
+ return;
+ /* Note: we cannot safely free() the original environ. */
+ environ = savedEnv = newenv;
+ newenv[0] = NULL;
+ newenv[1] = NULL;
+ if (cp && *cp)
+ setenv(MAKE_LEVEL_ENV, cp, 1);
+ } else {
+ for (; *str != '\n' && isspace((unsigned char) *str); str++)
+ continue;
+ if (str[0] && str[0] != '\n') {
+ vlist = str;
+ }
+ }
+
+ if (!vlist) {
+ /* Using .MAKE.EXPORTED */
+ n = snprintf(tmp, sizeof(tmp), "${" MAKE_EXPORTED ":O:u}");
+ if (n < (int)sizeof(tmp)) {
+ vlist = Var_Subst(NULL, tmp, VAR_GLOBAL, VARF_WANTRES);
+ }
+ }
+ if (vlist) {
+ Var *v;
+ char **av;
+ char *as;
+ int ac;
+ int i;
+
+ av = brk_string(vlist, &ac, FALSE, &as);
+ for (i = 0; i < ac; i++) {
+ v = VarFind(av[i], VAR_GLOBAL, 0);
+ if (!v)
+ continue;
+ if (!unexport_env &&
+ (v->flags & (VAR_EXPORTED|VAR_REEXPORT)) == VAR_EXPORTED) {
+ unsetenv(v->name);
+ }
+ v->flags &= ~(VAR_EXPORTED|VAR_REEXPORT);
+ /*
+ * If we are unexporting a list,
+ * remove each one from .MAKE.EXPORTED.
+ * If we are removing them all,
+ * just delete .MAKE.EXPORTED below.
+ */
+ if (vlist == str) {
+ n = snprintf(tmp, sizeof(tmp),
+ "${" MAKE_EXPORTED ":N%s}", v->name);
+ if (n < (int)sizeof(tmp)) {
+ cp = Var_Subst(NULL, tmp, VAR_GLOBAL, VARF_WANTRES);
+ Var_Set(MAKE_EXPORTED, cp, VAR_GLOBAL, 0);
+ free(cp);
+ }
+ }
+ }
+ free(as);
+ free(av);
+ if (vlist != str) {
+ Var_Delete(MAKE_EXPORTED, VAR_GLOBAL);
+ free(vlist);
+ }
+ }
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * Var_Set --
+ * Set the variable name to the value val in the given context.
+ *
+ * Input:
+ * name name of variable to set
+ * val value to give to the variable
+ * ctxt context in which to set it
+ *
+ * Results:
+ * None.
+ *
+ * Side Effects:
+ * If the variable doesn't yet exist, a new record is created for it.
+ * Else the old value is freed and the new one stuck in its place
+ *
+ * Notes:
+ * The variable is searched for only in its context before being
+ * created in that context. I.e. if the context is VAR_GLOBAL,
+ * only VAR_GLOBAL->context is searched. Likewise if it is VAR_CMD, only
+ * VAR_CMD->context is searched. This is done to avoid the literally
+ * thousands of unnecessary strcmp's that used to be done to
+ * set, say, $(@) or $(<).
+ * If the context is VAR_GLOBAL though, we check if the variable
+ * was set in VAR_CMD from the command line and skip it if so.
+ *-----------------------------------------------------------------------
+ */
+void
+Var_Set(const char *name, const char *val, GNode *ctxt, int flags)
+{
+ Var *v;
+ char *expanded_name = NULL;
+
+ /*
+ * We only look for a variable in the given context since anything set
+ * here will override anything in a lower context, so there's not much
+ * point in searching them all just to save a bit of memory...
+ */
+ if (strchr(name, '$') != NULL) {
+ expanded_name = Var_Subst(NULL, name, ctxt, VARF_WANTRES);
+ if (expanded_name[0] == 0) {
+ if (DEBUG(VAR)) {
+ fprintf(debug_file, "Var_Set(\"%s\", \"%s\", ...) "
+ "name expands to empty string - ignored\n",
+ name, val);
+ }
+ free(expanded_name);
+ return;
+ }
+ name = expanded_name;
+ }
+ if (ctxt == VAR_GLOBAL) {
+ v = VarFind(name, VAR_CMD, 0);
+ if (v != NULL) {
+ if ((v->flags & VAR_FROM_CMD)) {
+ if (DEBUG(VAR)) {
+ fprintf(debug_file, "%s:%s = %s ignored!\n", ctxt->name, name, val);
+ }
+ goto out;
+ }
+ VarFreeEnv(v, TRUE);
+ }
+ }
+ v = VarFind(name, ctxt, 0);
+ if (v == NULL) {
+ if (ctxt == VAR_CMD && (flags & VAR_NO_EXPORT) == 0) {
+ /*
+ * This var would normally prevent the same name being added
+ * to VAR_GLOBAL, so delete it from there if needed.
+ * Otherwise -V name may show the wrong value.
+ */
+ Var_Delete(name, VAR_GLOBAL);
+ }
+ VarAdd(name, val, ctxt);
+ } else {
+ Buf_Empty(&v->val);
+ if (val)
+ Buf_AddBytes(&v->val, strlen(val), val);
+
+ if (DEBUG(VAR)) {
+ fprintf(debug_file, "%s:%s = %s\n", ctxt->name, name, val);
+ }
+ if ((v->flags & VAR_EXPORTED)) {
+ Var_Export1(name, VAR_EXPORT_PARENT);
+ }
+ }
+ /*
+ * Any variables given on the command line are automatically exported
+ * to the environment (as per POSIX standard)
+ */
+ if (ctxt == VAR_CMD && (flags & VAR_NO_EXPORT) == 0) {
+ if (v == NULL) {
+ /* we just added it */
+ v = VarFind(name, ctxt, 0);
+ }
+ if (v != NULL)
+ v->flags |= VAR_FROM_CMD;
+ /*
+ * If requested, don't export these in the environment
+ * individually. We still put them in MAKEOVERRIDES so
+ * that the command-line settings continue to override
+ * Makefile settings.
+ */
+ if (varNoExportEnv != TRUE)
+ setenv(name, val ? val : "", 1);
+
+ Var_Append(MAKEOVERRIDES, name, VAR_GLOBAL);
+ }
+ if (*name == '.') {
+ if (strcmp(name, SAVE_DOLLARS) == 0)
+ save_dollars = s2Boolean(val, save_dollars);
+ }
+
+ out:
+ free(expanded_name);
+ if (v != NULL)
+ VarFreeEnv(v, TRUE);
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * Var_Append --
+ * The variable of the given name has the given value appended to it in
+ * the given context.
+ *
+ * Input:
+ * name name of variable to modify
+ * val String to append to it
+ * ctxt Context in which this should occur
+ *
+ * Results:
+ * None
+ *
+ * Side Effects:
+ * If the variable doesn't exist, it is created. Else the strings
+ * are concatenated (with a space in between).
+ *
+ * Notes:
+ * Only if the variable is being sought in the global context is the
+ * environment searched.
+ * XXX: Knows its calling circumstances in that if called with ctxt
+ * an actual target, it will only search that context since only
+ * a local variable could be being appended to. This is actually
+ * a big win and must be tolerated.
+ *-----------------------------------------------------------------------
+ */
+void
+Var_Append(const char *name, const char *val, GNode *ctxt)
+{
+ Var *v;
+ Hash_Entry *h;
+ char *expanded_name = NULL;
+
+ if (strchr(name, '$') != NULL) {
+ expanded_name = Var_Subst(NULL, name, ctxt, VARF_WANTRES);
+ if (expanded_name[0] == 0) {
+ if (DEBUG(VAR)) {
+ fprintf(debug_file, "Var_Append(\"%s\", \"%s\", ...) "
+ "name expands to empty string - ignored\n",
+ name, val);
+ }
+ free(expanded_name);
+ return;
+ }
+ name = expanded_name;
+ }
+
+ v = VarFind(name, ctxt, (ctxt == VAR_GLOBAL) ? (FIND_CMD|FIND_ENV) : 0);
+
+ if (v == NULL) {
+ Var_Set(name, val, ctxt, 0);
+ } else if (ctxt == VAR_CMD || !(v->flags & VAR_FROM_CMD)) {
+ Buf_AddByte(&v->val, ' ');
+ Buf_AddBytes(&v->val, strlen(val), val);
+
+ if (DEBUG(VAR)) {
+ fprintf(debug_file, "%s:%s = %s\n", ctxt->name, name,
+ Buf_GetAll(&v->val, NULL));
+ }
+
+ if (v->flags & VAR_FROM_ENV) {
+ /*
+ * If the original variable came from the environment, we
+ * have to install it in the global context (we could place
+ * it in the environment, but then we should provide a way to
+ * export other variables...)
+ */
+ v->flags &= ~VAR_FROM_ENV;
+ h = Hash_CreateEntry(&ctxt->context, name, NULL);
+ Hash_SetValue(h, v);
+ }
+ }
+ free(expanded_name);
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * Var_Exists --
+ * See if the given variable exists.
+ *
+ * Input:
+ * name Variable to find
+ * ctxt Context in which to start search
+ *
+ * Results:
+ * TRUE if it does, FALSE if it doesn't
+ *
+ * Side Effects:
+ * None.
+ *
+ *-----------------------------------------------------------------------
+ */
+Boolean
+Var_Exists(const char *name, GNode *ctxt)
+{
+ Var *v;
+ char *cp;
+
+ if ((cp = strchr(name, '$')) != NULL) {
+ cp = Var_Subst(NULL, name, ctxt, VARF_WANTRES);
+ }
+ v = VarFind(cp ? cp : name, ctxt, FIND_CMD|FIND_GLOBAL|FIND_ENV);
+ free(cp);
+ if (v == NULL) {
+ return(FALSE);
+ } else {
+ (void)VarFreeEnv(v, TRUE);
+ }
+ return(TRUE);
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * Var_Value --
+ * Return the value of the named variable in the given context
+ *
+ * Input:
+ * name name to find
+ * ctxt context in which to search for it
+ *
+ * Results:
+ * The value if the variable exists, NULL if it doesn't
+ *
+ * Side Effects:
+ * None
+ *-----------------------------------------------------------------------
+ */
+char *
+Var_Value(const char *name, GNode *ctxt, char **frp)
+{
+ Var *v;
+
+ v = VarFind(name, ctxt, FIND_ENV | FIND_GLOBAL | FIND_CMD);
+ *frp = NULL;
+ if (v != NULL) {
+ char *p = (Buf_GetAll(&v->val, NULL));
+ if (VarFreeEnv(v, FALSE))
+ *frp = p;
+ return p;
+ } else {
+ return NULL;
+ }
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * VarHead --
+ * Remove the tail of the given word and place the result in the given
+ * buffer.
+ *
+ * Input:
+ * word Word to trim
+ * addSpace True if need to add a space to the buffer
+ * before sticking in the head
+ * buf Buffer in which to store it
+ *
+ * Results:
+ * TRUE if characters were added to the buffer (a space needs to be
+ * added to the buffer before the next word).
+ *
+ * Side Effects:
+ * The trimmed word is added to the buffer.
+ *
+ *-----------------------------------------------------------------------
+ */
+static Boolean
+VarHead(GNode *ctx MAKE_ATTR_UNUSED, Var_Parse_State *vpstate,
+ char *word, Boolean addSpace, Buffer *buf,
+ void *dummy MAKE_ATTR_UNUSED)
+{
+ char *slash;
+
+ slash = strrchr(word, '/');
+ if (slash != NULL) {
+ if (addSpace && vpstate->varSpace) {
+ Buf_AddByte(buf, vpstate->varSpace);
+ }
+ *slash = '\0';
+ Buf_AddBytes(buf, strlen(word), word);
+ *slash = '/';
+ return (TRUE);
+ } else {
+ /*
+ * If no directory part, give . (q.v. the POSIX standard)
+ */
+ if (addSpace && vpstate->varSpace)
+ Buf_AddByte(buf, vpstate->varSpace);
+ Buf_AddByte(buf, '.');
+ }
+ return TRUE;
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * VarTail --
+ * Remove the head of the given word and place the result in the given
+ * buffer.
+ *
+ * Input:
+ * word Word to trim
+ * addSpace True if need to add a space to the buffer
+ * before adding the tail
+ * buf Buffer in which to store it
+ *
+ * Results:
+ * TRUE if characters were added to the buffer (a space needs to be
+ * added to the buffer before the next word).
+ *
+ * Side Effects:
+ * The trimmed word is added to the buffer.
+ *
+ *-----------------------------------------------------------------------
+ */
+static Boolean
+VarTail(GNode *ctx MAKE_ATTR_UNUSED, Var_Parse_State *vpstate,
+ char *word, Boolean addSpace, Buffer *buf,
+ void *dummy MAKE_ATTR_UNUSED)
+{
+ char *slash;
+
+ if (addSpace && vpstate->varSpace) {
+ Buf_AddByte(buf, vpstate->varSpace);
+ }
+
+ slash = strrchr(word, '/');
+ if (slash != NULL) {
+ *slash++ = '\0';
+ Buf_AddBytes(buf, strlen(slash), slash);
+ slash[-1] = '/';
+ } else {
+ Buf_AddBytes(buf, strlen(word), word);
+ }
+ return TRUE;
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * VarSuffix --
+ * Place the suffix of the given word in the given buffer.
+ *
+ * Input:
+ * word Word to trim
+ * addSpace TRUE if need to add a space before placing the
+ * suffix in the buffer
+ * buf Buffer in which to store it
+ *
+ * Results:
+ * TRUE if characters were added to the buffer (a space needs to be
+ * added to the buffer before the next word).
+ *
+ * Side Effects:
+ * The suffix from the word is placed in the buffer.
+ *
+ *-----------------------------------------------------------------------
+ */
+static Boolean
+VarSuffix(GNode *ctx MAKE_ATTR_UNUSED, Var_Parse_State *vpstate,
+ char *word, Boolean addSpace, Buffer *buf,
+ void *dummy MAKE_ATTR_UNUSED)
+{
+ char *dot;
+
+ dot = strrchr(word, '.');
+ if (dot != NULL) {
+ if (addSpace && vpstate->varSpace) {
+ Buf_AddByte(buf, vpstate->varSpace);
+ }
+ *dot++ = '\0';
+ Buf_AddBytes(buf, strlen(dot), dot);
+ dot[-1] = '.';
+ addSpace = TRUE;
+ }
+ return addSpace;
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * VarRoot --
+ * Remove the suffix of the given word and place the result in the
+ * buffer.
+ *
+ * Input:
+ * word Word to trim
+ * addSpace TRUE if need to add a space to the buffer
+ * before placing the root in it
+ * buf Buffer in which to store it
+ *
+ * Results:
+ * TRUE if characters were added to the buffer (a space needs to be
+ * added to the buffer before the next word).
+ *
+ * Side Effects:
+ * The trimmed word is added to the buffer.
+ *
+ *-----------------------------------------------------------------------
+ */
+static Boolean
+VarRoot(GNode *ctx MAKE_ATTR_UNUSED, Var_Parse_State *vpstate,
+ char *word, Boolean addSpace, Buffer *buf,
+ void *dummy MAKE_ATTR_UNUSED)
+{
+ char *dot;
+
+ if (addSpace && vpstate->varSpace) {
+ Buf_AddByte(buf, vpstate->varSpace);
+ }
+
+ dot = strrchr(word, '.');
+ if (dot != NULL) {
+ *dot = '\0';
+ Buf_AddBytes(buf, strlen(word), word);
+ *dot = '.';
+ } else {
+ Buf_AddBytes(buf, strlen(word), word);
+ }
+ return TRUE;
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * VarMatch --
+ * Place the word in the buffer if it matches the given pattern.
+ * Callback function for VarModify to implement the :M modifier.
+ *
+ * Input:
+ * word Word to examine
+ * addSpace TRUE if need to add a space to the buffer
+ * before adding the word, if it matches
+ * buf Buffer in which to store it
+ * pattern Pattern the word must match
+ *
+ * Results:
+ * TRUE if a space should be placed in the buffer before the next
+ * word.
+ *
+ * Side Effects:
+ * The word may be copied to the buffer.
+ *
+ *-----------------------------------------------------------------------
+ */
+static Boolean
+VarMatch(GNode *ctx MAKE_ATTR_UNUSED, Var_Parse_State *vpstate,
+ char *word, Boolean addSpace, Buffer *buf,
+ void *pattern)
+{
+ if (DEBUG(VAR))
+ fprintf(debug_file, "VarMatch [%s] [%s]\n", word, (char *)pattern);
+ if (Str_Match(word, (char *)pattern)) {
+ if (addSpace && vpstate->varSpace) {
+ Buf_AddByte(buf, vpstate->varSpace);
+ }
+ addSpace = TRUE;
+ Buf_AddBytes(buf, strlen(word), word);
+ }
+ return(addSpace);
+}
+
+#ifdef SYSVVARSUB
+/*-
+ *-----------------------------------------------------------------------
+ * VarSYSVMatch --
+ * Place the word in the buffer if it matches the given pattern.
+ * Callback function for VarModify to implement the System V %
+ * modifiers.
+ *
+ * Input:
+ * word Word to examine
+ * addSpace TRUE if need to add a space to the buffer
+ * before adding the word, if it matches
+ * buf Buffer in which to store it
+ * patp Pattern the word must match
+ *
+ * Results:
+ * TRUE if a space should be placed in the buffer before the next
+ * word.
+ *
+ * Side Effects:
+ * The word may be copied to the buffer.
+ *
+ *-----------------------------------------------------------------------
+ */
+static Boolean
+VarSYSVMatch(GNode *ctx, Var_Parse_State *vpstate,
+ char *word, Boolean addSpace, Buffer *buf,
+ void *patp)
+{
+ int len;
+ char *ptr;
+ VarPattern *pat = (VarPattern *)patp;
+ char *varexp;
+
+ if (addSpace && vpstate->varSpace)
+ Buf_AddByte(buf, vpstate->varSpace);
+
+ addSpace = TRUE;
+
+ if ((ptr = Str_SYSVMatch(word, pat->lhs, &len)) != NULL) {
+ varexp = Var_Subst(NULL, pat->rhs, ctx, VARF_WANTRES);
+ Str_SYSVSubst(buf, varexp, ptr, len);
+ free(varexp);
+ } else {
+ Buf_AddBytes(buf, strlen(word), word);
+ }
+
+ return(addSpace);
+}
+#endif
+
+
+/*-
+ *-----------------------------------------------------------------------
+ * VarNoMatch --
+ * Place the word in the buffer if it doesn't match the given pattern.
+ * Callback function for VarModify to implement the :N modifier.
+ *
+ * Input:
+ * word Word to examine
+ * addSpace TRUE if need to add a space to the buffer
+ * before adding the word, if it matches
+ * buf Buffer in which to store it
+ * pattern Pattern the word must match
+ *
+ * Results:
+ * TRUE if a space should be placed in the buffer before the next
+ * word.
+ *
+ * Side Effects:
+ * The word may be copied to the buffer.
+ *
+ *-----------------------------------------------------------------------
+ */
+static Boolean
+VarNoMatch(GNode *ctx MAKE_ATTR_UNUSED, Var_Parse_State *vpstate,
+ char *word, Boolean addSpace, Buffer *buf,
+ void *pattern)
+{
+ if (!Str_Match(word, (char *)pattern)) {
+ if (addSpace && vpstate->varSpace) {
+ Buf_AddByte(buf, vpstate->varSpace);
+ }
+ addSpace = TRUE;
+ Buf_AddBytes(buf, strlen(word), word);
+ }
+ return(addSpace);
+}
+
+
+/*-
+ *-----------------------------------------------------------------------
+ * VarSubstitute --
+ * Perform a string-substitution on the given word, placing the
+ * result in the passed buffer.
+ *
+ * Input:
+ * word Word to modify
+ * addSpace True if space should be added before
+ * other characters
+ * buf Buffer for result
+ * patternp Pattern for substitution
+ *
+ * Results:
+ * TRUE if a space is needed before more characters are added.
+ *
+ * Side Effects:
+ * None.
+ *
+ *-----------------------------------------------------------------------
+ */
+static Boolean
+VarSubstitute(GNode *ctx MAKE_ATTR_UNUSED, Var_Parse_State *vpstate,
+ char *word, Boolean addSpace, Buffer *buf,
+ void *patternp)
+{
+ int wordLen; /* Length of word */
+ char *cp; /* General pointer */
+ VarPattern *pattern = (VarPattern *)patternp;
+
+ wordLen = strlen(word);
+ if ((pattern->flags & (VAR_SUB_ONE|VAR_SUB_MATCHED)) !=
+ (VAR_SUB_ONE|VAR_SUB_MATCHED)) {
+ /*
+ * Still substituting -- break it down into simple anchored cases
+ * and if none of them fits, perform the general substitution case.
+ */
+ if ((pattern->flags & VAR_MATCH_START) &&
+ (strncmp(word, pattern->lhs, pattern->leftLen) == 0)) {
+ /*
+ * Anchored at start and beginning of word matches pattern
+ */
+ if ((pattern->flags & VAR_MATCH_END) &&
+ (wordLen == pattern->leftLen)) {
+ /*
+ * Also anchored at end and matches to the end (word
+ * is same length as pattern) add space and rhs only
+ * if rhs is non-null.
+ */
+ if (pattern->rightLen != 0) {
+ if (addSpace && vpstate->varSpace) {
+ Buf_AddByte(buf, vpstate->varSpace);
+ }
+ addSpace = TRUE;
+ Buf_AddBytes(buf, pattern->rightLen, pattern->rhs);
+ }
+ pattern->flags |= VAR_SUB_MATCHED;
+ } else if (pattern->flags & VAR_MATCH_END) {
+ /*
+ * Doesn't match to end -- copy word wholesale
+ */
+ goto nosub;
+ } else {
+ /*
+ * Matches at start but need to copy in trailing characters
+ */
+ if ((pattern->rightLen + wordLen - pattern->leftLen) != 0){
+ if (addSpace && vpstate->varSpace) {
+ Buf_AddByte(buf, vpstate->varSpace);
+ }
+ addSpace = TRUE;
+ }
+ Buf_AddBytes(buf, pattern->rightLen, pattern->rhs);
+ Buf_AddBytes(buf, wordLen - pattern->leftLen,
+ (word + pattern->leftLen));
+ pattern->flags |= VAR_SUB_MATCHED;
+ }
+ } else if (pattern->flags & VAR_MATCH_START) {
+ /*
+ * Had to match at start of word and didn't -- copy whole word.
+ */
+ goto nosub;
+ } else if (pattern->flags & VAR_MATCH_END) {
+ /*
+ * Anchored at end, Find only place match could occur (leftLen
+ * characters from the end of the word) and see if it does. Note
+ * that because the $ will be left at the end of the lhs, we have
+ * to use strncmp.
+ */
+ cp = word + (wordLen - pattern->leftLen);
+ if ((cp >= word) &&
+ (strncmp(cp, pattern->lhs, pattern->leftLen) == 0)) {
+ /*
+ * Match found. If we will place characters in the buffer,
+ * add a space before hand as indicated by addSpace, then
+ * stuff in the initial, unmatched part of the word followed
+ * by the right-hand-side.
+ */
+ if (((cp - word) + pattern->rightLen) != 0) {
+ if (addSpace && vpstate->varSpace) {
+ Buf_AddByte(buf, vpstate->varSpace);
+ }
+ addSpace = TRUE;
+ }
+ Buf_AddBytes(buf, cp - word, word);
+ Buf_AddBytes(buf, pattern->rightLen, pattern->rhs);
+ pattern->flags |= VAR_SUB_MATCHED;
+ } else {
+ /*
+ * Had to match at end and didn't. Copy entire word.
+ */
+ goto nosub;
+ }
+ } else {
+ /*
+ * Pattern is unanchored: search for the pattern in the word using
+ * String_FindSubstring, copying unmatched portions and the
+ * right-hand-side for each match found, handling non-global
+ * substitutions correctly, etc. When the loop is done, any
+ * remaining part of the word (word and wordLen are adjusted
+ * accordingly through the loop) is copied straight into the
+ * buffer.
+ * addSpace is set FALSE as soon as a space is added to the
+ * buffer.
+ */
+ Boolean done;
+ int origSize;
+
+ done = FALSE;
+ origSize = Buf_Size(buf);
+ while (!done) {
+ cp = Str_FindSubstring(word, pattern->lhs);
+ if (cp != NULL) {
+ if (addSpace && (((cp - word) + pattern->rightLen) != 0)){
+ Buf_AddByte(buf, vpstate->varSpace);
+ addSpace = FALSE;
+ }
+ Buf_AddBytes(buf, cp-word, word);
+ Buf_AddBytes(buf, pattern->rightLen, pattern->rhs);
+ wordLen -= (cp - word) + pattern->leftLen;
+ word = cp + pattern->leftLen;
+ if (wordLen == 0) {
+ done = TRUE;
+ }
+ if ((pattern->flags & VAR_SUB_GLOBAL) == 0) {
+ done = TRUE;
+ }
+ pattern->flags |= VAR_SUB_MATCHED;
+ } else {
+ done = TRUE;
+ }
+ }
+ if (wordLen != 0) {
+ if (addSpace && vpstate->varSpace) {
+ Buf_AddByte(buf, vpstate->varSpace);
+ }
+ Buf_AddBytes(buf, wordLen, word);
+ }
+ /*
+ * If added characters to the buffer, need to add a space
+ * before we add any more. If we didn't add any, just return
+ * the previous value of addSpace.
+ */
+ return ((Buf_Size(buf) != origSize) || addSpace);
+ }
+ return (addSpace);
+ }
+ nosub:
+ if (addSpace && vpstate->varSpace) {
+ Buf_AddByte(buf, vpstate->varSpace);
+ }
+ Buf_AddBytes(buf, wordLen, word);
+ return(TRUE);
+}
+
+#ifndef NO_REGEX
+/*-
+ *-----------------------------------------------------------------------
+ * VarREError --
+ * Print the error caused by a regcomp or regexec call.
+ *
+ * Results:
+ * None.
+ *
+ * Side Effects:
+ * An error gets printed.
+ *
+ *-----------------------------------------------------------------------
+ */
+static void
+VarREError(int reerr, regex_t *pat, const char *str)
+{
+ char *errbuf;
+ int errlen;
+
+ errlen = regerror(reerr, pat, 0, 0);
+ errbuf = bmake_malloc(errlen);
+ regerror(reerr, pat, errbuf, errlen);
+ Error("%s: %s", str, errbuf);
+ free(errbuf);
+}
+
+
+/*-
+ *-----------------------------------------------------------------------
+ * VarRESubstitute --
+ * Perform a regex substitution on the given word, placing the
+ * result in the passed buffer.
+ *
+ * Results:
+ * TRUE if a space is needed before more characters are added.
+ *
+ * Side Effects:
+ * None.
+ *
+ *-----------------------------------------------------------------------
+ */
+static Boolean
+VarRESubstitute(GNode *ctx MAKE_ATTR_UNUSED,
+ Var_Parse_State *vpstate MAKE_ATTR_UNUSED,
+ char *word, Boolean addSpace, Buffer *buf,
+ void *patternp)
+{
+ VarREPattern *pat;
+ int xrv;
+ char *wp;
+ char *rp;
+ int added;
+ int flags = 0;
+
+#define MAYBE_ADD_SPACE() \
+ if (addSpace && !added) \
+ Buf_AddByte(buf, ' '); \
+ added = 1
+
+ added = 0;
+ wp = word;
+ pat = patternp;
+
+ if ((pat->flags & (VAR_SUB_ONE|VAR_SUB_MATCHED)) ==
+ (VAR_SUB_ONE|VAR_SUB_MATCHED))
+ xrv = REG_NOMATCH;
+ else {
+ tryagain:
+ xrv = regexec(&pat->re, wp, pat->nsub, pat->matches, flags);
+ }
+
+ switch (xrv) {
+ case 0:
+ pat->flags |= VAR_SUB_MATCHED;
+ if (pat->matches[0].rm_so > 0) {
+ MAYBE_ADD_SPACE();
+ Buf_AddBytes(buf, pat->matches[0].rm_so, wp);
+ }
+
+ for (rp = pat->replace; *rp; rp++) {
+ if ((*rp == '\\') && ((rp[1] == '&') || (rp[1] == '\\'))) {
+ MAYBE_ADD_SPACE();
+ Buf_AddByte(buf,rp[1]);
+ rp++;
+ }
+ else if ((*rp == '&') ||
+ ((*rp == '\\') && isdigit((unsigned char)rp[1]))) {
+ int n;
+ const char *subbuf;
+ int sublen;
+ char errstr[3];
+
+ if (*rp == '&') {
+ n = 0;
+ errstr[0] = '&';
+ errstr[1] = '\0';
+ } else {
+ n = rp[1] - '0';
+ errstr[0] = '\\';
+ errstr[1] = rp[1];
+ errstr[2] = '\0';
+ rp++;
+ }
+
+ if (n > pat->nsub) {
+ Error("No subexpression %s", &errstr[0]);
+ subbuf = "";
+ sublen = 0;
+ } else if ((pat->matches[n].rm_so == -1) &&
+ (pat->matches[n].rm_eo == -1)) {
+ Error("No match for subexpression %s", &errstr[0]);
+ subbuf = "";
+ sublen = 0;
+ } else {
+ subbuf = wp + pat->matches[n].rm_so;
+ sublen = pat->matches[n].rm_eo - pat->matches[n].rm_so;
+ }
+
+ if (sublen > 0) {
+ MAYBE_ADD_SPACE();
+ Buf_AddBytes(buf, sublen, subbuf);
+ }
+ } else {
+ MAYBE_ADD_SPACE();
+ Buf_AddByte(buf, *rp);
+ }
+ }
+ wp += pat->matches[0].rm_eo;
+ if (pat->flags & VAR_SUB_GLOBAL) {
+ flags |= REG_NOTBOL;
+ if (pat->matches[0].rm_so == 0 && pat->matches[0].rm_eo == 0) {
+ MAYBE_ADD_SPACE();
+ Buf_AddByte(buf, *wp);
+ wp++;
+
+ }
+ if (*wp)
+ goto tryagain;
+ }
+ if (*wp) {
+ MAYBE_ADD_SPACE();
+ Buf_AddBytes(buf, strlen(wp), wp);
+ }
+ break;
+ default:
+ VarREError(xrv, &pat->re, "Unexpected regex error");
+ /* fall through */
+ case REG_NOMATCH:
+ if (*wp) {
+ MAYBE_ADD_SPACE();
+ Buf_AddBytes(buf,strlen(wp),wp);
+ }
+ break;
+ }
+ return(addSpace||added);
+}
+#endif
+
+
+
+/*-
+ *-----------------------------------------------------------------------
+ * VarLoopExpand --
+ * Implements the :@<temp>@<string>@ modifier of ODE make.
+ * We set the temp variable named in pattern.lhs to word and expand
+ * pattern.rhs storing the result in the passed buffer.
+ *
+ * Input:
+ * word Word to modify
+ * addSpace True if space should be added before
+ * other characters
+ * buf Buffer for result
+ * pattern Datafor substitution
+ *
+ * Results:
+ * TRUE if a space is needed before more characters are added.
+ *
+ * Side Effects:
+ * None.
+ *
+ *-----------------------------------------------------------------------
+ */
+static Boolean
+VarLoopExpand(GNode *ctx MAKE_ATTR_UNUSED,
+ Var_Parse_State *vpstate MAKE_ATTR_UNUSED,
+ char *word, Boolean addSpace, Buffer *buf,
+ void *loopp)
+{
+ VarLoop_t *loop = (VarLoop_t *)loopp;
+ char *s;
+ int slen;
+
+ if (word && *word) {
+ Var_Set(loop->tvar, word, loop->ctxt, VAR_NO_EXPORT);
+ s = Var_Subst(NULL, loop->str, loop->ctxt, loop->errnum | VARF_WANTRES);
+ if (s != NULL && *s != '\0') {
+ if (addSpace && *s != '\n')
+ Buf_AddByte(buf, ' ');
+ Buf_AddBytes(buf, (slen = strlen(s)), s);
+ addSpace = (slen > 0 && s[slen - 1] != '\n');
+ }
+ free(s);
+ }
+ return addSpace;
+}
+
+
+/*-
+ *-----------------------------------------------------------------------
+ * VarSelectWords --
+ * Implements the :[start..end] modifier.
+ * This is a special case of VarModify since we want to be able
+ * to scan the list backwards if start > end.
+ *
+ * Input:
+ * str String whose words should be trimmed
+ * seldata words to select
+ *
+ * Results:
+ * A string of all the words selected.
+ *
+ * Side Effects:
+ * None.
+ *
+ *-----------------------------------------------------------------------
+ */
+static char *
+VarSelectWords(GNode *ctx MAKE_ATTR_UNUSED, Var_Parse_State *vpstate,
+ const char *str, VarSelectWords_t *seldata)
+{
+ Buffer buf; /* Buffer for the new string */
+ Boolean addSpace; /* TRUE if need to add a space to the
+ * buffer before adding the trimmed
+ * word */
+ char **av; /* word list */
+ char *as; /* word list memory */
+ int ac, i;
+ int start, end, step;
+
+ Buf_Init(&buf, 0);
+ addSpace = FALSE;
+
+ if (vpstate->oneBigWord) {
+ /* fake what brk_string() would do if there were only one word */
+ ac = 1;
+ av = bmake_malloc((ac + 1) * sizeof(char *));
+ as = bmake_strdup(str);
+ av[0] = as;
+ av[1] = NULL;
+ } else {
+ av = brk_string(str, &ac, FALSE, &as);
+ }
+
+ /*
+ * Now sanitize seldata.
+ * If seldata->start or seldata->end are negative, convert them to
+ * the positive equivalents (-1 gets converted to argc, -2 gets
+ * converted to (argc-1), etc.).
+ */
+ if (seldata->start < 0)
+ seldata->start = ac + seldata->start + 1;
+ if (seldata->end < 0)
+ seldata->end = ac + seldata->end + 1;
+
+ /*
+ * We avoid scanning more of the list than we need to.
+ */
+ if (seldata->start > seldata->end) {
+ start = MIN(ac, seldata->start) - 1;
+ end = MAX(0, seldata->end - 1);
+ step = -1;
+ } else {
+ start = MAX(0, seldata->start - 1);
+ end = MIN(ac, seldata->end);
+ step = 1;
+ }
+
+ for (i = start;
+ (step < 0 && i >= end) || (step > 0 && i < end);
+ i += step) {
+ if (av[i] && *av[i]) {
+ if (addSpace && vpstate->varSpace) {
+ Buf_AddByte(&buf, vpstate->varSpace);
+ }
+ Buf_AddBytes(&buf, strlen(av[i]), av[i]);
+ addSpace = TRUE;
+ }
+ }
+
+ free(as);
+ free(av);
+
+ return Buf_Destroy(&buf, FALSE);
+}
+
+
+/*-
+ * VarRealpath --
+ * Replace each word with the result of realpath()
+ * if successful.
+ */
+static Boolean
+VarRealpath(GNode *ctx MAKE_ATTR_UNUSED, Var_Parse_State *vpstate,
+ char *word, Boolean addSpace, Buffer *buf,
+ void *patternp MAKE_ATTR_UNUSED)
+{
+ struct stat st;
+ char rbuf[MAXPATHLEN];
+ char *rp;
+
+ if (addSpace && vpstate->varSpace) {
+ Buf_AddByte(buf, vpstate->varSpace);
+ }
+ addSpace = TRUE;
+ rp = cached_realpath(word, rbuf);
+ if (rp && *rp == '/' && stat(rp, &st) == 0)
+ word = rp;
+
+ Buf_AddBytes(buf, strlen(word), word);
+ return(addSpace);
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * VarModify --
+ * Modify each of the words of the passed string using the given
+ * function. Used to implement all modifiers.
+ *
+ * Input:
+ * str String whose words should be trimmed
+ * modProc Function to use to modify them
+ * datum Datum to pass it
+ *
+ * Results:
+ * A string of all the words modified appropriately.
+ *
+ * Side Effects:
+ * None.
+ *
+ *-----------------------------------------------------------------------
+ */
+static char *
+VarModify(GNode *ctx, Var_Parse_State *vpstate,
+ const char *str,
+ Boolean (*modProc)(GNode *, Var_Parse_State *, char *,
+ Boolean, Buffer *, void *),
+ void *datum)
+{
+ Buffer buf; /* Buffer for the new string */
+ Boolean addSpace; /* TRUE if need to add a space to the
+ * buffer before adding the trimmed
+ * word */
+ char **av; /* word list */
+ char *as; /* word list memory */
+ int ac, i;
+
+ Buf_Init(&buf, 0);
+ addSpace = FALSE;
+
+ if (vpstate->oneBigWord) {
+ /* fake what brk_string() would do if there were only one word */
+ ac = 1;
+ av = bmake_malloc((ac + 1) * sizeof(char *));
+ as = bmake_strdup(str);
+ av[0] = as;
+ av[1] = NULL;
+ } else {
+ av = brk_string(str, &ac, FALSE, &as);
+ }
+
+ for (i = 0; i < ac; i++) {
+ addSpace = (*modProc)(ctx, vpstate, av[i], addSpace, &buf, datum);
+ }
+
+ free(as);
+ free(av);
+
+ return Buf_Destroy(&buf, FALSE);
+}
+
+
+static int
+VarWordCompare(const void *a, const void *b)
+{
+ int r = strcmp(*(const char * const *)a, *(const char * const *)b);
+ return r;
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * VarOrder --
+ * Order the words in the string.
+ *
+ * Input:
+ * str String whose words should be sorted.
+ * otype How to order: s - sort, x - random.
+ *
+ * Results:
+ * A string containing the words ordered.
+ *
+ * Side Effects:
+ * None.
+ *
+ *-----------------------------------------------------------------------
+ */
+static char *
+VarOrder(const char *str, const char otype)
+{
+ Buffer buf; /* Buffer for the new string */
+ char **av; /* word list [first word does not count] */
+ char *as; /* word list memory */
+ int ac, i;
+
+ Buf_Init(&buf, 0);
+
+ av = brk_string(str, &ac, FALSE, &as);
+
+ if (ac > 0)
+ switch (otype) {
+ case 's': /* sort alphabetically */
+ qsort(av, ac, sizeof(char *), VarWordCompare);
+ break;
+ case 'x': /* randomize */
+ {
+ int rndidx;
+ char *t;
+
+ /*
+ * We will use [ac..2] range for mod factors. This will produce
+ * random numbers in [(ac-1)..0] interval, and minimal
+ * reasonable value for mod factor is 2 (the mod 1 will produce
+ * 0 with probability 1).
+ */
+ for (i = ac-1; i > 0; i--) {
+ rndidx = random() % (i + 1);
+ if (i != rndidx) {
+ t = av[i];
+ av[i] = av[rndidx];
+ av[rndidx] = t;
+ }
+ }
+ }
+ } /* end of switch */
+
+ for (i = 0; i < ac; i++) {
+ Buf_AddBytes(&buf, strlen(av[i]), av[i]);
+ if (i != ac - 1)
+ Buf_AddByte(&buf, ' ');
+ }
+
+ free(as);
+ free(av);
+
+ return Buf_Destroy(&buf, FALSE);
+}
+
+
+/*-
+ *-----------------------------------------------------------------------
+ * VarUniq --
+ * Remove adjacent duplicate words.
+ *
+ * Input:
+ * str String whose words should be sorted
+ *
+ * Results:
+ * A string containing the resulting words.
+ *
+ * Side Effects:
+ * None.
+ *
+ *-----------------------------------------------------------------------
+ */
+static char *
+VarUniq(const char *str)
+{
+ Buffer buf; /* Buffer for new string */
+ char **av; /* List of words to affect */
+ char *as; /* Word list memory */
+ int ac, i, j;
+
+ Buf_Init(&buf, 0);
+ av = brk_string(str, &ac, FALSE, &as);
+
+ if (ac > 1) {
+ for (j = 0, i = 1; i < ac; i++)
+ if (strcmp(av[i], av[j]) != 0 && (++j != i))
+ av[j] = av[i];
+ ac = j + 1;
+ }
+
+ for (i = 0; i < ac; i++) {
+ Buf_AddBytes(&buf, strlen(av[i]), av[i]);
+ if (i != ac - 1)
+ Buf_AddByte(&buf, ' ');
+ }
+
+ free(as);
+ free(av);
+
+ return Buf_Destroy(&buf, FALSE);
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * VarRange --
+ * Return an integer sequence
+ *
+ * Input:
+ * str String whose words provide default range
+ * ac range length, if 0 use str words
+ *
+ * Side Effects:
+ * None.
+ *
+ *-----------------------------------------------------------------------
+ */
+static char *
+VarRange(const char *str, int ac)
+{
+ Buffer buf; /* Buffer for new string */
+ char tmp[32]; /* each element */
+ char **av; /* List of words to affect */
+ char *as; /* Word list memory */
+ int i, n;
+
+ Buf_Init(&buf, 0);
+ if (ac > 0) {
+ as = NULL;
+ av = NULL;
+ } else {
+ av = brk_string(str, &ac, FALSE, &as);
+ }
+ for (i = 0; i < ac; i++) {
+ n = snprintf(tmp, sizeof(tmp), "%d", 1 + i);
+ if (n >= (int)sizeof(tmp))
+ break;
+ Buf_AddBytes(&buf, n, tmp);
+ if (i != ac - 1)
+ Buf_AddByte(&buf, ' ');
+ }
+
+ free(as);
+ free(av);
+
+ return Buf_Destroy(&buf, FALSE);
+}
+
+
+/*-
+ *-----------------------------------------------------------------------
+ * VarGetPattern --
+ * Pass through the tstr looking for 1) escaped delimiters,
+ * '$'s and backslashes (place the escaped character in
+ * uninterpreted) and 2) unescaped $'s that aren't before
+ * the delimiter (expand the variable substitution unless flags
+ * has VAR_NOSUBST set).
+ * Return the expanded string or NULL if the delimiter was missing
+ * If pattern is specified, handle escaped ampersands, and replace
+ * unescaped ampersands with the lhs of the pattern.
+ *
+ * Results:
+ * A string of all the words modified appropriately.
+ * If length is specified, return the string length of the buffer
+ * If flags is specified and the last character of the pattern is a
+ * $ set the VAR_MATCH_END bit of flags.
+ *
+ * Side Effects:
+ * None.
+ *-----------------------------------------------------------------------
+ */
+static char *
+VarGetPattern(GNode *ctxt, Var_Parse_State *vpstate MAKE_ATTR_UNUSED,
+ int flags, const char **tstr, int delim, int *vflags,
+ int *length, VarPattern *pattern)
+{
+ const char *cp;
+ char *rstr;
+ Buffer buf;
+ int junk;
+ int errnum = flags & VARF_UNDEFERR;
+
+ Buf_Init(&buf, 0);
+ if (length == NULL)
+ length = &junk;
+
+#define IS_A_MATCH(cp, delim) \
+ ((cp[0] == '\\') && ((cp[1] == delim) || \
+ (cp[1] == '\\') || (cp[1] == '$') || (pattern && (cp[1] == '&'))))
+
+ /*
+ * Skim through until the matching delimiter is found;
+ * pick up variable substitutions on the way. Also allow
+ * backslashes to quote the delimiter, $, and \, but don't
+ * touch other backslashes.
+ */
+ for (cp = *tstr; *cp && (*cp != delim); cp++) {
+ if (IS_A_MATCH(cp, delim)) {
+ Buf_AddByte(&buf, cp[1]);
+ cp++;
+ } else if (*cp == '$') {
+ if (cp[1] == delim) {
+ if (vflags == NULL)
+ Buf_AddByte(&buf, *cp);
+ else
+ /*
+ * Unescaped $ at end of pattern => anchor
+ * pattern at end.
+ */
+ *vflags |= VAR_MATCH_END;
+ } else {
+ if (vflags == NULL || (*vflags & VAR_NOSUBST) == 0) {
+ char *cp2;
+ int len;
+ void *freeIt;
+
+ /*
+ * If unescaped dollar sign not before the
+ * delimiter, assume it's a variable
+ * substitution and recurse.
+ */
+ cp2 = Var_Parse(cp, ctxt, errnum | VARF_WANTRES, &len,
+ &freeIt);
+ Buf_AddBytes(&buf, strlen(cp2), cp2);
+ free(freeIt);
+ cp += len - 1;
+ } else {
+ const char *cp2 = &cp[1];
+
+ if (*cp2 == PROPEN || *cp2 == BROPEN) {
+ /*
+ * Find the end of this variable reference
+ * and suck it in without further ado.
+ * It will be interperated later.
+ */
+ int have = *cp2;
+ int want = (*cp2 == PROPEN) ? PRCLOSE : BRCLOSE;
+ int depth = 1;
+
+ for (++cp2; *cp2 != '\0' && depth > 0; ++cp2) {
+ if (cp2[-1] != '\\') {
+ if (*cp2 == have)
+ ++depth;
+ if (*cp2 == want)
+ --depth;
+ }
+ }
+ Buf_AddBytes(&buf, cp2 - cp, cp);
+ cp = --cp2;
+ } else
+ Buf_AddByte(&buf, *cp);
+ }
+ }
+ }
+ else if (pattern && *cp == '&')
+ Buf_AddBytes(&buf, pattern->leftLen, pattern->lhs);
+ else
+ Buf_AddByte(&buf, *cp);
+ }
+
+ if (*cp != delim) {
+ *tstr = cp;
+ *length = 0;
+ return NULL;
+ }
+
+ *tstr = ++cp;
+ *length = Buf_Size(&buf);
+ rstr = Buf_Destroy(&buf, FALSE);
+ if (DEBUG(VAR))
+ fprintf(debug_file, "Modifier pattern: \"%s\"\n", rstr);
+ return rstr;
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * VarQuote --
+ * Quote shell meta-characters and space characters in the string
+ * if quoteDollar is set, also quote and double any '$' characters.
+ *
+ * Results:
+ * The quoted string
+ *
+ * Side Effects:
+ * None.
+ *
+ *-----------------------------------------------------------------------
+ */
+static char *
+VarQuote(char *str, Boolean quoteDollar)
+{
+
+ Buffer buf;
+ const char *newline;
+ size_t nlen;
+
+ if ((newline = Shell_GetNewline()) == NULL)
+ newline = "\\\n";
+ nlen = strlen(newline);
+
+ Buf_Init(&buf, 0);
+
+ for (; *str != '\0'; str++) {
+ if (*str == '\n') {
+ Buf_AddBytes(&buf, nlen, newline);
+ continue;
+ }
+ if (isspace((unsigned char)*str) || ismeta((unsigned char)*str))
+ Buf_AddByte(&buf, '\\');
+ Buf_AddByte(&buf, *str);
+ if (quoteDollar && *str == '$')
+ Buf_AddBytes(&buf, 2, "\\$");
+ }
+
+ str = Buf_Destroy(&buf, FALSE);
+ if (DEBUG(VAR))
+ fprintf(debug_file, "QuoteMeta: [%s]\n", str);
+ return str;
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * VarHash --
+ * Hash the string using the MurmurHash3 algorithm.
+ * Output is computed using 32bit Little Endian arithmetic.
+ *
+ * Input:
+ * str String to modify
+ *
+ * Results:
+ * Hash value of str, encoded as 8 hex digits.
+ *
+ * Side Effects:
+ * None.
+ *
+ *-----------------------------------------------------------------------
+ */
+static char *
+VarHash(char *str)
+{
+ static const char hexdigits[16] = "0123456789abcdef";
+ Buffer buf;
+ size_t len, len2;
+ unsigned char *ustr = (unsigned char *)str;
+ uint32_t h, k, c1, c2;
+
+ h = 0x971e137bU;
+ c1 = 0x95543787U;
+ c2 = 0x2ad7eb25U;
+ len2 = strlen(str);
+
+ for (len = len2; len; ) {
+ k = 0;
+ switch (len) {
+ default:
+ k = (ustr[3] << 24) | (ustr[2] << 16) | (ustr[1] << 8) | ustr[0];
+ len -= 4;
+ ustr += 4;
+ break;
+ case 3:
+ k |= (ustr[2] << 16);
+ case 2:
+ k |= (ustr[1] << 8);
+ case 1:
+ k |= ustr[0];
+ len = 0;
+ }
+ c1 = c1 * 5 + 0x7b7d159cU;
+ c2 = c2 * 5 + 0x6bce6396U;
+ k *= c1;
+ k = (k << 11) ^ (k >> 21);
+ k *= c2;
+ h = (h << 13) ^ (h >> 19);
+ h = h * 5 + 0x52dce729U;
+ h ^= k;
+ }
+ h ^= len2;
+ h *= 0x85ebca6b;
+ h ^= h >> 13;
+ h *= 0xc2b2ae35;
+ h ^= h >> 16;
+
+ Buf_Init(&buf, 0);
+ for (len = 0; len < 8; ++len) {
+ Buf_AddByte(&buf, hexdigits[h & 15]);
+ h >>= 4;
+ }
+
+ return Buf_Destroy(&buf, FALSE);
+}
+
+static char *
+VarStrftime(const char *fmt, int zulu, time_t utc)
+{
+ char buf[BUFSIZ];
+
+ if (!utc)
+ time(&utc);
+ if (!*fmt)
+ fmt = "%c";
+ strftime(buf, sizeof(buf), fmt, zulu ? gmtime(&utc) : localtime(&utc));
+
+ buf[sizeof(buf) - 1] = '\0';
+ return bmake_strdup(buf);
+}
+
+/*
+ * Now we need to apply any modifiers the user wants applied.
+ * These are:
+ * :M<pattern> words which match the given <pattern>.
+ * <pattern> is of the standard file
+ * wildcarding form.
+ * :N<pattern> words which do not match the given <pattern>.
+ * :S<d><pat1><d><pat2><d>[1gW]
+ * Substitute <pat2> for <pat1> in the value
+ * :C<d><pat1><d><pat2><d>[1gW]
+ * Substitute <pat2> for regex <pat1> in the value
+ * :H Substitute the head of each word
+ * :T Substitute the tail of each word
+ * :E Substitute the extension (minus '.') of
+ * each word
+ * :R Substitute the root of each word
+ * (pathname minus the suffix).
+ * :O ("Order") Alphabeticaly sort words in variable.
+ * :Ox ("intermiX") Randomize words in variable.
+ * :u ("uniq") Remove adjacent duplicate words.
+ * :tu Converts the variable contents to uppercase.
+ * :tl Converts the variable contents to lowercase.
+ * :ts[c] Sets varSpace - the char used to
+ * separate words to 'c'. If 'c' is
+ * omitted then no separation is used.
+ * :tW Treat the variable contents as a single
+ * word, even if it contains spaces.
+ * (Mnemonic: one big 'W'ord.)
+ * :tw Treat the variable contents as multiple
+ * space-separated words.
+ * (Mnemonic: many small 'w'ords.)
+ * :[index] Select a single word from the value.
+ * :[start..end] Select multiple words from the value.
+ * :[*] or :[0] Select the entire value, as a single
+ * word. Equivalent to :tW.
+ * :[@] Select the entire value, as multiple
+ * words. Undoes the effect of :[*].
+ * Equivalent to :tw.
+ * :[#] Returns the number of words in the value.
+ *
+ * :?<true-value>:<false-value>
+ * If the variable evaluates to true, return
+ * true value, else return the second value.
+ * :lhs=rhs Like :S, but the rhs goes to the end of
+ * the invocation.
+ * :sh Treat the current value as a command
+ * to be run, new value is its output.
+ * The following added so we can handle ODE makefiles.
+ * :@<tmpvar>@<newval>@
+ * Assign a temporary local variable <tmpvar>
+ * to the current value of each word in turn
+ * and replace each word with the result of
+ * evaluating <newval>
+ * :D<newval> Use <newval> as value if variable defined
+ * :U<newval> Use <newval> as value if variable undefined
+ * :L Use the name of the variable as the value.
+ * :P Use the path of the node that has the same
+ * name as the variable as the value. This
+ * basically includes an implied :L so that
+ * the common method of refering to the path
+ * of your dependent 'x' in a rule is to use
+ * the form '${x:P}'.
+ * :!<cmd>! Run cmd much the same as :sh run's the
+ * current value of the variable.
+ * The ::= modifiers, actually assign a value to the variable.
+ * Their main purpose is in supporting modifiers of .for loop
+ * iterators and other obscure uses. They always expand to
+ * nothing. In a target rule that would otherwise expand to an
+ * empty line they can be preceded with @: to keep make happy.
+ * Eg.
+ *
+ * foo: .USE
+ * .for i in ${.TARGET} ${.TARGET:R}.gz
+ * @: ${t::=$i}
+ * @echo blah ${t:T}
+ * .endfor
+ *
+ * ::=<str> Assigns <str> as the new value of variable.
+ * ::?=<str> Assigns <str> as value of variable if
+ * it was not already set.
+ * ::+=<str> Appends <str> to variable.
+ * ::!=<cmd> Assigns output of <cmd> as the new value of
+ * variable.
+ */
+
+/* we now have some modifiers with long names */
+#define STRMOD_MATCH(s, want, n) \
+ (strncmp(s, want, n) == 0 && (s[n] == endc || s[n] == ':'))
+#define STRMOD_MATCHX(s, want, n) \
+ (strncmp(s, want, n) == 0 && (s[n] == endc || s[n] == ':' || s[n] == '='))
+#define CHARMOD_MATCH(c) (c == endc || c == ':')
+
+static char *
+ApplyModifiers(char *nstr, const char *tstr,
+ int startc, int endc,
+ Var *v, GNode *ctxt, int flags,
+ int *lengthPtr, void **freePtr)
+{
+ const char *start;
+ const char *cp; /* Secondary pointer into str (place marker
+ * for tstr) */
+ char *newStr; /* New value to return */
+ char *ep;
+ char termc; /* Character which terminated scan */
+ int cnt; /* Used to count brace pairs when variable in
+ * in parens or braces */
+ char delim;
+ int modifier; /* that we are processing */
+ Var_Parse_State parsestate; /* Flags passed to helper functions */
+ time_t utc; /* for VarStrftime */
+
+ delim = '\0';
+ parsestate.oneBigWord = FALSE;
+ parsestate.varSpace = ' '; /* word separator */
+
+ start = cp = tstr;
+
+ while (*tstr && *tstr != endc) {
+
+ if (*tstr == '$') {
+ /*
+ * We may have some complex modifiers in a variable.
+ */
+ void *freeIt;
+ char *rval;
+ int rlen;
+ int c;
+
+ rval = Var_Parse(tstr, ctxt, flags, &rlen, &freeIt);
+
+ /*
+ * If we have not parsed up to endc or ':',
+ * we are not interested.
+ */
+ if (rval != NULL && *rval &&
+ (c = tstr[rlen]) != '\0' &&
+ c != ':' &&
+ c != endc) {
+ free(freeIt);
+ goto apply_mods;
+ }
+
+ if (DEBUG(VAR)) {
+ fprintf(debug_file, "Got '%s' from '%.*s'%.*s\n",
+ rval, rlen, tstr, rlen, tstr + rlen);
+ }
+
+ tstr += rlen;
+
+ if (rval != NULL && *rval) {
+ int used;
+
+ nstr = ApplyModifiers(nstr, rval,
+ 0, 0, v, ctxt, flags, &used, freePtr);
+ if (nstr == var_Error
+ || (nstr == varNoError && (flags & VARF_UNDEFERR) == 0)
+ || strlen(rval) != (size_t) used) {
+ free(freeIt);
+ goto out; /* error already reported */
+ }
+ }
+ free(freeIt);
+ if (*tstr == ':')
+ tstr++;
+ else if (!*tstr && endc) {
+ Error("Unclosed variable specification after complex modifier (expecting '%c') for %s", endc, v->name);
+ goto out;
+ }
+ continue;
+ }
+ apply_mods:
+ if (DEBUG(VAR)) {
+ fprintf(debug_file, "Applying[%s] :%c to \"%s\"\n", v->name,
+ *tstr, nstr);
+ }
+ newStr = var_Error;
+ switch ((modifier = *tstr)) {
+ case ':':
+ {
+ if (tstr[1] == '=' ||
+ (tstr[2] == '=' &&
+ (tstr[1] == '!' || tstr[1] == '+' || tstr[1] == '?'))) {
+ /*
+ * "::=", "::!=", "::+=", or "::?="
+ */
+ GNode *v_ctxt; /* context where v belongs */
+ const char *emsg;
+ char *sv_name;
+ VarPattern pattern;
+ int how;
+ int vflags;
+
+ if (v->name[0] == 0)
+ goto bad_modifier;
+
+ v_ctxt = ctxt;
+ sv_name = NULL;
+ ++tstr;
+ if (v->flags & VAR_JUNK) {
+ /*
+ * We need to bmake_strdup() it incase
+ * VarGetPattern() recurses.
+ */
+ sv_name = v->name;
+ v->name = bmake_strdup(v->name);
+ } else if (ctxt != VAR_GLOBAL) {
+ Var *gv = VarFind(v->name, ctxt, 0);
+ if (gv == NULL)
+ v_ctxt = VAR_GLOBAL;
+ else
+ VarFreeEnv(gv, TRUE);
+ }
+
+ switch ((how = *tstr)) {
+ case '+':
+ case '?':
+ case '!':
+ cp = &tstr[2];
+ break;
+ default:
+ cp = ++tstr;
+ break;
+ }
+ delim = startc == PROPEN ? PRCLOSE : BRCLOSE;
+ pattern.flags = 0;
+
+ vflags = (flags & VARF_WANTRES) ? 0 : VAR_NOSUBST;
+ pattern.rhs = VarGetPattern(ctxt, &parsestate, flags,
+ &cp, delim, &vflags,
+ &pattern.rightLen,
+ NULL);
+ if (v->flags & VAR_JUNK) {
+ /* restore original name */
+ free(v->name);
+ v->name = sv_name;
+ }
+ if (pattern.rhs == NULL)
+ goto cleanup;
+
+ termc = *--cp;
+ delim = '\0';
+
+ if (flags & VARF_WANTRES) {
+ switch (how) {
+ case '+':
+ Var_Append(v->name, pattern.rhs, v_ctxt);
+ break;
+ case '!':
+ newStr = Cmd_Exec(pattern.rhs, &emsg);
+ if (emsg)
+ Error(emsg, nstr);
+ else
+ Var_Set(v->name, newStr, v_ctxt, 0);
+ free(newStr);
+ break;
+ case '?':
+ if ((v->flags & VAR_JUNK) == 0)
+ break;
+ /* FALLTHROUGH */
+ default:
+ Var_Set(v->name, pattern.rhs, v_ctxt, 0);
+ break;
+ }
+ }
+ free(UNCONST(pattern.rhs));
+ newStr = varNoError;
+ break;
+ }
+ goto default_case; /* "::<unrecognised>" */
+ }
+ case '@':
+ {
+ VarLoop_t loop;
+ int vflags = VAR_NOSUBST;
+
+ cp = ++tstr;
+ delim = '@';
+ if ((loop.tvar = VarGetPattern(ctxt, &parsestate, flags,
+ &cp, delim,
+ &vflags, &loop.tvarLen,
+ NULL)) == NULL)
+ goto cleanup;
+
+ if ((loop.str = VarGetPattern(ctxt, &parsestate, flags,
+ &cp, delim,
+ &vflags, &loop.strLen,
+ NULL)) == NULL)
+ goto cleanup;
+
+ termc = *cp;
+ delim = '\0';
+
+ loop.errnum = flags & VARF_UNDEFERR;
+ loop.ctxt = ctxt;
+ newStr = VarModify(ctxt, &parsestate, nstr, VarLoopExpand,
+ &loop);
+ Var_Delete(loop.tvar, ctxt);
+ free(loop.tvar);
+ free(loop.str);
+ break;
+ }
+ case '_': /* remember current value */
+ cp = tstr + 1; /* make sure it is set */
+ if (STRMOD_MATCHX(tstr, "_", 1)) {
+ if (tstr[1] == '=') {
+ char *np;
+ int n;
+
+ cp++;
+ n = strcspn(cp, ":)}");
+ np = bmake_strndup(cp, n+1);
+ np[n] = '\0';
+ cp = tstr + 2 + n;
+ Var_Set(np, nstr, ctxt, 0);
+ free(np);
+ } else {
+ Var_Set("_", nstr, ctxt, 0);
+ }
+ newStr = nstr;
+ termc = *cp;
+ break;
+ }
+ goto default_case;
+ case 'D':
+ case 'U':
+ {
+ Buffer buf; /* Buffer for patterns */
+ int nflags;
+
+ if (flags & VARF_WANTRES) {
+ int wantres;
+ if (*tstr == 'U')
+ wantres = ((v->flags & VAR_JUNK) != 0);
+ else
+ wantres = ((v->flags & VAR_JUNK) == 0);
+ nflags = flags & ~VARF_WANTRES;
+ if (wantres)
+ nflags |= VARF_WANTRES;
+ } else
+ nflags = flags;
+ /*
+ * Pass through tstr looking for 1) escaped delimiters,
+ * '$'s and backslashes (place the escaped character in
+ * uninterpreted) and 2) unescaped $'s that aren't before
+ * the delimiter (expand the variable substitution).
+ * The result is left in the Buffer buf.
+ */
+ Buf_Init(&buf, 0);
+ for (cp = tstr + 1;
+ *cp != endc && *cp != ':' && *cp != '\0';
+ cp++) {
+ if ((*cp == '\\') &&
+ ((cp[1] == ':') ||
+ (cp[1] == '$') ||
+ (cp[1] == endc) ||
+ (cp[1] == '\\')))
+ {
+ Buf_AddByte(&buf, cp[1]);
+ cp++;
+ } else if (*cp == '$') {
+ /*
+ * If unescaped dollar sign, assume it's a
+ * variable substitution and recurse.
+ */
+ char *cp2;
+ int len;
+ void *freeIt;
+
+ cp2 = Var_Parse(cp, ctxt, nflags, &len, &freeIt);
+ Buf_AddBytes(&buf, strlen(cp2), cp2);
+ free(freeIt);
+ cp += len - 1;
+ } else {
+ Buf_AddByte(&buf, *cp);
+ }
+ }
+
+ termc = *cp;
+
+ if ((v->flags & VAR_JUNK) != 0)
+ v->flags |= VAR_KEEP;
+ if (nflags & VARF_WANTRES) {
+ newStr = Buf_Destroy(&buf, FALSE);
+ } else {
+ newStr = nstr;
+ Buf_Destroy(&buf, TRUE);
+ }
+ break;
+ }
+ case 'L':
+ {
+ if ((v->flags & VAR_JUNK) != 0)
+ v->flags |= VAR_KEEP;
+ newStr = bmake_strdup(v->name);
+ cp = ++tstr;
+ termc = *tstr;
+ break;
+ }
+ case 'P':
+ {
+ GNode *gn;
+
+ if ((v->flags & VAR_JUNK) != 0)
+ v->flags |= VAR_KEEP;
+ gn = Targ_FindNode(v->name, TARG_NOCREATE);
+ if (gn == NULL || gn->type & OP_NOPATH) {
+ newStr = NULL;
+ } else if (gn->path) {
+ newStr = bmake_strdup(gn->path);
+ } else {
+ newStr = Dir_FindFile(v->name, Suff_FindPath(gn));
+ }
+ if (!newStr) {
+ newStr = bmake_strdup(v->name);
+ }
+ cp = ++tstr;
+ termc = *tstr;
+ break;
+ }
+ case '!':
+ {
+ const char *emsg;
+ VarPattern pattern;
+ pattern.flags = 0;
+
+ delim = '!';
+ emsg = NULL;
+ cp = ++tstr;
+ if ((pattern.rhs = VarGetPattern(ctxt, &parsestate, flags,
+ &cp, delim,
+ NULL, &pattern.rightLen,
+ NULL)) == NULL)
+ goto cleanup;
+ if (flags & VARF_WANTRES)
+ newStr = Cmd_Exec(pattern.rhs, &emsg);
+ else
+ newStr = varNoError;
+ free(UNCONST(pattern.rhs));
+ if (emsg)
+ Error(emsg, nstr);
+ termc = *cp;
+ delim = '\0';
+ if (v->flags & VAR_JUNK) {
+ v->flags |= VAR_KEEP;
+ }
+ break;
+ }
+ case '[':
+ {
+ /*
+ * Look for the closing ']', recursively
+ * expanding any embedded variables.
+ *
+ * estr is a pointer to the expanded result,
+ * which we must free().
+ */
+ char *estr;
+
+ cp = tstr+1; /* point to char after '[' */
+ delim = ']'; /* look for closing ']' */
+ estr = VarGetPattern(ctxt, &parsestate,
+ flags, &cp, delim,
+ NULL, NULL, NULL);
+ if (estr == NULL)
+ goto cleanup; /* report missing ']' */
+ /* now cp points just after the closing ']' */
+ delim = '\0';
+ if (cp[0] != ':' && cp[0] != endc) {
+ /* Found junk after ']' */
+ free(estr);
+ goto bad_modifier;
+ }
+ if (estr[0] == '\0') {
+ /* Found empty square brackets in ":[]". */
+ free(estr);
+ goto bad_modifier;
+ } else if (estr[0] == '#' && estr[1] == '\0') {
+ /* Found ":[#]" */
+
+ /*
+ * We will need enough space for the decimal
+ * representation of an int. We calculate the
+ * space needed for the octal representation,
+ * and add enough slop to cope with a '-' sign
+ * (which should never be needed) and a '\0'
+ * string terminator.
+ */
+ int newStrSize =
+ (sizeof(int) * CHAR_BIT + 2) / 3 + 2;
+
+ newStr = bmake_malloc(newStrSize);
+ if (parsestate.oneBigWord) {
+ strncpy(newStr, "1", newStrSize);
+ } else {
+ /* XXX: brk_string() is a rather expensive
+ * way of counting words. */
+ char **av;
+ char *as;
+ int ac;
+
+ av = brk_string(nstr, &ac, FALSE, &as);
+ snprintf(newStr, newStrSize, "%d", ac);
+ free(as);
+ free(av);
+ }
+ termc = *cp;
+ free(estr);
+ break;
+ } else if (estr[0] == '*' && estr[1] == '\0') {
+ /* Found ":[*]" */
+ parsestate.oneBigWord = TRUE;
+ newStr = nstr;
+ termc = *cp;
+ free(estr);
+ break;
+ } else if (estr[0] == '@' && estr[1] == '\0') {
+ /* Found ":[@]" */
+ parsestate.oneBigWord = FALSE;
+ newStr = nstr;
+ termc = *cp;
+ free(estr);
+ break;
+ } else {
+ /*
+ * We expect estr to contain a single
+ * integer for :[N], or two integers
+ * separated by ".." for :[start..end].
+ */
+ VarSelectWords_t seldata = { 0, 0 };
+
+ seldata.start = strtol(estr, &ep, 0);
+ if (ep == estr) {
+ /* Found junk instead of a number */
+ free(estr);
+ goto bad_modifier;
+ } else if (ep[0] == '\0') {
+ /* Found only one integer in :[N] */
+ seldata.end = seldata.start;
+ } else if (ep[0] == '.' && ep[1] == '.' &&
+ ep[2] != '\0') {
+ /* Expecting another integer after ".." */
+ ep += 2;
+ seldata.end = strtol(ep, &ep, 0);
+ if (ep[0] != '\0') {
+ /* Found junk after ".." */
+ free(estr);
+ goto bad_modifier;
+ }
+ } else {
+ /* Found junk instead of ".." */
+ free(estr);
+ goto bad_modifier;
+ }
+ /*
+ * Now seldata is properly filled in,
+ * but we still have to check for 0 as
+ * a special case.
+ */
+ if (seldata.start == 0 && seldata.end == 0) {
+ /* ":[0]" or perhaps ":[0..0]" */
+ parsestate.oneBigWord = TRUE;
+ newStr = nstr;
+ termc = *cp;
+ free(estr);
+ break;
+ } else if (seldata.start == 0 ||
+ seldata.end == 0) {
+ /* ":[0..N]" or ":[N..0]" */
+ free(estr);
+ goto bad_modifier;
+ }
+ /*
+ * Normal case: select the words
+ * described by seldata.
+ */
+ newStr = VarSelectWords(ctxt, &parsestate,
+ nstr, &seldata);
+
+ termc = *cp;
+ free(estr);
+ break;
+ }
+
+ }
+ case 'g':
+ cp = tstr + 1; /* make sure it is set */
+ if (STRMOD_MATCHX(tstr, "gmtime", 6)) {
+ if (tstr[6] == '=') {
+ utc = strtoul(&tstr[7], &ep, 10);
+ cp = ep;
+ } else {
+ utc = 0;
+ cp = tstr + 6;
+ }
+ newStr = VarStrftime(nstr, 1, utc);
+ termc = *cp;
+ } else {
+ goto default_case;
+ }
+ break;
+ case 'h':
+ cp = tstr + 1; /* make sure it is set */
+ if (STRMOD_MATCH(tstr, "hash", 4)) {
+ newStr = VarHash(nstr);
+ cp = tstr + 4;
+ termc = *cp;
+ } else {
+ goto default_case;
+ }
+ break;
+ case 'l':
+ cp = tstr + 1; /* make sure it is set */
+ if (STRMOD_MATCHX(tstr, "localtime", 9)) {
+ if (tstr[9] == '=') {
+ utc = strtoul(&tstr[10], &ep, 10);
+ cp = ep;
+ } else {
+ utc = 0;
+ cp = tstr + 9;
+ }
+ newStr = VarStrftime(nstr, 0, utc);
+ termc = *cp;
+ } else {
+ goto default_case;
+ }
+ break;
+ case 't':
+ {
+ cp = tstr + 1; /* make sure it is set */
+ if (tstr[1] != endc && tstr[1] != ':') {
+ if (tstr[1] == 's') {
+ /*
+ * Use the char (if any) at tstr[2]
+ * as the word separator.
+ */
+ VarPattern pattern;
+
+ if (tstr[2] != endc &&
+ (tstr[3] == endc || tstr[3] == ':')) {
+ /* ":ts<unrecognised><endc>" or
+ * ":ts<unrecognised>:" */
+ parsestate.varSpace = tstr[2];
+ cp = tstr + 3;
+ } else if (tstr[2] == endc || tstr[2] == ':') {
+ /* ":ts<endc>" or ":ts:" */
+ parsestate.varSpace = 0; /* no separator */
+ cp = tstr + 2;
+ } else if (tstr[2] == '\\') {
+ const char *xp = &tstr[3];
+ int base = 8; /* assume octal */
+
+ switch (tstr[3]) {
+ case 'n':
+ parsestate.varSpace = '\n';
+ cp = tstr + 4;
+ break;
+ case 't':
+ parsestate.varSpace = '\t';
+ cp = tstr + 4;
+ break;
+ case 'x':
+ base = 16;
+ xp++;
+ goto get_numeric;
+ case '0':
+ base = 0;
+ goto get_numeric;
+ default:
+ if (isdigit((unsigned char)tstr[3])) {
+
+ get_numeric:
+ parsestate.varSpace =
+ strtoul(xp, &ep, base);
+ if (*ep != ':' && *ep != endc)
+ goto bad_modifier;
+ cp = ep;
+ } else {
+ /*
+ * ":ts<backslash><unrecognised>".
+ */
+ goto bad_modifier;
+ }
+ break;
+ }
+ } else {
+ /*
+ * Found ":ts<unrecognised><unrecognised>".
+ */
+ goto bad_modifier;
+ }
+
+ termc = *cp;
+
+ /*
+ * We cannot be certain that VarModify
+ * will be used - even if there is a
+ * subsequent modifier, so do a no-op
+ * VarSubstitute now to for str to be
+ * re-expanded without the spaces.
+ */
+ pattern.flags = VAR_SUB_ONE;
+ pattern.lhs = pattern.rhs = "\032";
+ pattern.leftLen = pattern.rightLen = 1;
+
+ newStr = VarModify(ctxt, &parsestate, nstr,
+ VarSubstitute,
+ &pattern);
+ } else if (tstr[2] == endc || tstr[2] == ':') {
+ /*
+ * Check for two-character options:
+ * ":tu", ":tl"
+ */
+ if (tstr[1] == 'A') { /* absolute path */
+ newStr = VarModify(ctxt, &parsestate, nstr,
+ VarRealpath, NULL);
+ cp = tstr + 2;
+ termc = *cp;
+ } else if (tstr[1] == 'u') {
+ char *dp = bmake_strdup(nstr);
+ for (newStr = dp; *dp; dp++)
+ *dp = toupper((unsigned char)*dp);
+ cp = tstr + 2;
+ termc = *cp;
+ } else if (tstr[1] == 'l') {
+ char *dp = bmake_strdup(nstr);
+ for (newStr = dp; *dp; dp++)
+ *dp = tolower((unsigned char)*dp);
+ cp = tstr + 2;
+ termc = *cp;
+ } else if (tstr[1] == 'W' || tstr[1] == 'w') {
+ parsestate.oneBigWord = (tstr[1] == 'W');
+ newStr = nstr;
+ cp = tstr + 2;
+ termc = *cp;
+ } else {
+ /* Found ":t<unrecognised>:" or
+ * ":t<unrecognised><endc>". */
+ goto bad_modifier;
+ }
+ } else {
+ /*
+ * Found ":t<unrecognised><unrecognised>".
+ */
+ goto bad_modifier;
+ }
+ } else {
+ /*
+ * Found ":t<endc>" or ":t:".
+ */
+ goto bad_modifier;
+ }
+ break;
+ }
+ case 'N':
+ case 'M':
+ {
+ char *pattern;
+ const char *endpat; /* points just after end of pattern */
+ char *cp2;
+ Boolean copy; /* pattern should be, or has been, copied */
+ Boolean needSubst;
+ int nest;
+
+ copy = FALSE;
+ needSubst = FALSE;
+ nest = 1;
+ /*
+ * In the loop below, ignore ':' unless we are at
+ * (or back to) the original brace level.
+ * XXX This will likely not work right if $() and ${}
+ * are intermixed.
+ */
+ for (cp = tstr + 1;
+ *cp != '\0' && !(*cp == ':' && nest == 1);
+ cp++)
+ {
+ if (*cp == '\\' &&
+ (cp[1] == ':' ||
+ cp[1] == endc || cp[1] == startc)) {
+ if (!needSubst) {
+ copy = TRUE;
+ }
+ cp++;
+ continue;
+ }
+ if (*cp == '$') {
+ needSubst = TRUE;
+ }
+ if (*cp == '(' || *cp == '{')
+ ++nest;
+ if (*cp == ')' || *cp == '}') {
+ --nest;
+ if (nest == 0)
+ break;
+ }
+ }
+ termc = *cp;
+ endpat = cp;
+ if (copy) {
+ /*
+ * Need to compress the \:'s out of the pattern, so
+ * allocate enough room to hold the uncompressed
+ * pattern (note that cp started at tstr+1, so
+ * cp - tstr takes the null byte into account) and
+ * compress the pattern into the space.
+ */
+ pattern = bmake_malloc(cp - tstr);
+ for (cp2 = pattern, cp = tstr + 1;
+ cp < endpat;
+ cp++, cp2++)
+ {
+ if ((*cp == '\\') && (cp+1 < endpat) &&
+ (cp[1] == ':' || cp[1] == endc)) {
+ cp++;
+ }
+ *cp2 = *cp;
+ }
+ *cp2 = '\0';
+ endpat = cp2;
+ } else {
+ /*
+ * Either Var_Subst or VarModify will need a
+ * nul-terminated string soon, so construct one now.
+ */
+ pattern = bmake_strndup(tstr+1, endpat - (tstr + 1));
+ }
+ if (needSubst) {
+ /*
+ * pattern contains embedded '$', so use Var_Subst to
+ * expand it.
+ */
+ cp2 = pattern;
+ pattern = Var_Subst(NULL, cp2, ctxt, flags | VARF_WANTRES);
+ free(cp2);
+ }
+ if (DEBUG(VAR))
+ fprintf(debug_file, "Pattern[%s] for [%s] is [%s]\n",
+ v->name, nstr, pattern);
+ if (*tstr == 'M') {
+ newStr = VarModify(ctxt, &parsestate, nstr, VarMatch,
+ pattern);
+ } else {
+ newStr = VarModify(ctxt, &parsestate, nstr, VarNoMatch,
+ pattern);
+ }
+ free(pattern);
+ break;
+ }
+ case 'S':
+ {
+ VarPattern pattern;
+ Var_Parse_State tmpparsestate;
+
+ pattern.flags = 0;
+ tmpparsestate = parsestate;
+ delim = tstr[1];
+ tstr += 2;
+
+ /*
+ * If pattern begins with '^', it is anchored to the
+ * start of the word -- skip over it and flag pattern.
+ */
+ if (*tstr == '^') {
+ pattern.flags |= VAR_MATCH_START;
+ tstr += 1;
+ }
+
+ cp = tstr;
+ if ((pattern.lhs = VarGetPattern(ctxt, &parsestate, flags,
+ &cp, delim,
+ &pattern.flags,
+ &pattern.leftLen,
+ NULL)) == NULL)
+ goto cleanup;
+
+ if ((pattern.rhs = VarGetPattern(ctxt, &parsestate, flags,
+ &cp, delim, NULL,
+ &pattern.rightLen,
+ &pattern)) == NULL)
+ goto cleanup;
+
+ /*
+ * Check for global substitution. If 'g' after the final
+ * delimiter, substitution is global and is marked that
+ * way.
+ */
+ for (;; cp++) {
+ switch (*cp) {
+ case 'g':
+ pattern.flags |= VAR_SUB_GLOBAL;
+ continue;
+ case '1':
+ pattern.flags |= VAR_SUB_ONE;
+ continue;
+ case 'W':
+ tmpparsestate.oneBigWord = TRUE;
+ continue;
+ }
+ break;
+ }
+
+ termc = *cp;
+ newStr = VarModify(ctxt, &tmpparsestate, nstr,
+ VarSubstitute,
+ &pattern);
+
+ /*
+ * Free the two strings.
+ */
+ free(UNCONST(pattern.lhs));
+ free(UNCONST(pattern.rhs));
+ delim = '\0';
+ break;
+ }
+ case '?':
+ {
+ VarPattern pattern;
+ Boolean value;
+ int cond_rc;
+ int lhs_flags, rhs_flags;
+
+ /* find ':', and then substitute accordingly */
+ if (flags & VARF_WANTRES) {
+ cond_rc = Cond_EvalExpression(NULL, v->name, &value, 0, FALSE);
+ if (cond_rc == COND_INVALID) {
+ lhs_flags = rhs_flags = VAR_NOSUBST;
+ } else if (value) {
+ lhs_flags = 0;
+ rhs_flags = VAR_NOSUBST;
+ } else {
+ lhs_flags = VAR_NOSUBST;
+ rhs_flags = 0;
+ }
+ } else {
+ /* we are just consuming and discarding */
+ cond_rc = value = 0;
+ lhs_flags = rhs_flags = VAR_NOSUBST;
+ }
+ pattern.flags = 0;
+
+ cp = ++tstr;
+ delim = ':';
+ if ((pattern.lhs = VarGetPattern(ctxt, &parsestate, flags,
+ &cp, delim, &lhs_flags,
+ &pattern.leftLen,
+ NULL)) == NULL)
+ goto cleanup;
+
+ /* BROPEN or PROPEN */
+ delim = endc;
+ if ((pattern.rhs = VarGetPattern(ctxt, &parsestate, flags,
+ &cp, delim, &rhs_flags,
+ &pattern.rightLen,
+ NULL)) == NULL)
+ goto cleanup;
+
+ termc = *--cp;
+ delim = '\0';
+ if (cond_rc == COND_INVALID) {
+ Error("Bad conditional expression `%s' in %s?%s:%s",
+ v->name, v->name, pattern.lhs, pattern.rhs);
+ goto cleanup;
+ }
+
+ if (value) {
+ newStr = UNCONST(pattern.lhs);
+ free(UNCONST(pattern.rhs));
+ } else {
+ newStr = UNCONST(pattern.rhs);
+ free(UNCONST(pattern.lhs));
+ }
+ if (v->flags & VAR_JUNK) {
+ v->flags |= VAR_KEEP;
+ }
+ break;
+ }
+#ifndef NO_REGEX
+ case 'C':
+ {
+ VarREPattern pattern;
+ char *re;
+ int error;
+ Var_Parse_State tmpparsestate;
+
+ pattern.flags = 0;
+ tmpparsestate = parsestate;
+ delim = tstr[1];
+ tstr += 2;
+
+ cp = tstr;
+
+ if ((re = VarGetPattern(ctxt, &parsestate, flags, &cp, delim,
+ NULL, NULL, NULL)) == NULL)
+ goto cleanup;
+
+ if ((pattern.replace = VarGetPattern(ctxt, &parsestate,
+ flags, &cp, delim, NULL,
+ NULL, NULL)) == NULL){
+ free(re);
+ goto cleanup;
+ }
+
+ for (;; cp++) {
+ switch (*cp) {
+ case 'g':
+ pattern.flags |= VAR_SUB_GLOBAL;
+ continue;
+ case '1':
+ pattern.flags |= VAR_SUB_ONE;
+ continue;
+ case 'W':
+ tmpparsestate.oneBigWord = TRUE;
+ continue;
+ }
+ break;
+ }
+
+ termc = *cp;
+
+ error = regcomp(&pattern.re, re, REG_EXTENDED);
+ free(re);
+ if (error) {
+ *lengthPtr = cp - start + 1;
+ VarREError(error, &pattern.re, "RE substitution error");
+ free(pattern.replace);
+ goto cleanup;
+ }
+
+ pattern.nsub = pattern.re.re_nsub + 1;
+ if (pattern.nsub < 1)
+ pattern.nsub = 1;
+ if (pattern.nsub > 10)
+ pattern.nsub = 10;
+ pattern.matches = bmake_malloc(pattern.nsub *
+ sizeof(regmatch_t));
+ newStr = VarModify(ctxt, &tmpparsestate, nstr,
+ VarRESubstitute,
+ &pattern);
+ regfree(&pattern.re);
+ free(pattern.replace);
+ free(pattern.matches);
+ delim = '\0';
+ break;
+ }
+#endif
+ case 'q':
+ case 'Q':
+ if (tstr[1] == endc || tstr[1] == ':') {
+ newStr = VarQuote(nstr, modifier == 'q');
+ cp = tstr + 1;
+ termc = *cp;
+ break;
+ }
+ goto default_case;
+ case 'T':
+ if (tstr[1] == endc || tstr[1] == ':') {
+ newStr = VarModify(ctxt, &parsestate, nstr, VarTail,
+ NULL);
+ cp = tstr + 1;
+ termc = *cp;
+ break;
+ }
+ goto default_case;
+ case 'H':
+ if (tstr[1] == endc || tstr[1] == ':') {
+ newStr = VarModify(ctxt, &parsestate, nstr, VarHead,
+ NULL);
+ cp = tstr + 1;
+ termc = *cp;
+ break;
+ }
+ goto default_case;
+ case 'E':
+ if (tstr[1] == endc || tstr[1] == ':') {
+ newStr = VarModify(ctxt, &parsestate, nstr, VarSuffix,
+ NULL);
+ cp = tstr + 1;
+ termc = *cp;
+ break;
+ }
+ goto default_case;
+ case 'R':
+ if (tstr[1] == endc || tstr[1] == ':') {
+ newStr = VarModify(ctxt, &parsestate, nstr, VarRoot,
+ NULL);
+ cp = tstr + 1;
+ termc = *cp;
+ break;
+ }
+ goto default_case;
+ case 'r':
+ cp = tstr + 1; /* make sure it is set */
+ if (STRMOD_MATCHX(tstr, "range", 5)) {
+ int n;
+
+ if (tstr[5] == '=') {
+ n = strtoul(&tstr[6], &ep, 10);
+ cp = ep;
+ } else {
+ n = 0;
+ cp = tstr + 5;
+ }
+ newStr = VarRange(nstr, n);
+ termc = *cp;
+ break;
+ }
+ goto default_case;
+ case 'O':
+ {
+ char otype;
+
+ cp = tstr + 1; /* skip to the rest in any case */
+ if (tstr[1] == endc || tstr[1] == ':') {
+ otype = 's';
+ termc = *cp;
+ } else if ( (tstr[1] == 'x') &&
+ (tstr[2] == endc || tstr[2] == ':') ) {
+ otype = tstr[1];
+ cp = tstr + 2;
+ termc = *cp;
+ } else {
+ goto bad_modifier;
+ }
+ newStr = VarOrder(nstr, otype);
+ break;
+ }
+ case 'u':
+ if (tstr[1] == endc || tstr[1] == ':') {
+ newStr = VarUniq(nstr);
+ cp = tstr + 1;
+ termc = *cp;
+ break;
+ }
+ goto default_case;
+#ifdef SUNSHCMD
+ case 's':
+ if (tstr[1] == 'h' && (tstr[2] == endc || tstr[2] == ':')) {
+ const char *emsg;
+ if (flags & VARF_WANTRES) {
+ newStr = Cmd_Exec(nstr, &emsg);
+ if (emsg)
+ Error(emsg, nstr);
+ } else
+ newStr = varNoError;
+ cp = tstr + 2;
+ termc = *cp;
+ break;
+ }
+ goto default_case;
+#endif
+ default:
+ default_case:
+ {
+#ifdef SYSVVARSUB
+ /*
+ * This can either be a bogus modifier or a System-V
+ * substitution command.
+ */
+ VarPattern pattern;
+ Boolean eqFound;
+
+ pattern.flags = 0;
+ eqFound = FALSE;
+ /*
+ * First we make a pass through the string trying
+ * to verify it is a SYSV-make-style translation:
+ * it must be: <string1>=<string2>)
+ */
+ cp = tstr;
+ cnt = 1;
+ while (*cp != '\0' && cnt) {
+ if (*cp == '=') {
+ eqFound = TRUE;
+ /* continue looking for endc */
+ }
+ else if (*cp == endc)
+ cnt--;
+ else if (*cp == startc)
+ cnt++;
+ if (cnt)
+ cp++;
+ }
+ if (*cp == endc && eqFound) {
+
+ /*
+ * Now we break this sucker into the lhs and
+ * rhs. We must null terminate them of course.
+ */
+ delim='=';
+ cp = tstr;
+ if ((pattern.lhs = VarGetPattern(ctxt, &parsestate,
+ flags, &cp, delim, &pattern.flags,
+ &pattern.leftLen, NULL)) == NULL)
+ goto cleanup;
+ delim = endc;
+ if ((pattern.rhs = VarGetPattern(ctxt, &parsestate,
+ flags, &cp, delim, NULL, &pattern.rightLen,
+ &pattern)) == NULL)
+ goto cleanup;
+
+ /*
+ * SYSV modifications happen through the whole
+ * string. Note the pattern is anchored at the end.
+ */
+ termc = *--cp;
+ delim = '\0';
+ if (pattern.leftLen == 0 && *nstr == '\0') {
+ newStr = nstr; /* special case */
+ } else {
+ newStr = VarModify(ctxt, &parsestate, nstr,
+ VarSYSVMatch,
+ &pattern);
+ }
+ free(UNCONST(pattern.lhs));
+ free(UNCONST(pattern.rhs));
+ } else
+#endif
+ {
+ Error("Unknown modifier '%c'", *tstr);
+ for (cp = tstr+1;
+ *cp != ':' && *cp != endc && *cp != '\0';
+ cp++)
+ continue;
+ termc = *cp;
+ newStr = var_Error;
+ }
+ }
+ }
+ if (DEBUG(VAR)) {
+ fprintf(debug_file, "Result[%s] of :%c is \"%s\"\n",
+ v->name, modifier, newStr);
+ }
+
+ if (newStr != nstr) {
+ if (*freePtr) {
+ free(nstr);
+ *freePtr = NULL;
+ }
+ nstr = newStr;
+ if (nstr != var_Error && nstr != varNoError) {
+ *freePtr = nstr;
+ }
+ }
+ if (termc == '\0' && endc != '\0') {
+ Error("Unclosed variable specification (expecting '%c') for \"%s\" (value \"%s\") modifier %c", endc, v->name, nstr, modifier);
+ } else if (termc == ':') {
+ cp++;
+ }
+ tstr = cp;
+ }
+ out:
+ *lengthPtr = tstr - start;
+ return (nstr);
+
+ bad_modifier:
+ /* "{(" */
+ Error("Bad modifier `:%.*s' for %s", (int)strcspn(tstr, ":)}"), tstr,
+ v->name);
+
+ cleanup:
+ *lengthPtr = cp - start;
+ if (delim != '\0')
+ Error("Unclosed substitution for %s (%c missing)",
+ v->name, delim);
+ free(*freePtr);
+ *freePtr = NULL;
+ return (var_Error);
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * Var_Parse --
+ * Given the start of a variable invocation, extract the variable
+ * name and find its value, then modify it according to the
+ * specification.
+ *
+ * Input:
+ * str The string to parse
+ * ctxt The context for the variable
+ * flags VARF_UNDEFERR if undefineds are an error
+ * VARF_WANTRES if we actually want the result
+ * VARF_ASSIGN if we are in a := assignment
+ * lengthPtr OUT: The length of the specification
+ * freePtr OUT: Non-NULL if caller should free *freePtr
+ *
+ * Results:
+ * The (possibly-modified) value of the variable or var_Error if the
+ * specification is invalid. The length of the specification is
+ * placed in *lengthPtr (for invalid specifications, this is just
+ * 2...?).
+ * If *freePtr is non-NULL then it's a pointer that the caller
+ * should pass to free() to free memory used by the result.
+ *
+ * Side Effects:
+ * None.
+ *
+ *-----------------------------------------------------------------------
+ */
+/* coverity[+alloc : arg-*4] */
+char *
+Var_Parse(const char *str, GNode *ctxt, int flags,
+ int *lengthPtr, void **freePtr)
+{
+ const char *tstr; /* Pointer into str */
+ Var *v; /* Variable in invocation */
+ Boolean haveModifier;/* TRUE if have modifiers for the variable */
+ char endc; /* Ending character when variable in parens
+ * or braces */
+ char startc; /* Starting character when variable in parens
+ * or braces */
+ int vlen; /* Length of variable name */
+ const char *start; /* Points to original start of str */
+ char *nstr; /* New string, used during expansion */
+ Boolean dynamic; /* TRUE if the variable is local and we're
+ * expanding it in a non-local context. This
+ * is done to support dynamic sources. The
+ * result is just the invocation, unaltered */
+ const char *extramodifiers; /* extra modifiers to apply first */
+ char name[2];
+
+ *freePtr = NULL;
+ extramodifiers = NULL;
+ dynamic = FALSE;
+ start = str;
+
+ startc = str[1];
+ if (startc != PROPEN && startc != BROPEN) {
+ /*
+ * If it's not bounded by braces of some sort, life is much simpler.
+ * We just need to check for the first character and return the
+ * value if it exists.
+ */
+
+ /* Error out some really stupid names */
+ if (startc == '\0' || strchr(")}:$", startc)) {
+ *lengthPtr = 1;
+ return var_Error;
+ }
+ name[0] = startc;
+ name[1] = '\0';
+
+ v = VarFind(name, ctxt, FIND_ENV | FIND_GLOBAL | FIND_CMD);
+ if (v == NULL) {
+ *lengthPtr = 2;
+
+ if ((ctxt == VAR_CMD) || (ctxt == VAR_GLOBAL)) {
+ /*
+ * If substituting a local variable in a non-local context,
+ * assume it's for dynamic source stuff. We have to handle
+ * this specially and return the longhand for the variable
+ * with the dollar sign escaped so it makes it back to the
+ * caller. Only four of the local variables are treated
+ * specially as they are the only four that will be set
+ * when dynamic sources are expanded.
+ */
+ switch (str[1]) {
+ case '@':
+ return UNCONST("$(.TARGET)");
+ case '%':
+ return UNCONST("$(.MEMBER)");
+ case '*':
+ return UNCONST("$(.PREFIX)");
+ case '!':
+ return UNCONST("$(.ARCHIVE)");
+ }
+ }
+ /*
+ * Error
+ */
+ return (flags & VARF_UNDEFERR) ? var_Error : varNoError;
+ } else {
+ haveModifier = FALSE;
+ tstr = &str[1];
+ endc = str[1];
+ }
+ } else {
+ Buffer buf; /* Holds the variable name */
+ int depth = 1;
+
+ endc = startc == PROPEN ? PRCLOSE : BRCLOSE;
+ Buf_Init(&buf, 0);
+
+ /*
+ * Skip to the end character or a colon, whichever comes first.
+ */
+ for (tstr = str + 2; *tstr != '\0'; tstr++)
+ {
+ /*
+ * Track depth so we can spot parse errors.
+ */
+ if (*tstr == startc) {
+ depth++;
+ }
+ if (*tstr == endc) {
+ if (--depth == 0)
+ break;
+ }
+ if (depth == 1 && *tstr == ':') {
+ break;
+ }
+ /*
+ * A variable inside a variable, expand
+ */
+ if (*tstr == '$') {
+ int rlen;
+ void *freeIt;
+ char *rval = Var_Parse(tstr, ctxt, flags, &rlen, &freeIt);
+ if (rval != NULL) {
+ Buf_AddBytes(&buf, strlen(rval), rval);
+ }
+ free(freeIt);
+ tstr += rlen - 1;
+ }
+ else
+ Buf_AddByte(&buf, *tstr);
+ }
+ if (*tstr == ':') {
+ haveModifier = TRUE;
+ } else if (*tstr == endc) {
+ haveModifier = FALSE;
+ } else {
+ /*
+ * If we never did find the end character, return NULL
+ * right now, setting the length to be the distance to
+ * the end of the string, since that's what make does.
+ */
+ *lengthPtr = tstr - str;
+ Buf_Destroy(&buf, TRUE);
+ return (var_Error);
+ }
+ str = Buf_GetAll(&buf, &vlen);
+
+ /*
+ * At this point, str points into newly allocated memory from
+ * buf, containing only the name of the variable.
+ *
+ * start and tstr point into the const string that was pointed
+ * to by the original value of the str parameter. start points
+ * to the '$' at the beginning of the string, while tstr points
+ * to the char just after the end of the variable name -- this
+ * will be '\0', ':', PRCLOSE, or BRCLOSE.
+ */
+
+ v = VarFind(str, ctxt, FIND_ENV | FIND_GLOBAL | FIND_CMD);
+ /*
+ * Check also for bogus D and F forms of local variables since we're
+ * in a local context and the name is the right length.
+ */
+ if ((v == NULL) && (ctxt != VAR_CMD) && (ctxt != VAR_GLOBAL) &&
+ (vlen == 2) && (str[1] == 'F' || str[1] == 'D') &&
+ strchr("@%?*!<>", str[0]) != NULL) {
+ /*
+ * Well, it's local -- go look for it.
+ */
+ name[0] = *str;
+ name[1] = '\0';
+ v = VarFind(name, ctxt, 0);
+
+ if (v != NULL) {
+ if (str[1] == 'D') {
+ extramodifiers = "H:";
+ }
+ else { /* F */
+ extramodifiers = "T:";
+ }
+ }
+ }
+
+ if (v == NULL) {
+ if (((vlen == 1) ||
+ (((vlen == 2) && (str[1] == 'F' || str[1] == 'D')))) &&
+ ((ctxt == VAR_CMD) || (ctxt == VAR_GLOBAL)))
+ {
+ /*
+ * If substituting a local variable in a non-local context,
+ * assume it's for dynamic source stuff. We have to handle
+ * this specially and return the longhand for the variable
+ * with the dollar sign escaped so it makes it back to the
+ * caller. Only four of the local variables are treated
+ * specially as they are the only four that will be set
+ * when dynamic sources are expanded.
+ */
+ switch (*str) {
+ case '@':
+ case '%':
+ case '*':
+ case '!':
+ dynamic = TRUE;
+ break;
+ }
+ } else if ((vlen > 2) && (*str == '.') &&
+ isupper((unsigned char) str[1]) &&
+ ((ctxt == VAR_CMD) || (ctxt == VAR_GLOBAL)))
+ {
+ int len;
+
+ len = vlen - 1;
+ if ((strncmp(str, ".TARGET", len) == 0) ||
+ (strncmp(str, ".ARCHIVE", len) == 0) ||
+ (strncmp(str, ".PREFIX", len) == 0) ||
+ (strncmp(str, ".MEMBER", len) == 0))
+ {
+ dynamic = TRUE;
+ }
+ }
+
+ if (!haveModifier) {
+ /*
+ * No modifiers -- have specification length so we can return
+ * now.
+ */
+ *lengthPtr = tstr - start + 1;
+ if (dynamic) {
+ char *pstr = bmake_strndup(start, *lengthPtr);
+ *freePtr = pstr;
+ Buf_Destroy(&buf, TRUE);
+ return(pstr);
+ } else {
+ Buf_Destroy(&buf, TRUE);
+ return (flags & VARF_UNDEFERR) ? var_Error : varNoError;
+ }
+ } else {
+ /*
+ * Still need to get to the end of the variable specification,
+ * so kludge up a Var structure for the modifications
+ */
+ v = bmake_malloc(sizeof(Var));
+ v->name = UNCONST(str);
+ Buf_Init(&v->val, 1);
+ v->flags = VAR_JUNK;
+ Buf_Destroy(&buf, FALSE);
+ }
+ } else
+ Buf_Destroy(&buf, TRUE);
+ }
+
+ if (v->flags & VAR_IN_USE) {
+ Fatal("Variable %s is recursive.", v->name);
+ /*NOTREACHED*/
+ } else {
+ v->flags |= VAR_IN_USE;
+ }
+ /*
+ * Before doing any modification, we have to make sure the value
+ * has been fully expanded. If it looks like recursion might be
+ * necessary (there's a dollar sign somewhere in the variable's value)
+ * we just call Var_Subst to do any other substitutions that are
+ * necessary. Note that the value returned by Var_Subst will have
+ * been dynamically-allocated, so it will need freeing when we
+ * return.
+ */
+ nstr = Buf_GetAll(&v->val, NULL);
+ if (strchr(nstr, '$') != NULL) {
+ nstr = Var_Subst(NULL, nstr, ctxt, flags);
+ *freePtr = nstr;
+ }
+
+ v->flags &= ~VAR_IN_USE;
+
+ if ((nstr != NULL) && (haveModifier || extramodifiers != NULL)) {
+ void *extraFree;
+ int used;
+
+ extraFree = NULL;
+ if (extramodifiers != NULL) {
+ nstr = ApplyModifiers(nstr, extramodifiers, '(', ')',
+ v, ctxt, flags, &used, &extraFree);
+ }
+
+ if (haveModifier) {
+ /* Skip initial colon. */
+ tstr++;
+
+ nstr = ApplyModifiers(nstr, tstr, startc, endc,
+ v, ctxt, flags, &used, freePtr);
+ tstr += used;
+ free(extraFree);
+ } else {
+ *freePtr = extraFree;
+ }
+ }
+ if (*tstr) {
+ *lengthPtr = tstr - start + 1;
+ } else {
+ *lengthPtr = tstr - start;
+ }
+
+ if (v->flags & VAR_FROM_ENV) {
+ Boolean destroy = FALSE;
+
+ if (nstr != Buf_GetAll(&v->val, NULL)) {
+ destroy = TRUE;
+ } else {
+ /*
+ * Returning the value unmodified, so tell the caller to free
+ * the thing.
+ */
+ *freePtr = nstr;
+ }
+ VarFreeEnv(v, destroy);
+ } else if (v->flags & VAR_JUNK) {
+ /*
+ * Perform any free'ing needed and set *freePtr to NULL so the caller
+ * doesn't try to free a static pointer.
+ * If VAR_KEEP is also set then we want to keep str as is.
+ */
+ if (!(v->flags & VAR_KEEP)) {
+ if (*freePtr) {
+ free(nstr);
+ *freePtr = NULL;
+ }
+ if (dynamic) {
+ nstr = bmake_strndup(start, *lengthPtr);
+ *freePtr = nstr;
+ } else {
+ nstr = (flags & VARF_UNDEFERR) ? var_Error : varNoError;
+ }
+ }
+ if (nstr != Buf_GetAll(&v->val, NULL))
+ Buf_Destroy(&v->val, TRUE);
+ free(v->name);
+ free(v);
+ }
+ return (nstr);
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * Var_Subst --
+ * Substitute for all variables in the given string in the given context
+ * If flags & VARF_UNDEFERR, Parse_Error will be called when an undefined
+ * variable is encountered.
+ *
+ * Input:
+ * var Named variable || NULL for all
+ * str the string which to substitute
+ * ctxt the context wherein to find variables
+ * flags VARF_UNDEFERR if undefineds are an error
+ * VARF_WANTRES if we actually want the result
+ * VARF_ASSIGN if we are in a := assignment
+ *
+ * Results:
+ * The resulting string.
+ *
+ * Side Effects:
+ * None. The old string must be freed by the caller
+ *-----------------------------------------------------------------------
+ */
+char *
+Var_Subst(const char *var, const char *str, GNode *ctxt, int flags)
+{
+ Buffer buf; /* Buffer for forming things */
+ char *val; /* Value to substitute for a variable */
+ int length; /* Length of the variable invocation */
+ Boolean trailingBslash; /* variable ends in \ */
+ void *freeIt = NULL; /* Set if it should be freed */
+ static Boolean errorReported; /* Set true if an error has already
+ * been reported to prevent a plethora
+ * of messages when recursing */
+
+ Buf_Init(&buf, 0);
+ errorReported = FALSE;
+ trailingBslash = FALSE;
+
+ while (*str) {
+ if (*str == '\n' && trailingBslash)
+ Buf_AddByte(&buf, ' ');
+ if (var == NULL && (*str == '$') && (str[1] == '$')) {
+ /*
+ * A dollar sign may be escaped either with another dollar sign.
+ * In such a case, we skip over the escape character and store the
+ * dollar sign into the buffer directly.
+ */
+ if (save_dollars && (flags & VARF_ASSIGN))
+ Buf_AddByte(&buf, *str);
+ str++;
+ Buf_AddByte(&buf, *str);
+ str++;
+ } else if (*str != '$') {
+ /*
+ * Skip as many characters as possible -- either to the end of
+ * the string or to the next dollar sign (variable invocation).
+ */
+ const char *cp;
+
+ for (cp = str++; *str != '$' && *str != '\0'; str++)
+ continue;
+ Buf_AddBytes(&buf, str - cp, cp);
+ } else {
+ if (var != NULL) {
+ int expand;
+ for (;;) {
+ if (str[1] == '\0') {
+ /* A trailing $ is kind of a special case */
+ Buf_AddByte(&buf, str[0]);
+ str++;
+ expand = FALSE;
+ } else if (str[1] != PROPEN && str[1] != BROPEN) {
+ if (str[1] != *var || strlen(var) > 1) {
+ Buf_AddBytes(&buf, 2, str);
+ str += 2;
+ expand = FALSE;
+ }
+ else
+ expand = TRUE;
+ break;
+ }
+ else {
+ const char *p;
+
+ /*
+ * Scan up to the end of the variable name.
+ */
+ for (p = &str[2]; *p &&
+ *p != ':' && *p != PRCLOSE && *p != BRCLOSE; p++)
+ if (*p == '$')
+ break;
+ /*
+ * A variable inside the variable. We cannot expand
+ * the external variable yet, so we try again with
+ * the nested one
+ */
+ if (*p == '$') {
+ Buf_AddBytes(&buf, p - str, str);
+ str = p;
+ continue;
+ }
+
+ if (strncmp(var, str + 2, p - str - 2) != 0 ||
+ var[p - str - 2] != '\0') {
+ /*
+ * Not the variable we want to expand, scan
+ * until the next variable
+ */
+ for (;*p != '$' && *p != '\0'; p++)
+ continue;
+ Buf_AddBytes(&buf, p - str, str);
+ str = p;
+ expand = FALSE;
+ }
+ else
+ expand = TRUE;
+ break;
+ }
+ }
+ if (!expand)
+ continue;
+ }
+
+ val = Var_Parse(str, ctxt, flags, &length, &freeIt);
+
+ /*
+ * When we come down here, val should either point to the
+ * value of this variable, suitably modified, or be NULL.
+ * Length should be the total length of the potential
+ * variable invocation (from $ to end character...)
+ */
+ if (val == var_Error || val == varNoError) {
+ /*
+ * If performing old-time variable substitution, skip over
+ * the variable and continue with the substitution. Otherwise,
+ * store the dollar sign and advance str so we continue with
+ * the string...
+ */
+ if (oldVars) {
+ str += length;
+ } else if ((flags & VARF_UNDEFERR) || val == var_Error) {
+ /*
+ * If variable is undefined, complain and skip the
+ * variable. The complaint will stop us from doing anything
+ * when the file is parsed.
+ */
+ if (!errorReported) {
+ Parse_Error(PARSE_FATAL,
+ "Undefined variable \"%.*s\"",length,str);
+ }
+ str += length;
+ errorReported = TRUE;
+ } else {
+ Buf_AddByte(&buf, *str);
+ str += 1;
+ }
+ } else {
+ /*
+ * We've now got a variable structure to store in. But first,
+ * advance the string pointer.
+ */
+ str += length;
+
+ /*
+ * Copy all the characters from the variable value straight
+ * into the new string.
+ */
+ length = strlen(val);
+ Buf_AddBytes(&buf, length, val);
+ trailingBslash = length > 0 && val[length - 1] == '\\';
+ }
+ free(freeIt);
+ freeIt = NULL;
+ }
+ }
+
+ return Buf_DestroyCompact(&buf);
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * Var_GetTail --
+ * Return the tail from each of a list of words. Used to set the
+ * System V local variables.
+ *
+ * Input:
+ * file Filename to modify
+ *
+ * Results:
+ * The resulting string.
+ *
+ * Side Effects:
+ * None.
+ *
+ *-----------------------------------------------------------------------
+ */
+#if 0
+char *
+Var_GetTail(char *file)
+{
+ return(VarModify(file, VarTail, NULL));
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * Var_GetHead --
+ * Find the leading components of a (list of) filename(s).
+ * XXX: VarHead does not replace foo by ., as (sun) System V make
+ * does.
+ *
+ * Input:
+ * file Filename to manipulate
+ *
+ * Results:
+ * The leading components.
+ *
+ * Side Effects:
+ * None.
+ *
+ *-----------------------------------------------------------------------
+ */
+char *
+Var_GetHead(char *file)
+{
+ return(VarModify(file, VarHead, NULL));
+}
+#endif
+
+/*-
+ *-----------------------------------------------------------------------
+ * Var_Init --
+ * Initialize the module
+ *
+ * Results:
+ * None
+ *
+ * Side Effects:
+ * The VAR_CMD and VAR_GLOBAL contexts are created
+ *-----------------------------------------------------------------------
+ */
+void
+Var_Init(void)
+{
+ VAR_INTERNAL = Targ_NewGN("Internal");
+ VAR_GLOBAL = Targ_NewGN("Global");
+ VAR_CMD = Targ_NewGN("Command");
+
+}
+
+
+void
+Var_End(void)
+{
+}
+
+
+/****************** PRINT DEBUGGING INFO *****************/
+static void
+VarPrintVar(void *vp)
+{
+ Var *v = (Var *)vp;
+ fprintf(debug_file, "%-16s = %s\n", v->name, Buf_GetAll(&v->val, NULL));
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * Var_Dump --
+ * print all variables in a context
+ *-----------------------------------------------------------------------
+ */
+void
+Var_Dump(GNode *ctxt)
+{
+ Hash_Search search;
+ Hash_Entry *h;
+
+ for (h = Hash_EnumFirst(&ctxt->context, &search);
+ h != NULL;
+ h = Hash_EnumNext(&search)) {
+ VarPrintVar(Hash_GetValue(h));
+ }
+}
diff --git a/usr.bin/man/man.1 b/usr.bin/man/man.1
new file mode 100644
index 0000000..49b3bf3
--- /dev/null
+++ b/usr.bin/man/man.1
@@ -0,0 +1,269 @@
+.\" $NetBSD: man.1,v 1.29 2016/06/16 15:10:58 abhinav Exp $
+.\"
+.\" Copyright (c) 1989, 1990, 1993
+.\" The Regents of the University of California. All rights reserved.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\" notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must reproduce the above copyright
+.\" notice, this list of conditions and the following disclaimer in the
+.\" documentation and/or other materials provided with the distribution.
+.\" 3. Neither the name of the University nor the names of its contributors
+.\" may be used to endorse or promote products derived from this software
+.\" without specific prior written permission.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+.\" ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+.\" SUCH DAMAGE.
+.\"
+.\" @(#)man.1 8.2 (Berkeley) 1/2/94
+.\"
+.Dd June 16, 2016
+.Dt MAN 1
+.Os
+.Sh NAME
+.Nm man
+.Nd display the on-line manual pages
+.Pq aka Dq Em man pages
+.Sh SYNOPSIS
+.Nm
+.Oo Fl acw Ns \&| Ns Fl h Oc
+.Op Fl C Ar file
+.Op Fl M Ar path
+.Op Fl m Ar path
+.Op Fl S Ar srch
+.Oo
+.Op Fl s
+.Ar section
+.Oc
+.Ar name Ar ...
+.Nm
+.Op Fl C Ar file
+.Fl f
+.Ar command Ar ...
+.Nm
+.Op Fl C Ar file
+.Fl k
+.Ar keyword Ar ...
+.Nm
+.Fl p
+.Sh DESCRIPTION
+The
+.Nm
+utility displays the manual pages named on the command line.
+Its options are as follows:
+.Bl -tag -width indent
+.It Fl a
+Display all of the man pages for a specified
+.Ar section
+and
+.Ar name
+combination.
+(Normally, only the first man page found is displayed.)
+.It Fl C
+Use the specified
+.Ar file
+instead of the default configuration file.
+This permits users to configure their own man environment.
+See
+.Xr man.conf 5
+for a description of the contents of this file.
+.It Fl c
+Copy the man page to the standard output instead of using
+.Xr more 1
+to paginate it.
+This is done by default if the standard output is not a terminal device.
+.It Fl f
+Synonym for
+.Xr whatis 1 .
+It searches man pages for
+.Ar command
+in their names and displays header lines from all matching pages.
+.It Fl h
+Display only the
+.Dq Tn SYNOPSIS
+lines of the requested man pages.
+For commands, this is typically the command line usage information.
+For library functions, this usually contains the required include
+files and function prototypes.
+.It Fl k
+Search man pages for
+.Ar keyword Ns Pq s ,
+in the same manner as
+.Xr apropos 1 .
+.It Fl M
+Override the list of standard directories which
+.Nm
+searches for man pages.
+The supplied
+.Ar path
+must be a colon
+.Pq Dq \&:
+separated list of directories.
+This search path may also be set using the environment variable
+.Ev MANPATH .
+The subdirectories to be searched, and their search order,
+is specified by the
+.Dq _subdir
+line in the
+.Nm
+configuration file.
+.It Fl m
+Augment the list of standard directories which
+.Nm
+searches for man pages.
+The supplied
+.Ar path
+must be a colon
+.Pq Dq \&:
+separated list of directories.
+These directories will be searched before the standard directories or
+the directories specified using the
+.Fl M
+option or the
+.Ev MANPATH
+environment variable.
+The subdirectories to be searched, and their search order,
+is specified by the
+.Dq _subdir
+line in the
+.Nm
+configuration file.
+.It Fl p
+Print the search path for the manual pages.
+.It Fl s
+Restrict the directories that
+.Nm
+will search to the specified section.
+The
+.Nm
+configuration file (see
+.Xr man.conf 5 )
+specifies the possible
+.Ar section
+values that are currently available.
+.It Fl S
+Display only man pages that have the specified string in the directory
+part of their filenames.
+This allows the man page search process criteria to be
+narrowed without having to change the MANPATH or
+.Dq _default
+variables.
+.It Fl w
+List the pathnames of the man pages which
+.Nm
+would display for the specified
+.Ar section
+and
+.Ar name
+combination.
+.El
+.Pp
+If the
+.Ql Fl s
+option is not specified,
+there is more than one argument,
+the
+.Ql Fl k
+option is not used, and the first argument is a valid section, then that
+argument will be used as if specified by the
+.Ql Fl s
+option.
+.Pp
+If
+.Ar name
+is given with a full path (beginning with
+.Ql Pa \&/ )
+or a relative path that begins with
+.Ql Pa .\&/
+or
+.Ql Pa .\&./ ,
+then
+.Nm
+interprets it as a file specification, so that you can do
+.Nm
+.Cm ./foo.5
+or even
+.Nm
+.Cm /cd/foo/bar.1.gz .
+If
+.Ar name
+contains
+.Ql Pa /
+but does not match one of the above cases, then the
+search path is used; this allows you to request
+machine-specific man pages, such as
+.Nm Cm vax/boot .
+.Sh ENVIRONMENT
+.Bl -tag -width MANPATHX
+.It Ev MACHINE
+As some man pages are intended only for specific architectures,
+.Nm
+searches any subdirectories,
+with the same name as the current architecture,
+in every directory which it searches.
+Machine specific areas are checked before general areas.
+The current machine type may be overridden by setting the environment
+variable
+.Ev MACHINE
+to the name of a specific architecture.
+Machine-specific man pages may also be requested by
+prepending the relevant subdirectory name to the page name,
+separated by
+.Ql Pa \&/ .
+.It Ev MANPATH
+The standard search path used by
+.Nm
+may be overridden by specifying a path in the
+.Ev MANPATH
+environment variable.
+The format of the path is a colon
+.Pq Dq \&:
+separated list of directories.
+The subdirectories to be searched as well as their search order
+is specified by the
+.Dq _subdir
+line in the
+.Nm
+configuration file.
+.It Ev PAGER
+The pagination command used for writing the output.
+If the
+.Ev PAGER
+environment variable is null or not set, the standard pagination program
+.Xr more 1
+will be used.
+.El
+.Sh FILES
+.Bl -hang -width /etc/man.conf -compact
+.It Pa /etc/man.conf
+default man configuration file.
+.El
+.Sh SEE ALSO
+.Xr apropos 1 ,
+.Xr whatis 1 ,
+.Xr whereis 1 ,
+.Xr man.conf 5 ,
+.Xr mdoc 7 ,
+.Xr mdoc.samples 7
+.Sh STANDARDS
+.Nm
+conforms to
+.St -xcu5 .
+.\"and is expected to conform to
+.\".St -p1003.2-?? .
+.Sh BUGS
+The on-line man pages are, by necessity, forgiving toward stupid
+display devices, causing a few man pages to be not as nicely formatted
+as their typeset counterparts.
diff --git a/usr.bin/man/man.c b/usr.bin/man/man.c
new file mode 100644
index 0000000..cba36bc
--- /dev/null
+++ b/usr.bin/man/man.c
@@ -0,0 +1,1077 @@
+/* $NetBSD: man.c,v 1.67 2018/06/15 20:16:35 mrg Exp $ */
+
+/*
+ * Copyright (c) 1987, 1993, 1994, 1995
+ * The Regents of the University of California. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+
+#ifndef lint
+__COPYRIGHT("@(#) Copyright (c) 1987, 1993, 1994, 1995\
+ The Regents of the University of California. All rights reserved.");
+#endif /* not lint */
+
+#ifndef lint
+#if 0
+static char sccsid[] = "@(#)man.c 8.17 (Berkeley) 1/31/95";
+#else
+__RCSID("$NetBSD: man.c,v 1.67 2018/06/15 20:16:35 mrg Exp $");
+#endif
+#endif /* not lint */
+
+#include <sys/param.h>
+#include <sys/queue.h>
+#include <sys/stat.h>
+#include <sys/utsname.h>
+
+#include <ctype.h>
+#include <err.h>
+#include <fcntl.h>
+#include <fnmatch.h>
+#include <glob.h>
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <util.h>
+#include <locale.h>
+
+#include "manconf.h"
+#include "pathnames.h"
+
+#ifndef MAN_DEBUG
+#define MAN_DEBUG 0 /* debug path output */
+#endif
+
+/*
+ * manstate: structure collecting the current global state so we can
+ * easily identify it and pass it to helper functions in one arg.
+ */
+struct manstate {
+ /* command line flags */
+ int all; /* -a: show all matches rather than first */
+ int cat; /* -c: do not use a pager */
+ char *conffile; /* -C: use alternate config file */
+ int how; /* -h: show SYNOPSIS only */
+ char *manpath; /* -M: alternate MANPATH */
+ char *addpath; /* -m: add these dirs to front of manpath */
+ char *pathsearch; /* -S: path of man must contain this string */
+ char *sectionname; /* -s: limit search to a given man section */
+ int where; /* -w: just show paths of all matching files */
+ int getpath; /* -p: print the path of directories containing man pages */
+
+ /* important tags from the config file */
+ TAG *defaultpath; /* _default: default MANPATH */
+ TAG *subdirs; /* _subdir: default subdir search list */
+ TAG *suffixlist; /* _suffix: for files that can be cat()'d */
+ TAG *buildlist; /* _build: for files that must be built */
+
+ /* tags for internal use */
+ TAG *intmp; /* _intmp: tmp files we must cleanup */
+ TAG *missinglist; /* _missing: pages we couldn't find */
+ TAG *mymanpath; /* _new_path: final version of MANPATH */
+ TAG *section; /* <sec>: tag for m.sectionname */
+
+ /* other misc stuff */
+ const char *pager; /* pager to use */
+ size_t pagerlen; /* length of the above */
+ const char *machine; /* machine */
+ const char *machclass; /* machine class */
+};
+
+/*
+ * prototypes
+ */
+static void build_page(const char *, char **, struct manstate *);
+static void cat(const char *);
+static const char *check_pager(const char *);
+static int cleanup(void);
+static void how(const char *);
+static void jump(char **, const char *, const char *) __dead;
+static int manual(char *, struct manstate *, glob_t *);
+static void onsig(int) __dead;
+static void usage(void) __dead;
+static void addpath(struct manstate *, const char *, size_t, const char *);
+static const char *getclass(const char *);
+static void printmanpath(struct manstate *);
+
+/*
+ * main function
+ */
+int
+main(int argc, char **argv)
+{
+ static struct manstate m;
+ struct utsname utsname;
+ int ch, abs_section, found;
+ ENTRY *esubd, *epath;
+ char *p, **ap, *cmd;
+ size_t len;
+ glob_t pg;
+
+ setprogname(argv[0]);
+ setlocale(LC_ALL, "");
+ /*
+ * parse command line...
+ */
+ while ((ch = getopt(argc, argv, "-aC:cfhkM:m:P:ps:S:w")) != -1)
+ switch (ch) {
+ case 'a':
+ m.all = 1;
+ break;
+ case 'C':
+ m.conffile = optarg;
+ break;
+ case 'c':
+ case '-': /* XXX: '-' is a deprecated version of '-c' */
+ m.cat = 1;
+ break;
+ case 'h':
+ m.how = 1;
+ break;
+ case 'm':
+ m.addpath = optarg;
+ break;
+ case 'M':
+ case 'P': /* -P for backward compatibility */
+ if ((m.manpath = strdup(optarg)) == NULL)
+ err(EXIT_FAILURE, "malloc failed");
+ break;
+ case 'p':
+ m.getpath = 1;
+ break;
+ /*
+ * The -f and -k options are backward compatible,
+ * undocumented ways of calling whatis(1) and apropos(1).
+ */
+ case 'f':
+ jump(argv, "-f", "whatis");
+ /* NOTREACHED */
+ case 'k':
+ jump(argv, "-k", "apropos");
+ /* NOTREACHED */
+ case 's':
+ if (m.sectionname != NULL)
+ usage();
+ m.sectionname = optarg;
+ break;
+ case 'S':
+ m.pathsearch = optarg;
+ break;
+ case 'w':
+ m.all = m.where = 1;
+ break;
+ case '?':
+ default:
+ usage();
+ }
+ argc -= optind;
+ argv += optind;
+
+ if (!m.getpath && !argc)
+ usage();
+
+ /*
+ * read the configuration file and collect any other information
+ * we will need (machine type, pager, section [if specified
+ * without '-s'], and MANPATH through the environment).
+ */
+ config(m.conffile); /* exits on error ... */
+
+ if ((m.machine = getenv("MACHINE")) == NULL) {
+ if (uname(&utsname) == -1)
+ err(EXIT_FAILURE, "uname");
+ m.machine = utsname.machine;
+ }
+
+ m.machclass = getclass(m.machine);
+
+ if (!m.cat && !m.how && !m.where) { /* if we need a pager ... */
+ if (!isatty(STDOUT_FILENO)) {
+ m.cat = 1;
+ } else {
+ if ((m.pager = getenv("PAGER")) != NULL &&
+ m.pager[0] != '\0')
+ m.pager = check_pager(m.pager);
+ else
+ m.pager = _PATH_PAGER;
+ m.pagerlen = strlen(m.pager);
+ }
+ }
+
+ /* do we need to set m.section to a non-null value? */
+ if (m.sectionname) {
+
+ m.section = gettag(m.sectionname, 0); /* -s must be a section */
+ if (m.section == NULL)
+ errx(EXIT_FAILURE, "unknown section: %s", m.sectionname);
+
+ } else if (argc > 1) {
+
+ m.section = gettag(*argv, 0); /* might be a section? */
+ if (m.section) {
+ argv++;
+ argc--;
+ }
+
+ }
+
+ if (m.manpath == NULL)
+ m.manpath = getenv("MANPATH"); /* note: -M overrides getenv */
+
+
+ /*
+ * get default values from config file, plus create the tags we
+ * use for keeping internal state. make sure all our mallocs
+ * go through.
+ */
+ /* from cfg file */
+ m.defaultpath = gettag("_default", 1);
+ m.subdirs = gettag("_subdir", 1);
+ m.suffixlist = gettag("_suffix", 1);
+ m.buildlist = gettag("_build", 1);
+ /* internal use */
+ m.mymanpath = gettag("_new_path", 1);
+ m.missinglist = gettag("_missing", 1);
+ m.intmp = gettag("_intmp", 1);
+ if (!m.defaultpath || !m.subdirs || !m.suffixlist || !m.buildlist ||
+ !m.mymanpath || !m.missinglist || !m.intmp)
+ errx(EXIT_FAILURE, "malloc failed");
+
+ /*
+ * are we using a section whose elements are all absolute paths?
+ * (we only need to look at the first entry on the section list,
+ * as config() will ensure that any additional entries will match
+ * the first one.)
+ */
+ abs_section = (m.section != NULL &&
+ !TAILQ_EMPTY(&m.section->entrylist) &&
+ *(TAILQ_FIRST(&m.section->entrylist)->s) == '/');
+
+ /*
+ * now that we have all the data we need, we must determine the
+ * manpath we are going to use to find the requested entries using
+ * the following steps...
+ *
+ * [1] if the user specified a section and that section's elements
+ * from the config file are all absolute paths, then we override
+ * defaultpath and -M/MANPATH with the section's absolute paths.
+ */
+ if (abs_section) {
+ m.manpath = NULL; /* ignore -M/MANPATH */
+ m.defaultpath = m.section; /* overwrite _default path */
+ m.section = NULL; /* promoted to defaultpath */
+ }
+
+ /*
+ * [2] section can now only be non-null if the user asked for
+ * a section and that section's elements did not have
+ * absolute paths. in this case we use the section's
+ * elements to override _subdir from the config file.
+ *
+ * after this step, we are done processing "m.section"...
+ */
+ if (m.section)
+ m.subdirs = m.section;
+
+ /*
+ * [3] we need to setup the path we want to use (m.mymanpath).
+ * if the user gave us a path (m.manpath) use it, otherwise
+ * go with the default. in either case we need to append
+ * the subdir and machine spec to each element of the path.
+ *
+ * for absolute section paths that come from the config file,
+ * we only append the subdir spec if the path ends in
+ * a '/' --- elements that do not end in '/' are assumed to
+ * not have subdirectories. this is mainly for backward compat,
+ * but it allows non-subdir configs like:
+ * sect3 /usr/share/man/{old/,}cat3
+ * doc /usr/{pkg,share}/doc/{sendmail/op,sendmail/intro}
+ *
+ * note that we try and be careful to not put double slashes
+ * in the path (e.g. we want /usr/share/man/man1, not
+ * /usr/share/man//man1) because "more" will put the filename
+ * we generate in its prompt and the double slashes look ugly.
+ */
+ if (m.manpath) {
+
+ /* note: strtok is going to destroy m.manpath */
+ for (p = strtok(m.manpath, ":") ; p ; p = strtok(NULL, ":")) {
+ len = strlen(p);
+ if (len < 1)
+ continue;
+ TAILQ_FOREACH(esubd, &m.subdirs->entrylist, q)
+ addpath(&m, p, len, esubd->s);
+ }
+
+ } else {
+
+ TAILQ_FOREACH(epath, &m.defaultpath->entrylist, q) {
+ /* handle trailing "/" magic here ... */
+ if (abs_section && epath->s[epath->len - 1] != '/') {
+ addpath(&m, "", 1, epath->s);
+ continue;
+ }
+
+ TAILQ_FOREACH(esubd, &m.subdirs->entrylist, q)
+ addpath(&m, epath->s, epath->len, esubd->s);
+ }
+
+ }
+
+ /*
+ * [4] finally, prepend the "-m" m.addpath to mymanpath if it
+ * was specified. subdirs and machine are always applied to
+ * m.addpath.
+ */
+ if (m.addpath) {
+
+ /* note: strtok is going to destroy m.addpath */
+ for (p = strtok(m.addpath, ":") ; p ; p = strtok(NULL, ":")) {
+ len = strlen(p);
+ if (len < 1)
+ continue;
+ TAILQ_FOREACH(esubd, &m.subdirs->entrylist, q)
+ addpath(&m, p, len, esubd->s);
+ }
+
+ }
+
+ if (m.getpath)
+ printmanpath(&m);
+
+ /*
+ * now m.mymanpath is complete!
+ */
+#if MAN_DEBUG
+ printf("mymanpath:\n");
+ TAILQ_FOREACH(epath, &m.mymanpath->entrylist, q) {
+ printf("\t%s\n", epath->s);
+ }
+#endif
+
+ /*
+ * start searching for matching files and format them if necessary.
+ * setup an interrupt handler so that we can ensure that temporary
+ * files go away.
+ */
+ (void)signal(SIGINT, onsig);
+ (void)signal(SIGHUP, onsig);
+ (void)signal(SIGPIPE, onsig);
+
+ memset(&pg, 0, sizeof(pg));
+ for (found = 0; *argv; ++argv)
+ if (manual(*argv, &m, &pg)) {
+ found = 1;
+ }
+
+ /* if nothing found, we're done. */
+ if (!found) {
+ (void)cleanup();
+ exit(EXIT_FAILURE);
+ }
+
+ /*
+ * handle the simple display cases first (m.cat, m.how, m.where)
+ */
+ if (m.cat) {
+ for (ap = pg.gl_pathv; *ap != NULL; ++ap) {
+ if (**ap == '\0')
+ continue;
+ cat(*ap);
+ }
+ exit(cleanup());
+ }
+ if (m.how) {
+ for (ap = pg.gl_pathv; *ap != NULL; ++ap) {
+ if (**ap == '\0')
+ continue;
+ how(*ap);
+ }
+ exit(cleanup());
+ }
+ if (m.where) {
+ for (ap = pg.gl_pathv; *ap != NULL; ++ap) {
+ if (**ap == '\0')
+ continue;
+ (void)printf("%s\n", *ap);
+ }
+ exit(cleanup());
+ }
+
+ /*
+ * normal case - we display things in a single command, so
+ * build a list of things to display. first compute total
+ * length of buffer we will need so we can malloc it.
+ */
+ for (ap = pg.gl_pathv, len = m.pagerlen + 1; *ap != NULL; ++ap) {
+ if (**ap == '\0')
+ continue;
+ len += strlen(*ap) + 1;
+ }
+ if ((cmd = malloc(len)) == NULL) {
+ warn("malloc");
+ (void)cleanup();
+ exit(EXIT_FAILURE);
+ }
+
+ /* now build the command string... */
+ p = cmd;
+ len = m.pagerlen;
+ memcpy(p, m.pager, len);
+ p += len;
+ *p++ = ' ';
+ for (ap = pg.gl_pathv; *ap != NULL; ++ap) {
+ if (**ap == '\0')
+ continue;
+ len = strlen(*ap);
+ memcpy(p, *ap, len);
+ p += len;
+ *p++ = ' ';
+ }
+ *--p = '\0';
+
+ /* Use system(3) in case someone's pager is "pager arg1 arg2". */
+ (void)system(cmd);
+
+ exit(cleanup());
+}
+
+static int
+manual_find_literalfile(struct manstate *mp, char **pv)
+{
+ ENTRY *suffix;
+ int found;
+ char buf[MAXPATHLEN];
+ const char *p;
+ int suflen;
+
+ found = 0;
+
+ /*
+ * Expand both '*' and suffix to force an actual
+ * match via fnmatch(3). Since the only match in pg
+ * is the literal file, the match is genuine.
+ */
+
+ TAILQ_FOREACH(suffix, &mp->buildlist->entrylist, q) {
+ for (p = suffix->s, suflen = 0;
+ *p != '\0' && !isspace((unsigned char)*p);
+ ++p)
+ ++suflen;
+ if (*p == '\0')
+ continue;
+
+ (void)snprintf(buf, sizeof(buf), "*%.*s", suflen, suffix->s);
+
+ if (!fnmatch(buf, *pv, 0)) {
+ if (!mp->where)
+ build_page(p + 1, pv, mp);
+ found = 1;
+ break;
+ }
+ }
+
+ return found;
+}
+
+static int
+manual_find_buildkeyword(const char *prefix, const char *escpage,
+ struct manstate *mp, char **pv)
+{
+ ENTRY *suffix;
+ int found;
+ char buf[MAXPATHLEN];
+ const char *p;
+ int suflen;
+
+ found = 0;
+ /* Try the _build keywords next. */
+ TAILQ_FOREACH(suffix, &mp->buildlist->entrylist, q) {
+ for (p = suffix->s, suflen = 0;
+ *p != '\0' && !isspace((unsigned char)*p);
+ ++p)
+ ++suflen;
+ if (*p == '\0')
+ continue;
+
+ (void)snprintf(buf, sizeof(buf), "%s%s%.*s",
+ prefix, escpage, suflen, suffix->s);
+ if (!fnmatch(buf, *pv, 0)) {
+ if (!mp->where)
+ build_page(p + 1, pv, mp);
+ found = 1;
+ break;
+ }
+ }
+
+ return found;
+}
+
+/*
+ * manual --
+ * Search the manuals for the pages.
+ */
+static int
+manual(char *page, struct manstate *mp, glob_t *pg)
+{
+ ENTRY *suffix, *mdir;
+ int anyfound, error, found;
+ size_t cnt;
+ char *p, buf[MAXPATHLEN], *escpage, *eptr;
+ static const char escglob[] = "\\~?*{}[]";
+
+ anyfound = 0;
+
+ /*
+ * Fixup page which may contain glob(3) special characters, e.g.
+ * the famous "No man page for [" FAQ.
+ */
+ if ((escpage = malloc((2 * strlen(page)) + 1)) == NULL) {
+ warn("malloc");
+ (void)cleanup();
+ exit(EXIT_FAILURE);
+ }
+
+ p = page;
+ eptr = escpage;
+
+ while (*p) {
+ if (strchr(escglob, *p) != NULL) {
+ *eptr++ = '\\';
+ *eptr++ = *p++;
+ } else
+ *eptr++ = *p++;
+ }
+
+ *eptr = '\0';
+
+ /*
+ * If 'page' is given with an absolute path,
+ * or a relative path explicitly beginning with "./"
+ * or "../", then interpret it as a file specification.
+ */
+ if ((page[0] == '/')
+ || (page[0] == '.' && page[1] == '/')
+ || (page[0] == '.' && page[1] == '.' && page[2] == '/')
+ ) {
+ /* check if file actually exists */
+ (void)strlcpy(buf, escpage, sizeof(buf));
+ error = glob(buf, GLOB_APPEND | GLOB_BRACE | GLOB_NOSORT, NULL, pg);
+ if (error != 0) {
+ if (error == GLOB_NOMATCH) {
+ goto notfound;
+ } else {
+ errx(EXIT_FAILURE, "glob failed");
+ }
+ }
+
+ if (pg->gl_matchc == 0)
+ goto notfound;
+
+ /* literal file only yields one match */
+ cnt = pg->gl_pathc - pg->gl_matchc;
+
+ if (manual_find_literalfile(mp, &pg->gl_pathv[cnt])) {
+ anyfound = 1;
+ } else {
+ /* It's not a man page, forget about it. */
+ *pg->gl_pathv[cnt] = '\0';
+ }
+
+ notfound:
+ if (!anyfound) {
+ if (addentry(mp->missinglist, page, 0) < 0) {
+ warn("malloc");
+ (void)cleanup();
+ exit(EXIT_FAILURE);
+ }
+ }
+ free(escpage);
+ return anyfound;
+ }
+
+ /* For each man directory in mymanpath ... */
+ TAILQ_FOREACH(mdir, &mp->mymanpath->entrylist, q) {
+
+ /*
+ * use glob(3) to look in the filesystem for matching files.
+ * match any suffix here, as we will check that later.
+ */
+ (void)snprintf(buf, sizeof(buf), "%s/%s.*", mdir->s, escpage);
+ if ((error = glob(buf,
+ GLOB_APPEND | GLOB_BRACE | GLOB_NOSORT, NULL, pg)) != 0) {
+ if (error == GLOB_NOMATCH)
+ continue;
+ else {
+ warn("globbing");
+ (void)cleanup();
+ exit(EXIT_FAILURE);
+ }
+ }
+ if (pg->gl_matchc == 0)
+ continue;
+
+ /*
+ * start going through the matches glob(3) just found and
+ * use m.pathsearch (if present) to filter out pages we
+ * don't want. then verify the suffix is valid, and build
+ * the page if we have a _build suffix.
+ */
+ for (cnt = pg->gl_pathc - pg->gl_matchc;
+ cnt < pg->gl_pathc; ++cnt) {
+
+ /* filter on directory path name */
+ if (mp->pathsearch) {
+ p = strstr(pg->gl_pathv[cnt], mp->pathsearch);
+ if (!p || strchr(p, '/') == NULL) {
+ *pg->gl_pathv[cnt] = '\0'; /* zap! */
+ continue;
+ }
+ }
+
+ /*
+ * Try the _suffix keywords first.
+ *
+ * XXX
+ * Older versions of man.conf didn't have the _suffix
+ * keywords, it was assumed that everything was a .0.
+ * We just test for .0 first, it's fast and probably
+ * going to hit.
+ */
+ (void)snprintf(buf, sizeof(buf), "*/%s.0", escpage);
+ if (!fnmatch(buf, pg->gl_pathv[cnt], 0))
+ goto next;
+
+ found = 0;
+ TAILQ_FOREACH(suffix, &mp->suffixlist->entrylist, q) {
+ (void)snprintf(buf,
+ sizeof(buf), "*/%s%s", escpage,
+ suffix->s);
+ if (!fnmatch(buf, pg->gl_pathv[cnt], 0)) {
+ found = 1;
+ break;
+ }
+ }
+ if (found)
+ goto next;
+
+ /* Try the _build keywords next. */
+ found = manual_find_buildkeyword("*/", escpage,
+ mp, &pg->gl_pathv[cnt]);
+ if (found) {
+next: anyfound = 1;
+ if (!mp->all) {
+ /* Delete any other matches. */
+ while (++cnt< pg->gl_pathc)
+ *pg->gl_pathv[cnt] = '\0';
+ break;
+ }
+ continue;
+ }
+
+ /* It's not a man page, forget about it. */
+ *pg->gl_pathv[cnt] = '\0';
+ }
+
+ if (anyfound && !mp->all)
+ break;
+ }
+
+ /* If not found, enter onto the missing list. */
+ if (!anyfound) {
+ if (addentry(mp->missinglist, page, 0) < 0) {
+ warn("malloc");
+ (void)cleanup();
+ exit(EXIT_FAILURE);
+ }
+ }
+
+ free(escpage);
+ return anyfound;
+}
+
+/*
+ * A do-nothing counterpart to fmtcheck(3) that only supplies the
+ * __format_arg marker. Actual fmtcheck(3) call is done once in
+ * config().
+ */
+__always_inline __format_arg(2)
+static inline const char *
+fmtcheck_ok(const char *userfmt, const char *template)
+{
+ return userfmt;
+}
+
+/*
+ * build_page --
+ * Build a man page for display.
+ */
+static void
+build_page(const char *fmt, char **pathp, struct manstate *mp)
+{
+ static int warned;
+ int olddir, fd, n;
+ size_t tmpdirlen;
+ char *p, *b;
+ char buf[MAXPATHLEN], cmd[MAXPATHLEN], tpath[MAXPATHLEN];
+ const char *tmpdir;
+
+ /* Let the user know this may take awhile. */
+ if (!warned) {
+ warned = 1;
+ warnx("Formatting manual page...");
+ }
+
+ /*
+ * Historically man chdir'd to the root of the man tree.
+ * This was used in man pages that contained relative ".so"
+ * directives (including other man pages for command aliases etc.)
+ * It even went one step farther, by examining the first line
+ * of the man page and parsing the .so filename so it would
+ * make hard(?) links to the cat'ted man pages for space savings.
+ * (We don't do that here, but we could).
+ */
+
+ /* copy and find the end */
+ for (b = buf, p = *pathp; (*b++ = *p++) != '\0';)
+ continue;
+
+ /*
+ * skip the last two path components, page name and man[n] ...
+ * (e.g. buf will be "/usr/share/man" and p will be "man1/man.1")
+ * we also save a pointer to our current directory so that we
+ * can fchdir() back to it. this allows relative MANDIR paths
+ * to work with multiple man pages... e.g. consider:
+ * cd /usr/share && man -M ./man cat ls
+ * when no "cat1" subdir files are present.
+ */
+ olddir = -1;
+ for (--b, --p, n = 2; b != buf; b--, p--)
+ if (*b == '/')
+ if (--n == 0) {
+ *b = '\0';
+ olddir = open(".", O_RDONLY);
+ (void) chdir(buf);
+ p++;
+ break;
+ }
+
+
+ /* advance fmt past the suffix spec to the printf format string */
+ for (; *fmt && isspace((unsigned char)*fmt); ++fmt)
+ continue;
+
+ /*
+ * Get a temporary file and build a version of the file
+ * to display. Replace the old file name with the new one.
+ */
+ if ((tmpdir = getenv("TMPDIR")) == NULL)
+ tmpdir = _PATH_TMP;
+ tmpdirlen = strlen(tmpdir);
+ (void)snprintf(tpath, sizeof (tpath), "%s%s%s", tmpdir,
+ (tmpdirlen > 0 && tmpdir[tmpdirlen-1] == '/') ? "" : "/", TMPFILE);
+ if ((fd = mkstemp(tpath)) == -1) {
+ warn("%s", tpath);
+ (void)cleanup();
+ exit(EXIT_FAILURE);
+ }
+ (void)snprintf(buf, sizeof(buf), "%s > %s", fmt, tpath);
+ (void)snprintf(cmd, sizeof(cmd), fmtcheck_ok(buf, "%s"), p);
+ (void)system(cmd);
+ (void)close(fd);
+ if ((*pathp = strdup(tpath)) == NULL) {
+ warn("malloc");
+ (void)cleanup();
+ exit(EXIT_FAILURE);
+ }
+
+ /* Link the built file into the remove-when-done list. */
+ if (addentry(mp->intmp, *pathp, 0) < 0) {
+ warn("malloc");
+ (void)cleanup();
+ exit(EXIT_FAILURE);
+ }
+
+ /* restore old directory so relative manpaths still work */
+ if (olddir != -1) {
+ fchdir(olddir);
+ close(olddir);
+ }
+}
+
+/*
+ * how --
+ * display how information
+ */
+static void
+how(const char *fname)
+{
+ FILE *fp;
+
+ int lcnt, print;
+ char buf[256];
+ const char *p;
+
+ if (!(fp = fopen(fname, "r"))) {
+ warn("%s", fname);
+ (void)cleanup();
+ exit(EXIT_FAILURE);
+ }
+#define S1 "SYNOPSIS"
+#define S2 "S\bSY\bYN\bNO\bOP\bPS\bSI\bIS\bS"
+#define D1 "DESCRIPTION"
+#define D2 "D\bDE\bES\bSC\bCR\bRI\bIP\bPT\bTI\bIO\bON\bN"
+ for (lcnt = print = 0; fgets(buf, sizeof(buf), fp);) {
+ if (!strncmp(buf, S1, sizeof(S1) - 1) ||
+ !strncmp(buf, S2, sizeof(S2) - 1)) {
+ print = 1;
+ continue;
+ } else if (!strncmp(buf, D1, sizeof(D1) - 1) ||
+ !strncmp(buf, D2, sizeof(D2) - 1)) {
+ if (fp)
+ (void)fclose(fp);
+ return;
+ }
+ if (!print)
+ continue;
+ if (*buf == '\n')
+ ++lcnt;
+ else {
+ for(; lcnt; --lcnt)
+ (void)putchar('\n');
+ for (p = buf; isspace((unsigned char)*p); ++p)
+ continue;
+ (void)fputs(p, stdout);
+ }
+ }
+ (void)fclose(fp);
+}
+
+/*
+ * cat --
+ * cat out the file
+ */
+static void
+cat(const char *fname)
+{
+ int fd;
+ ssize_t n;
+ char buf[2048];
+
+ if ((fd = open(fname, O_RDONLY, 0)) < 0) {
+ warn("%s", fname);
+ (void)cleanup();
+ exit(EXIT_FAILURE);
+ }
+ while ((n = read(fd, buf, sizeof(buf))) > 0)
+ if (write(STDOUT_FILENO, buf, (size_t)n) != n) {
+ warn("write");
+ (void)cleanup();
+ exit(EXIT_FAILURE);
+ }
+ if (n == -1) {
+ warn("read");
+ (void)cleanup();
+ exit(EXIT_FAILURE);
+ }
+ (void)close(fd);
+}
+
+/*
+ * check_pager --
+ * check the user supplied page information
+ */
+static const char *
+check_pager(const char *name)
+{
+ const char *p;
+
+ /*
+ * if the user uses "more", we make it "more -s"; watch out for
+ * PAGER = "mypager /usr/ucb/more"
+ */
+ for (p = name; *p && !isspace((unsigned char)*p); ++p)
+ continue;
+ for (; p > name && *p != '/'; --p);
+ if (p != name)
+ ++p;
+
+ /* make sure it's "more", not "morex" */
+ if (!strncmp(p, "more", 4) && (!p[4] || isspace((unsigned char)p[4]))){
+ char *newname;
+ (void)asprintf(&newname, "%s %s", p, "-s");
+ name = newname;
+ }
+
+ return name;
+}
+
+/*
+ * jump --
+ * strip out flag argument and jump
+ */
+static void
+jump(char **argv, const char *flag, const char *name)
+{
+ char **arg;
+
+ argv[0] = __UNCONST(name);
+ for (arg = argv + 1; *arg; ++arg)
+ if (!strcmp(*arg, flag))
+ break;
+ for (; *arg; ++arg)
+ arg[0] = arg[1];
+ execvp(name, argv);
+ err(EXIT_FAILURE, "Cannot execute `%s'", name);
+}
+
+/*
+ * onsig --
+ * If signaled, delete the temporary files.
+ */
+static void
+onsig(int signo)
+{
+
+ (void)cleanup();
+
+ (void)raise_default_signal(signo);
+
+ /* NOTREACHED */
+ exit(EXIT_FAILURE);
+}
+
+/*
+ * cleanup --
+ * Clean up temporary files, show any error messages.
+ */
+static int
+cleanup(void)
+{
+ TAG *intmpp, *missp;
+ ENTRY *ep;
+ int rval;
+
+ rval = EXIT_SUCCESS;
+ /*
+ * note that _missing and _intmp were created by main(), so
+ * gettag() cannot return NULL here.
+ */
+ missp = gettag("_missing", 0); /* missing man pages */
+ intmpp = gettag("_intmp", 0); /* tmp files we need to unlink */
+
+ TAILQ_FOREACH(ep, &missp->entrylist, q) {
+ warnx("no entry for %s in the manual.", ep->s);
+ rval = EXIT_FAILURE;
+ }
+
+ TAILQ_FOREACH(ep, &intmpp->entrylist, q)
+ (void)unlink(ep->s);
+
+ return rval;
+}
+
+static const char *
+getclass(const char *machine)
+{
+ char buf[BUFSIZ];
+ TAG *t;
+ snprintf(buf, sizeof(buf), "_%s", machine);
+ t = gettag(buf, 0);
+ return t != NULL && !TAILQ_EMPTY(&t->entrylist) ?
+ TAILQ_FIRST(&t->entrylist)->s : NULL;
+}
+
+static void
+addpath(struct manstate *m, const char *dir, size_t len, const char *sub)
+{
+ char buf[2 * MAXPATHLEN + 1];
+ (void)snprintf(buf, sizeof(buf), "%s%s%s{/%s,%s%s%s}",
+ dir, (dir[len - 1] == '/') ? "" : "/", sub, m->machine,
+ m->machclass ? "/" : "", m->machclass ? m->machclass : "",
+ m->machclass ? "," : "");
+ if (addentry(m->mymanpath, buf, 0) < 0)
+ errx(EXIT_FAILURE, "malloc failed");
+}
+
+/*
+ * usage --
+ * print usage message and die
+ */
+static void
+usage(void)
+{
+ (void)fprintf(stderr, "Usage: %s [-acw|-h] [-C cfg] [-M path] "
+ "[-m path] [-S srch] [[-s] sect] name ...\n", getprogname());
+ (void)fprintf(stderr, "Usage: %s [-C file] -f command ...\n", getprogname());
+ (void)fprintf(stderr,
+ "Usage: %s [-C file] -k keyword ...\n",
+ getprogname());
+ (void)fprintf(stderr, "Usage: %s -p\n", getprogname());
+ exit(EXIT_FAILURE);
+}
+
+/*
+ * printmanpath --
+ * Prints a list of directories containing man pages.
+ */
+static void
+printmanpath(struct manstate *m)
+{
+ ENTRY *epath;
+ char **ap;
+ glob_t pg;
+ struct stat sb;
+ TAG *path = m->mymanpath;
+
+ /* the tail queue is empty if no _default tag is defined in * man.conf */
+ if (TAILQ_EMPTY(&path->entrylist))
+ errx(EXIT_FAILURE, "Empty manpath");
+
+ TAILQ_FOREACH(epath, &path->entrylist, q) {
+ if (glob(epath->s, GLOB_BRACE | GLOB_NOSORT, NULL, &pg) != 0)
+ err(EXIT_FAILURE, "glob failed");
+
+ if (pg.gl_matchc == 0) {
+ globfree(&pg);
+ continue;
+ }
+
+ for (ap = pg.gl_pathv; *ap != NULL; ++ap) {
+ /* Skip cat page directories */
+ if (strstr(*ap, "/cat") != NULL)
+ continue;
+ /* Skip non-directories. */
+ if (stat(*ap, &sb) == 0 && S_ISDIR(sb.st_mode))
+ printf("%s\n", *ap);
+ }
+ globfree(&pg);
+ }
+}
diff --git a/usr.bin/man/man.conf.5 b/usr.bin/man/man.conf.5
new file mode 100644
index 0000000..b89c74b
--- /dev/null
+++ b/usr.bin/man/man.conf.5
@@ -0,0 +1,286 @@
+.\" $NetBSD: man.conf.5,v 1.27 2016/06/16 15:11:43 abhinav Exp $
+.\"
+.\" Copyright (c) 1989, 1991, 1993
+.\" The Regents of the University of California. All rights reserved.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\" notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must reproduce the above copyright
+.\" notice, this list of conditions and the following disclaimer in the
+.\" documentation and/or other materials provided with the distribution.
+.\" 3. Neither the name of the University nor the names of its contributors
+.\" may be used to endorse or promote products derived from this software
+.\" without specific prior written permission.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+.\" ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+.\" SUCH DAMAGE.
+.\"
+.\" @(#)man.conf.5 8.5 (Berkeley) 1/2/94
+.\"
+.Dd June 16, 2016
+.Dt MAN.CONF 5
+.Os
+.Sh NAME
+.Nm man.conf
+.Nd configuration file for manual pages
+.Sh DESCRIPTION
+The
+.Nm
+file contains the default configuration used by
+.Xr man 1 ,
+.Xr apropos 1 ,
+.Xr whatis 1 ,
+.Xr catman 8 ,
+and
+.Xr makemandb 8
+to find manual pages and information about manual pages (e.g. the
+whatis database).
+.Pp
+Manual pages are located by searching an ordered set of directories
+called the
+.Dq man path
+for a file that matches the name of the requested page.
+Each directory in the search path usually has a set of subdirectories
+in it (though this is not required).
+When subdirectories are used, there are normally two subdirectories
+for each section of the manual.
+One subdirectory contains formatted copies of that section's manual
+pages that can be directly displayed to a terminal, while the other
+section subdirectory contains unformatted copies of the pages (see
+.Xr nroff 1
+and
+.Xr mdoc 7 ) .
+Formatted manual pages are normally named with a trailing
+.Dq \.0
+suffix.
+.Pp
+The
+.Nm
+file contains comment and configuration lines.
+Comment lines start with the
+.Dq #
+character.
+Blank lines are also treated as comment lines.
+Configuration lines consist of a configuration keyword followed by a
+configuration string.
+There are two types of configuration keywords: control keywords and
+section keywords.
+Control keywords must start with the
+.Dq _
+character.
+The following control keywords are currently defined:
+.Bl -tag -width XXmachineX
+.It _build
+Identifies the set of suffixes used for manual pages that must be
+formatted for display and the command that should be used to format
+them.
+Manual file names, regardless of their format, are expected to end in a
+.Dq \.*
+pattern, i.e. a
+.Dq \&\.
+followed by some suffix.
+The first field of a _build line contains a man page suffix specification.
+The suffix specification may contain the normal shell globbing characters
+(NOT including curly braces
+.Pq Dq {} ) .
+The rest of the _build line is a shell command line whose standard
+output is a formatted manual page that can be directly displayed to
+the user.
+There should be exactly one occurrence of the string
+.Dq %s
+in the shell command line, and it will
+be replaced by the name of the file which is being formatted.
+.It _crunch
+Used by
+.Xr catman 8
+to determine how to crunch formatted pages
+which originally were compressed man pages: The first field lists a suffix
+which indicates what kind of compression were used to compress the man page.
+The rest of the line must be a shell command line, used to compress the
+formatted pages.
+There should be exactly one occurrence of the string
+.Dq %s
+in the shell command line, and it will
+be replaced by the name of the output file.
+.It _default
+Contains the system-wide default man path used to search for man pages.
+.It _mandb
+Defines the full pathname (not just a directory path) for a database to
+be used
+by the
+.Xr apropos 1
+and
+.Xr whatis 1
+commands.
+The pathname may contain the normal shell globbing characters,
+including curly braces
+.Pq Dq {} ;
+to escape a shell globbing character,
+precede it with a backslash
+.Pq Dq \e .
+.It _subdir
+Contains the list (in search order) of section subdirectories which will
+be searched in any man path directory named with a trailing slash
+.Pq Dq /
+character.
+This list is also used, even if there is no trailing slash character,
+when a path is specified to the
+.Xr man 1
+utility by the user, by the
+.Ev MANPATH
+environment variable, or by the
+.Fl M
+and
+.Fl m
+options.
+.It _suffix
+identifies the set of suffixes used for formatted man pages
+(the
+.Dq \.0
+suffix is normally used here).
+Formatted man pages can be directly displayed to the user.
+Each suffix may contain the normal shell globbing characters (NOT
+including curly braces
+.Pq Dq {} ) .
+.It _version
+Contains the version of the configuration file.
+.It _ Ns Aq machine
+Defines additional paths to be searched for the particular
+.Dv machine
+whose literal value is taken from
+.Xr uname 1
+.Fl m .
+For example on an
+.Dv amd64 ,
+.Dv _amd64
+is used.
+.El
+.Pp
+Section configuration lines in
+.Nm
+consist of a section keyword naming the section and a configuration
+string that defines the directory or subdirectory path that the section's
+manual pages are located in.
+The path may contain the normal shell globbing characters,
+including curly braces
+.Pq Dq {} ;
+to escape a shell globbing character,
+precede it with a backslash
+.Pq Dq \e .
+Section keywords must not start with the
+.Dq _
+character.
+.Pp
+A section path may contain either a list of absolute directories or
+a list of or relative directories (but not both).
+Relative directory paths are treated as a list of subdirectories that
+are appended to the current man path directory being searched.
+Section configuration lines with absolute directory paths (starting with
+.Dq / )
+completely replace the current man search path directory with their
+content.
+.Pp
+Section configuration lines with absolute directory paths ending
+with a trailing slash character are expected to contain subdirectories
+of manual pages, (see the keyword
+.Dq _subdir
+above).
+The
+.Dq _subdir
+subdirectory list is not applied to absolute section directories
+if there is no trailing slash.
+.Pp
+In addition to the above rules, the
+.Xr man 1
+command also always checks in each directory that it searches for
+a subdirectory with the same name as the current machine type.
+If the machine-specific directory is found, it is also searched.
+This allows the manual to contain machine-specific man pages.
+Note that the machine subdirectory does not need to be specified
+in the
+.Nm
+file.
+.Pp
+Multiple specifications for all types of
+.Nm
+configuration lines are
+cumulative and the entries are used in the order listed in the file;
+multiple entries may be listed per line, as well.
+.Sh FILES
+.Bl -tag -width /etc/man.conf -compact
+.It Pa /etc/man.conf
+Standard manual configuration file.
+.El
+.Sh EXAMPLES
+Given the following
+.Nm
+file:
+.Bd -literal -offset indent
+_version BSD.2
+_subdir cat[123]
+_suffix .0
+_build .[1-9] nroff -man %s
+_build .tbl tbl %s | nroff -man
+_i386 x86
+_default /usr/share/man/
+sect3 /usr/share/man/{old/,}cat3
+.Ed
+.Pp
+By default, the command
+.Dq Li man mktemp
+will search for
+.Dq mktemp. Ns Aq any_digit
+and
+.Dq mktemp.tbl
+in the directories
+.Dq Pa /usr/share/man/cat1 ,
+.Dq Pa /usr/share/man/cat2 ,
+and
+.Dq Pa /usr/share/man/cat3 .
+If on a machine of type
+.Dq vax ,
+the subdirectory
+.Dq vax
+in each
+directory would be searched as well, before the directory was
+searched.
+.Pp
+If
+.Dq mktemp.tbl
+was found first, the command
+.Dq Li tbl Ao manual page Ac | nroff -man
+would be run to build a man page for display to the user.
+.Pp
+The command
+.Dq Li man sect3 mktemp
+would search the directories
+.Dq Pa /usr/share/man/old/cat3
+and
+.Dq Pa /usr/share/man/cat3 ,
+in that order, for
+the mktemp manual page.
+If a subdirectory with the same name as the current machine type
+existed in any of them, it would be searched as well, before each
+of them were searched.
+.Sh SEE ALSO
+.Xr apropos 1 ,
+.Xr machine 1 ,
+.Xr man 1 ,
+.Xr whatis 1 ,
+.Xr whereis 1 ,
+.Xr fnmatch 3 ,
+.Xr glob 3 ,
+.Xr catman 8 ,
+.Xr makemandb 8
diff --git a/usr.bin/man/manconf.c b/usr.bin/man/manconf.c
new file mode 100644
index 0000000..f12d031
--- /dev/null
+++ b/usr.bin/man/manconf.c
@@ -0,0 +1,272 @@
+/* $NetBSD: manconf.c,v 1.8 2014/02/17 02:53:48 uwe Exp $ */
+
+/*
+ * Copyright (c) 1989, 1993, 1995
+ * The Regents of the University of California. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+/*
+ * manconf.c: provides interface for reading man.conf files
+ *
+ * note that this code is shared across all programs that read man.conf.
+ * (currently: apropos, catman, makewhatis, man, and whatis...)
+ */
+
+#if HAVE_NBTOOL_CONFIG_H
+#include "nbtool_config.h"
+#endif
+
+#include <sys/cdefs.h>
+#ifndef lint
+#if 0
+static char sccsid[] = "@(#)config.c 8.8 (Berkeley) 1/31/95";
+#else
+__RCSID("$NetBSD: manconf.c,v 1.8 2014/02/17 02:53:48 uwe Exp $");
+#endif
+#endif /* not lint */
+
+#include <sys/types.h>
+#include <sys/queue.h>
+
+#include <ctype.h>
+#include <err.h>
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "manconf.h"
+#include "pathnames.h"
+
+TAILQ_HEAD(_head, _tag);
+static struct _head head; /* 'head' -- top level data structure */
+
+/*
+ * xstrdup: like strdup, but also returns length of string in lenp
+ */
+static char *
+xstrdup(const char *str, size_t *lenp)
+{
+ size_t len;
+ char *copy;
+
+ len = strlen(str) + 1;
+ copy = malloc(len);
+ if (!copy)
+ return NULL;
+ (void)memcpy(copy, str, len);
+ if (lenp)
+ *lenp = len - 1; /* subtract out the null */
+ return copy;
+}
+
+/*
+ * config --
+ *
+ * Read the configuration file and build a doubly linked
+ * list off of "head" that looks like:
+ *
+ * tag1 <-> entry <-> entry <-> entry
+ * |
+ * tag2 <-> entry <-> entry <-> entry
+ *
+ * note: will err/errx out on error (fopen or malloc failure)
+ */
+void
+config(const char *fname)
+{
+ TAG *tp;
+ FILE *cfp;
+ size_t len;
+ int lcnt;
+ char *p, *t, type;
+
+ if (fname == NULL)
+ fname = _PATH_MANCONF;
+ if ((cfp = fopen(fname, "r")) == NULL)
+ err(EXIT_FAILURE, "%s", fname);
+ TAILQ_INIT(&head);
+ for (lcnt = 1; (p = fgetln(cfp, &len)) != NULL; ++lcnt) {
+ if (len == 1) /* Skip empty lines. */
+ continue;
+ if (p[len - 1] != '\n') { /* Skip corrupted lines. */
+ warnx("%s: line %d corrupted", fname, lcnt);
+ continue;
+ }
+ p[len - 1] = '\0'; /* Terminate the line. */
+
+ /* Skip leading space. */
+ for (/*EMPTY*/; *p != '\0' && isspace((unsigned char)*p); ++p)
+ continue;
+ /* Skip empty/comment lines. */
+ if (*p == '\0' || *p == '#')
+ continue;
+ /* Find first token. */
+ for (t = p; *t && !isspace((unsigned char)*t); ++t)
+ continue;
+ if (*t == '\0') /* Need more than one token.*/
+ continue;
+ *t = '\0';
+
+ tp = gettag(p, 1);
+ if (!tp)
+ errx(EXIT_FAILURE, "gettag: malloc failed");
+
+ /*
+ * Attach new records. Check to see if it is a
+ * section record or not.
+ */
+
+ if (*p == '_') { /* not a section record */
+ /*
+ * Special cases: _build and _crunch take the
+ * rest of the line as a single entry.
+ */
+ if (!strcmp(p, "_build") || !strcmp(p, "_crunch")) {
+ const char *u;
+
+ /*
+ * The reason we're not just using
+ * strtok(3) for all of the parsing is
+ * so we don't get caught if a line
+ * has only a single token on it.
+ */
+ while (*++t && isspace((unsigned char)*t));
+#ifndef HAVE_NBTOOL_CONFIG_H
+ /* pre-verify user-supplied command format */
+ u = t;
+ while (*u && !isspace((unsigned char)*u))
+ ++u;
+ while (*u && isspace((unsigned char)*u))
+ ++u;
+ if (fmtcheck(u, "%s") != u) {
+ warnx("%s:%d: invalid %s command ignored",
+ fname, lcnt, p);
+ continue;
+ }
+#endif /* !HAVE_NBTOOL_CONFIG_H */
+ if (addentry(tp, t, 0) == -1)
+ errx(EXIT_FAILURE,
+ "addentry: malloc failed");
+ } else {
+ for (++t; (p = strtok(t, " \t\n")) != NULL;
+ t = NULL) {
+ if (addentry(tp, p, 0) == -1)
+ errx(EXIT_FAILURE,
+ "addentry: malloc failed");
+ }
+ }
+
+ } else { /* section record */
+
+ /*
+ * section entries can either be all absolute
+ * paths or all relative paths, but not both.
+ */
+ type = (char)((TAILQ_FIRST(&tp->entrylist) != NULL) ?
+ *(TAILQ_FIRST(&tp->entrylist)->s) : '\0');
+
+ for (++t; (p = strtok(t, " \t\n")) != NULL; t = NULL) {
+
+ /* ensure an assigned type */
+ if (type == 0)
+ type = *p;
+
+ /* check for illegal mix */
+ if (*p != type) {
+ warnx("section %s: %s: invalid entry, does not match previous types",
+ tp->s, p);
+ warnx("man.conf cannot mix absolute and relative paths in an entry");
+ continue;
+ }
+ if (addentry(tp, p, 0) == -1)
+ errx(EXIT_FAILURE,
+ "addentry: malloc failed");
+ }
+ }
+ }
+ (void)fclose(cfp);
+}
+
+/*
+ * gettag --
+ * if (!create) return tag for given name if it exists, or NULL otherwise
+ *
+ * if (create) return tag for given name if it exists, try and create
+ * a new tag if it does not exist. return NULL if unable to create new
+ * tag.
+ */
+TAG *
+gettag(const char *name, int create)
+{
+ TAG *tp;
+
+ TAILQ_FOREACH(tp, &head, q)
+ if (!strcmp(name, tp->s))
+ return tp;
+ if (!create)
+ return NULL;
+
+ /* try and add it in */
+ tp = malloc(sizeof(*tp));
+ if (tp)
+ tp->s = xstrdup(name, &tp->len);
+ if (!tp || !tp->s) {
+ if (tp)
+ free(tp);
+ return NULL;
+ }
+ TAILQ_INIT(&tp->entrylist);
+ TAILQ_INSERT_TAIL(&head, tp, q);
+ return tp;
+}
+
+/*
+ * addentry --
+ * add an entry to a list.
+ * returns -1 if malloc failed, otherwise 0.
+ */
+int
+addentry(TAG *tp, const char *newent, int ishead)
+{
+ ENTRY *ep;
+
+ ep = malloc(sizeof(*ep));
+ if (ep)
+ ep->s = xstrdup(newent, &ep->len);
+ if (!ep || !ep->s) {
+ if (ep)
+ free(ep);
+ return -1;
+ }
+ if (ishead)
+ TAILQ_INSERT_HEAD(&tp->entrylist, ep, q);
+ else
+ TAILQ_INSERT_TAIL(&tp->entrylist, ep, q);
+
+ return 0;
+}
diff --git a/usr.bin/man/manconf.h b/usr.bin/man/manconf.h
new file mode 100644
index 0000000..ab87728
--- /dev/null
+++ b/usr.bin/man/manconf.h
@@ -0,0 +1,60 @@
+/* $NetBSD: manconf.h,v 1.3 2006/04/10 14:39:06 chuck Exp $ */
+
+/*-
+ * Copyright (c) 1993
+ * The Regents of the University of California.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * @(#)config.h 8.4 (Berkeley) 12/18/93
+ */
+
+/*
+ * manconf.h: common data structures and APIs shared across all programs
+ * that access man.conf (currently: apropos, catman, makewhatis, man, and
+ * whatis).
+ */
+
+/* TAG: top-level structure (one per section/reserved word) */
+typedef struct _tag {
+ TAILQ_ENTRY(_tag) q; /* Queue of tags */
+
+ TAILQ_HEAD(tqh, _entry) entrylist; /* Queue of entries */
+ char *s; /* Associated string */
+ size_t len; /* Length of 's' */
+} TAG;
+
+/* ENTRY: each TAG has one or more ENTRY strings linked off of it */
+typedef struct _entry {
+ TAILQ_ENTRY(_entry) q; /* Queue of entries */
+
+ char *s; /* Associated string */
+ size_t len; /* Length of 's' */
+} ENTRY;
+
+int addentry(TAG *, const char *, int);
+void config(const char *);
+TAG *gettag(const char *, int);
diff --git a/usr.bin/man/pathnames.h b/usr.bin/man/pathnames.h
new file mode 100644
index 0000000..15aa211
--- /dev/null
+++ b/usr.bin/man/pathnames.h
@@ -0,0 +1,39 @@
+/* $NetBSD: pathnames.h,v 1.7 2016/05/21 20:54:34 christos Exp $ */
+
+/*
+ * Copyright (c) 1989, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * @(#)pathnames.h 8.3 (Berkeley) 1/2/94
+ */
+
+#include <paths.h>
+
+#define _PATH_MANCONF "/etc/man.conf"
+#define _PATH_PAGER "/usr/bin/more -s"
+#define _PATH_WHATIS "whatis.db"
+#define TMPFILE "man.XXXXXX"
diff --git a/usr.bin/mesg/mesg.1 b/usr.bin/mesg/mesg.1
new file mode 100644
index 0000000..e5d9100
--- /dev/null
+++ b/usr.bin/mesg/mesg.1
@@ -0,0 +1,89 @@
+.\" $NetBSD: mesg.1,v 1.10 2017/07/03 21:34:20 wiz Exp $
+.\"
+.\" Copyright (c) 1987, 1990, 1993
+.\" The Regents of the University of California. All rights reserved.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\" notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must reproduce the above copyright
+.\" notice, this list of conditions and the following disclaimer in the
+.\" documentation and/or other materials provided with the distribution.
+.\" 3. Neither the name of the University nor the names of its contributors
+.\" may be used to endorse or promote products derived from this software
+.\" without specific prior written permission.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+.\" ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+.\" SUCH DAMAGE.
+.\"
+.\" @(#)mesg.1 8.1 (Berkeley) 6/6/93
+.\"
+.Dd June 6, 1993
+.Dt MESG 1
+.Os
+.Sh NAME
+.Nm mesg
+.Nd display (do not display) messages from other users
+.Sh SYNOPSIS
+.Nm
+.Op Cm n | Cm y
+.Sh DESCRIPTION
+The
+.Nm
+utility is invoked by a user to control write access others
+have to the terminal device associated with the standard error
+output.
+Write access is allowed by default, and programs such as
+.Xr talk 1
+and
+.Xr write 1
+may display messages on the terminal.
+.Pp
+Options available:
+.Bl -tag -width flag
+.It Cm n
+Disallows messages.
+.It Cm y
+Permits messages to be displayed.
+.El
+.Pp
+If no arguments are given,
+.Nm
+displays the present message status to the standard error output.
+.Pp
+The
+.Nm
+utility exits with one of the following values:
+.Pp
+.Bl -tag -width flag -compact -offset indent
+.It Li "\ 0"
+Messages are allowed.
+.It Li "\ 1"
+Messages are not allowed.
+.It Li ">1"
+An error has occurred.
+.El
+.Sh FILES
+.Bl -tag -width /dev/[pt]ty[pq]? -compact
+.It Pa /dev/[pt]ty[pq]?
+.El
+.Sh SEE ALSO
+.Xr biff 1 ,
+.Xr talk 1 ,
+.Xr write 1
+.Sh HISTORY
+A
+.Nm
+command appeared in
+.At v6 .
diff --git a/usr.bin/mesg/mesg.c b/usr.bin/mesg/mesg.c
new file mode 100644
index 0000000..cc54a75
--- /dev/null
+++ b/usr.bin/mesg/mesg.c
@@ -0,0 +1,108 @@
+/* $NetBSD: mesg.c,v 1.8 2008/07/21 14:19:24 lukem Exp $ */
+
+/*
+ * Copyright (c) 1987, 1993
+ * The Regents of the University of California. All rights reserved.
+ * (c) UNIX System Laboratories, Inc.
+ * All or some portions of this file are derived from material licensed
+ * to the University of California by American Telephone and Telegraph
+ * Co. or Unix System Laboratories, Inc. and are reproduced herein with
+ * the permission of UNIX System Laboratories, Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+
+#ifndef lint
+__COPYRIGHT("@(#) Copyright (c) 1987, 1993\
+ The Regents of the University of California. All rights reserved.");
+#endif /* not lint */
+
+#ifndef lint
+#if 0
+static char sccsid[] = "@(#)mesg.c 8.2 (Berkeley) 1/21/94";
+#endif
+__RCSID("$NetBSD: mesg.c,v 1.8 2008/07/21 14:19:24 lukem Exp $");
+#endif /* not lint */
+
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#include <err.h>
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+int
+main(int argc, char *argv[])
+{
+ struct stat sb;
+ char *tty;
+ int ch;
+
+ setprogname(*argv);
+
+ while ((ch = getopt(argc, argv, "")) != -1)
+ switch (ch) {
+ case '?':
+ default:
+ goto usage;
+ }
+ argc -= optind;
+ argv += optind;
+
+ if ((tty = ttyname(STDIN_FILENO)) == NULL &&
+ (tty = ttyname(STDOUT_FILENO)) == NULL &&
+ (tty = ttyname(STDERR_FILENO)) == NULL)
+ err(2, "ttyname");
+ if (stat(tty, &sb) == -1)
+ err(2, "%s", tty);
+
+ if (*argv == NULL) {
+ if (sb.st_mode & S_IWGRP) {
+ (void)fprintf(stderr, "is y\n");
+ return 0;
+ }
+ (void)fprintf(stderr, "is n\n");
+ return 1;
+ }
+
+ switch (*argv[0]) {
+ case 'y':
+ if (chmod(tty, sb.st_mode | S_IWGRP) == -1)
+ err(2, "%s", tty);
+ return 0;
+ case 'n':
+ if (chmod(tty, sb.st_mode & ~S_IWGRP) == -1)
+ err(2, "%s", tty);
+ return 1;
+ }
+
+usage: (void)fprintf(stderr, "Usage: %s [y | n]\n", getprogname());
+ return 2;
+}
diff --git a/usr.bin/mkfifo/mkfifo.1 b/usr.bin/mkfifo/mkfifo.1
new file mode 100644
index 0000000..a178dc2
--- /dev/null
+++ b/usr.bin/mkfifo/mkfifo.1
@@ -0,0 +1,86 @@
+.\" $NetBSD: mkfifo.1,v 1.13 2017/07/04 07:03:32 wiz Exp $
+.\"
+.\" Copyright (c) 1990, 1993
+.\" The Regents of the University of California. All rights reserved.
+.\"
+.\" This code is derived from software contributed to Berkeley by
+.\" the Institute of Electrical and Electronics Engineers, Inc.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\" notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must reproduce the above copyright
+.\" notice, this list of conditions and the following disclaimer in the
+.\" documentation and/or other materials provided with the distribution.
+.\" 3. Neither the name of the University nor the names of its contributors
+.\" may be used to endorse or promote products derived from this software
+.\" without specific prior written permission.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+.\" ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+.\" SUCH DAMAGE.
+.\"
+.\" @(#)mkfifo.1 8.2 (Berkeley) 1/5/94
+.\"
+.Dd January 5, 1994
+.Dt MKFIFO 1
+.Os
+.Sh NAME
+.Nm mkfifo
+.Nd make fifos
+.Sh SYNOPSIS
+.Nm
+.Op Fl m Ar mode
+.Ar fifo_name ...
+.Sh DESCRIPTION
+.Nm
+creates the fifos requested, in the order specified,
+using mode
+.Li \&0666
+modified by the current
+.Xr umask 2 .
+.Pp
+The options are as follows:
+.Bl -tag -width Ds
+.It Fl m
+Set the file permission bits of newly-created fifos to
+.Ar mode .
+The mode is specified as in
+.Xr chmod 1 .
+In symbolic mode strings, the
+.Dq +
+and
+.Dq -
+operators are interpreted relative to an assumed initial mode of
+.Dq a=rw
+.El
+.Pp
+.Nm
+requires write permission in the parent directory.
+.Sh EXIT STATUS
+.Ex -std
+.Sh SEE ALSO
+.Xr mkdir 1 ,
+.Xr rm 1 ,
+.Xr mkfifo 2 ,
+.Xr mknod 8
+.Sh STANDARDS
+The
+.Nm
+utility is expected to be
+.St -p1003.2-92
+compliant.
+.Sh HISTORY
+.Nm
+command appeared in
+.Bx 4.4 .
diff --git a/usr.bin/mkfifo/mkfifo.c b/usr.bin/mkfifo/mkfifo.c
new file mode 100644
index 0000000..9d49ec5
--- /dev/null
+++ b/usr.bin/mkfifo/mkfifo.c
@@ -0,0 +1,107 @@
+/* $NetBSD: mkfifo.c,v 1.13 2011/09/04 20:30:34 joerg Exp $ */
+
+/*
+ * Copyright (c) 1990, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+#ifndef lint
+__COPYRIGHT("@(#) Copyright (c) 1990, 1993\
+ The Regents of the University of California. All rights reserved.");
+#endif /* not lint */
+
+#ifndef lint
+#if 0
+static char sccsid[] = "@(#)mkfifo.c 8.2 (Berkeley) 1/5/94";
+#endif
+__RCSID("$NetBSD: mkfifo.c,v 1.13 2011/09/04 20:30:34 joerg Exp $");
+#endif /* not lint */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <locale.h>
+#include <errno.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <err.h>
+
+__dead static void usage(void);
+
+int
+main(int argc, char *argv[])
+{
+ int ch, exitval;
+ void *set;
+ mode_t mode;
+
+ setlocale (LC_ALL, "");
+
+ /* The default mode is the value of the bitwise inclusive or of
+ S_IRUSR, S_IWUSR, S_IRGRP, S_IWGRP, S_IROTH, and S_IWOTH
+ modified by the file creation mask */
+ mode = 0666 & ~umask(0);
+
+ while ((ch = getopt(argc, argv, "m:")) != -1)
+ switch(ch) {
+ case 'm':
+ if (!(set = setmode(optarg))) {
+ err(1, "Cannot set file mode `%s'", optarg);
+ /* NOTREACHED */
+ }
+ /* In symbolic mode strings, the + and - operators are
+ interpreted relative to an assumed initial mode of
+ a=rw. */
+ mode = getmode(set, 0666);
+ free(set);
+ break;
+ case '?':
+ default:
+ usage();
+ }
+ argc -= optind;
+ argv += optind;
+ if (argv[0] == NULL)
+ usage();
+
+ for (exitval = 0; *argv; ++argv) {
+ if (mkfifo(*argv, mode) < 0) {
+ warn("%s", *argv);
+ exitval = 1;
+ }
+ }
+ exit(exitval);
+}
+
+static void
+usage(void)
+{
+ (void)fprintf(stderr, "usage: mkfifo [-m mode] fifoname ...\n");
+ exit(1);
+}
diff --git a/usr.bin/mkstr/mkstr.1 b/usr.bin/mkstr/mkstr.1
new file mode 100644
index 0000000..5fd812d
--- /dev/null
+++ b/usr.bin/mkstr/mkstr.1
@@ -0,0 +1,131 @@
+.\" $NetBSD: mkstr.1,v 1.13 2017/07/03 21:34:20 wiz Exp $
+.\"
+.\" Copyright (c) 1980, 1990, 1993
+.\" The Regents of the University of California. All rights reserved.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\" notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must reproduce the above copyright
+.\" notice, this list of conditions and the following disclaimer in the
+.\" documentation and/or other materials provided with the distribution.
+.\" 3. Neither the name of the University nor the names of its contributors
+.\" may be used to endorse or promote products derived from this software
+.\" without specific prior written permission.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+.\" ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+.\" SUCH DAMAGE.
+.\"
+.\" @(#)mkstr.1 8.1 (Berkeley) 6/6/93
+.\"
+.Dd June 6, 1993
+.Dt MKSTR 1
+.Os
+.Sh NAME
+.Nm mkstr
+.Nd create an error message file by massaging C source
+.Sh SYNOPSIS
+.Nm
+.Op Fl
+.Ar messagefile
+.Ar prefix file ...
+.Sh DESCRIPTION
+.Nm
+creates files containing error messages extracted from C source,
+and restructures the same C source, to use the created error message
+file.
+The intent of
+.Nm
+was to reduce the size of large programs and
+reduce swapping (see
+.Sx BUGS
+section below).
+.Pp
+.Nm
+processes each of the specified
+.Ar files ,
+placing a restructured version of the input in a file whose name
+consists of the specified
+.Ar prefix
+and the original name.
+A typical usage of
+.Nm
+is
+.Bd -literal -offset indent
+mkstr pistrings xx *.c
+.Ed
+.Pp
+This command causes all the error messages from the C source
+files in the current directory to be placed in the file
+.Ar pistrings
+and restructured copies of the sources to be placed in
+files whose names are prefixed with
+.Ar \&xx .
+.Pp
+Options:
+.Bl -tag -width indent
+.It Fl
+Error messages are placed at the end of the specified
+message file for recompiling part of a large
+.Nm
+ed
+program.
+.El
+.Pp
+.Nm
+finds error messages in the source by
+searching for the string
+.Li \&`error("'
+in the input stream.
+Each time it occurs, the C string starting at the
+.Sq \&"\&
+is stored
+in the message file followed by a null character and a new-line character;
+The new source is restructured with
+.Xr lseek 2
+pointers into the error message file for retrieval.
+.Bd -literal -offset indent
+char efilname = "/usr/lib/pi_strings";
+int efil = -1;
+
+error(a1, a2, a3, a4)
+\&{
+ char buf[256];
+
+ if (efil < 0) {
+ efil = open(efilname, 0);
+ if (efil < 0) {
+oops:
+ perror(efilname);
+ exit 1 ;
+ }
+ }
+ if (lseek(efil, a1, 0) < 0 || read(efil, buf, 256) \*[Le] 0)
+ goto oops;
+ printf(buf, a2, a3, a4);
+}
+.Ed
+.Sh SEE ALSO
+.Xr xstr 1 ,
+.Xr lseek 2
+.Sh HISTORY
+.Nm
+appeared in
+.Bx 1 .
+.Sh BUGS
+.Nm
+was intended for the limited architecture of the PDP-11 family.
+Very few programs actually use it.
+It is not an efficient method, the error messages
+should be stored in the program text.
diff --git a/usr.bin/mkstr/mkstr.c b/usr.bin/mkstr/mkstr.c
new file mode 100644
index 0000000..0bfe6ca
--- /dev/null
+++ b/usr.bin/mkstr/mkstr.c
@@ -0,0 +1,320 @@
+/* $NetBSD: mkstr.c,v 1.17 2016/09/05 00:40:29 sevan Exp $ */
+
+/*
+ * Copyright (c) 1980, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+#ifndef lint
+__COPYRIGHT("@(#) Copyright (c) 1980, 1993\
+ The Regents of the University of California. All rights reserved.");
+#endif /* not lint */
+
+#ifndef lint
+#if 0
+static char sccsid[] = "@(#)mkstr.c 8.1 (Berkeley) 6/6/93";
+#else
+__RCSID("$NetBSD: mkstr.c,v 1.17 2016/09/05 00:40:29 sevan Exp $");
+#endif
+#endif /* not lint */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#define ungetchar(c) ungetc(c, stdin)
+
+/*
+ * mkstr - create a string error message file by massaging C source
+ *
+ * Bill Joy UCB August 1977
+ *
+ * Modified March 1978 to hash old messages to be able to recompile
+ * without addding messages to the message file (usually)
+ *
+ * Based on an earlier program conceived by Bill Joy and Chuck Haley
+ *
+ * Program to create a string error message file
+ * from a group of C programs. Arguments are the name
+ * of the file where the strings are to be placed, the
+ * prefix of the new files where the processed source text
+ * is to be placed, and the files to be processed.
+ *
+ * The program looks for 'error("' in the source stream.
+ * Whenever it finds this, the following characters from the '"'
+ * to a '"' are replaced by 'seekpt' where seekpt is a
+ * pointer into the error message file.
+ * If the '(' is not immediately followed by a '"' no change occurs.
+ *
+ * The optional '-' causes strings to be added at the end of the
+ * existing error message file for recompilation of single routines.
+ */
+
+
+FILE *mesgread, *mesgwrite;
+char *progname;
+const char usagestr[] = "usage: %s [ - ] mesgfile prefix file ...\n";
+char name[100], *np;
+
+void process(void);
+int match(const char *);
+int octdigit(char);
+void inithash(void);
+long hashit(const char *, char, long);
+void copystr(void);
+int fgetNUL(char *, int, FILE *);
+
+int
+main(int argc, char *argv[])
+{
+ char addon = 0;
+
+ argc--, progname = *argv++;
+ if (argc > 1 && argv[0][0] == '-')
+ addon++, argc--, argv++;
+ if (argc < 3)
+ fprintf(stderr, usagestr, progname), exit(1);
+ mesgwrite = fopen(argv[0], addon ? "a" : "w");
+ if (mesgwrite == NULL)
+ perror(argv[0]), exit(1);
+ mesgread = fopen(argv[0], "r");
+ if (mesgread == NULL)
+ perror(argv[0]), exit(1);
+ inithash();
+ argc--, argv++;
+ strlcpy(name, argv[0], sizeof(name));
+ np = name + strlen(name);
+ argc--, argv++;
+ do {
+ strlcpy(np, argv[0], sizeof(name) - (np - name));
+ if (freopen(name, "w", stdout) == NULL)
+ perror(name), exit(1);
+ if (freopen(argv[0], "r", stdin) == NULL)
+ perror(argv[0]), exit(1);
+ process();
+ argc--, argv++;
+ } while (argc > 0);
+ exit(0);
+}
+
+void
+process(void)
+{
+ int c;
+
+ for (;;) {
+ c = getchar();
+ if (c == EOF)
+ return;
+ if (c != 'e') {
+ putchar(c);
+ continue;
+ }
+ if (match("error(")) {
+ printf("error(");
+ c = getchar();
+ if (c != '"')
+ putchar(c);
+ else
+ copystr();
+ }
+ }
+}
+
+int
+match(const char *ocp)
+{
+ const char *cp;
+ int c;
+
+ for (cp = ocp + 1; *cp; cp++) {
+ c = getchar();
+ if (c != *cp) {
+ while (ocp < cp)
+ putchar(*ocp++);
+ ungetchar(c);
+ return (0);
+ }
+ }
+ return (1);
+}
+
+void
+copystr(void)
+{
+ int c, ch;
+ char buf[512];
+ char *cp = buf;
+
+ for (;;) {
+ c = getchar();
+ if (c == EOF)
+ break;
+ switch (c) {
+
+ case '"':
+ *cp++ = 0;
+ goto out;
+ case '\\':
+ c = getchar();
+ switch (c) {
+
+ case 'b':
+ c = '\b';
+ break;
+ case 't':
+ c = '\t';
+ break;
+ case 'r':
+ c = '\r';
+ break;
+ case 'n':
+ c = '\n';
+ break;
+ case '\n':
+ continue;
+ case 'f':
+ c = '\f';
+ break;
+ case '0':
+ c = 0;
+ break;
+ case '\\':
+ break;
+ default:
+ if (!octdigit(c))
+ break;
+ c -= '0';
+ ch = getchar();
+ if (!octdigit(ch))
+ break;
+ c <<= 7, c += ch - '0';
+ ch = getchar();
+ if (!octdigit(ch))
+ break;
+ c <<= 3, c+= ch - '0', ch = -1;
+ break;
+ }
+ }
+ *cp++ = c;
+ }
+out:
+ *cp = 0;
+ printf("%ld", hashit(buf, 1, 0));
+}
+
+int
+octdigit(char c)
+{
+
+ return (c >= '0' && c <= '7');
+}
+
+void
+inithash(void)
+{
+ char buf[512];
+ long mesgpt = 0;
+
+ rewind(mesgread);
+ while (fgetNUL(buf, sizeof buf, mesgread) != 0) {
+ hashit(buf, 0, mesgpt);
+ mesgpt += strlen(buf) + 2;
+ }
+}
+
+#define NBUCKETS 511
+
+struct hash {
+ long hval;
+ long hpt;
+ struct hash *hnext;
+} *bucket[NBUCKETS];
+
+long
+hashit(const char *str, char really, long fakept)
+{
+ int i;
+ struct hash *hp;
+ char buf[512];
+ long hashval = 0;
+ const char *cp;
+
+#ifdef __GNUC__
+ hp = NULL; /* XXX gcc */
+#endif
+ if (really)
+ fflush(mesgwrite);
+ for (cp = str; *cp;)
+ hashval = (hashval << 1) + *cp++;
+ i = hashval % NBUCKETS;
+ if (i < 0)
+ i += NBUCKETS;
+ if (really != 0)
+ for (hp = bucket[i]; hp != 0; hp = hp->hnext)
+ if (hp->hval == hashval) {
+ fseek(mesgread, hp->hpt, 0);
+ fgetNUL(buf, sizeof buf, mesgread);
+/*
+ fprintf(stderr, "Got (from %ld) %s\n", hp->hpt, buf);
+*/
+ if (strcmp(buf, str) == 0)
+ break;
+ }
+ if (!really || hp == 0) {
+ hp = (struct hash *) calloc(1, sizeof *hp);
+ hp->hnext = bucket[i];
+ hp->hval = hashval;
+ hp->hpt = really ? ftell(mesgwrite) : fakept;
+ if (really) {
+ fwrite(str, sizeof (char), strlen(str) + 1, mesgwrite);
+ fwrite("\n", sizeof (char), 1, mesgwrite);
+ }
+ bucket[i] = hp;
+ }
+/*
+ fprintf(stderr, "%s hashed to %ld at %ld\n", str, hp->hval, hp->hpt);
+*/
+ return (hp->hpt);
+}
+
+#include <sys/types.h>
+#include <sys/stat.h>
+
+int
+fgetNUL(char *obuf, int rmdr, FILE *file)
+{
+ int c;
+ char *buf = obuf;
+
+ while (--rmdr > 0 && (c = getc(file)) != 0 && c != EOF)
+ *buf++ = c;
+ *buf++ = 0;
+ getc(file);
+ return ((feof(file) || ferror(file)) ? 0 : 1);
+}
diff --git a/usr.bin/newgrp/grutil.c b/usr.bin/newgrp/grutil.c
new file mode 100644
index 0000000..ee81590
--- /dev/null
+++ b/usr.bin/newgrp/grutil.c
@@ -0,0 +1,338 @@
+/* $NetBSD: grutil.c,v 1.4 2014/06/23 06:57:31 shm Exp $ */
+
+/*-
+ * Copyright (c) 2007 The NetBSD Foundation, Inc.
+ * All rights reserved.
+ *
+ * This code is derived from software contributed to The NetBSD Foundation
+ * by Brian Ginsbach.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
+ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+#include <sys/cdefs.h>
+__RCSID("$NetBSD: grutil.c,v 1.4 2014/06/23 06:57:31 shm Exp $");
+
+#include <sys/param.h>
+#include <err.h>
+#include <errno.h>
+#include <grp.h>
+#include <pwd.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <util.h>
+
+#ifdef LOGIN_CAP
+#include <login_cap.h>
+#endif
+
+#include "grutil.h"
+
+typedef enum {
+ ADDGRP_NOERROR = 0, /* must be zero */
+ ADDGRP_EMALLOC = 1,
+ ADDGRP_EGETGROUPS = 2,
+ ADDGRP_ESETGROUPS = 3
+} addgrp_ret_t;
+
+static void
+free_groups(void *groups)
+{
+ int oerrno;
+
+ oerrno = errno;
+ free(groups);
+ errno = oerrno;
+}
+
+static addgrp_ret_t
+alloc_groups(int *ngroups, gid_t **groups, int *ngroupsmax)
+{
+ *ngroupsmax = (int)sysconf(_SC_NGROUPS_MAX);
+ if (*ngroupsmax < 0)
+ *ngroupsmax = NGROUPS_MAX;
+
+ *groups = malloc(*ngroupsmax * sizeof(**groups));
+ if (*groups == NULL)
+ return ADDGRP_EMALLOC;
+
+ *ngroups = getgroups(*ngroupsmax, *groups);
+ if (*ngroups == -1) {
+ free_groups(*groups);
+ return ADDGRP_ESETGROUPS;
+ }
+ return ADDGRP_NOERROR;
+}
+
+static addgrp_ret_t
+addgid(gid_t *groups, int ngroups, int ngroupsmax, gid_t gid, int makespace)
+{
+ int i;
+
+ /* search for gid in supplemental group list */
+ for (i = 0; i < ngroups && groups[i] != gid; i++)
+ continue;
+
+ /* add the gid to the supplemental group list */
+ if (i == ngroups) {
+ if (ngroups < ngroupsmax)
+ groups[ngroups++] = gid;
+ else { /*
+ * setgroups(2) will fail with errno = EINVAL
+ * if ngroups > nmaxgroups. If makespace is
+ * set, replace the last group with the new
+ * one. Otherwise, fail the way setgroups(2)
+ * would if we passed the larger groups array.
+ */
+ if (makespace) {
+ /*
+ * Find a slot that doesn't contain
+ * the primary group.
+ */
+ struct passwd *pwd;
+ gid_t pgid;
+ pwd = getpwuid(getuid());
+ if (pwd == NULL)
+ goto error;
+ pgid = pwd->pw_gid;
+ for (i = ngroupsmax - 1; i >= 0; i--)
+ if (groups[i] != pgid)
+ break;
+ if (i < 0)
+ goto error;
+ groups[i] = gid;
+ }
+ else {
+ error:
+ errno = EINVAL;
+ return ADDGRP_ESETGROUPS;
+ }
+ }
+ if (setgroups(ngroups, groups) < 0)
+ return ADDGRP_ESETGROUPS;
+ }
+ return ADDGRP_NOERROR;
+}
+
+static addgrp_ret_t
+addgrp(gid_t newgid, int makespace)
+{
+ int ngroups, ngroupsmax;
+ addgrp_ret_t rval;
+ gid_t *groups;
+ gid_t oldgid;
+
+ oldgid = getgid();
+ if (oldgid == newgid) /* nothing to do */
+ return ADDGRP_NOERROR;
+
+ rval = alloc_groups(&ngroups, &groups, &ngroupsmax);
+ if (rval != ADDGRP_NOERROR)
+ return rval;
+
+ /*
+ * BSD based systems normally have the egid in the supplemental
+ * group list.
+ */
+#if (defined(BSD) && BSD >= 199306)
+ /*
+ * According to POSIX/XPG6:
+ * On system where the egid is normally in the supplemental group list
+ * (or whenever the old egid actually is in the supplemental group
+ * list):
+ * o If the new egid is in the supplemental group list,
+ * just change the egid.
+ * o If the new egid is not in the supplemental group list,
+ * add the new egid to the list if there is room.
+ */
+
+ rval = addgid(groups, ngroups, ngroupsmax, newgid, makespace);
+#else
+ /*
+ * According to POSIX/XPG6:
+ * On systems where the egid is not normally in the supplemental group
+ * list (or whenever the old egid is not in the supplemental group
+ * list):
+ * o If the new egid is in the supplemental group list, delete
+ * it from the list.
+ * o If the old egid is not in the supplemental group list,
+ * add the old egid to the list if there is room.
+ */
+ {
+ int i;
+
+ /* search for new egid in supplemental group list */
+ for (i = 0; i < ngroups && groups[i] != newgid; i++)
+ continue;
+
+ /* remove new egid from supplemental group list */
+ if (i != ngroups)
+ for (--ngroups; i < ngroups; i++)
+ groups[i] = groups[i + 1];
+
+ rval = addgid(groups, ngroups, ngroupsmax, oldgid, makespace);
+ }
+#endif
+ free_groups(groups);
+ return rval;
+}
+
+/*
+ * If newgrp fails, it returns (gid_t)-1 and the errno variable is
+ * set to:
+ * [EINVAL] Unknown group.
+ * [EPERM] Bad password.
+ */
+static gid_t
+newgrp(const char *gname, struct passwd *pwd, uid_t ruid, const char *prompt)
+{
+ struct group *grp;
+ char **ap;
+ char *p;
+ gid_t *groups;
+ int ngroups, ngroupsmax;
+
+ if (gname == NULL)
+ return pwd->pw_gid;
+
+ grp = getgrnam(gname);
+
+#ifdef GRUTIL_ACCEPT_GROUP_NUMBERS
+ if (grp == NULL) {
+ gid_t gid;
+ if (*gname != '-') {
+ gid = (gid_t)strtol(gname, &p, 10);
+ if (*p == '\0')
+ grp = getgrgid(gid);
+ }
+ }
+#endif
+ if (grp == NULL) {
+ errno = EINVAL;
+ return (gid_t)-1;
+ }
+
+ if (ruid == 0 || pwd->pw_gid == grp->gr_gid)
+ return grp->gr_gid;
+
+ if (alloc_groups(&ngroups, &groups, &ngroupsmax) == ADDGRP_NOERROR) {
+ int i;
+ for (i = 0; i < ngroups; i++)
+ if (groups[i] == grp->gr_gid) {
+ free_groups(groups);
+ return grp->gr_gid;
+ }
+ free_groups(groups);
+ }
+
+ /*
+ * Check the group membership list in case the groups[] array
+ * was maxed out or the user has been added to it since login.
+ */
+ for (ap = grp->gr_mem; *ap != NULL; ap++)
+ if (strcmp(*ap, pwd->pw_name) == 0)
+ return grp->gr_gid;
+
+ if (*grp->gr_passwd != '\0') {
+ p = getpass(prompt);
+ if (strcmp(grp->gr_passwd, crypt(p, grp->gr_passwd)) == 0) {
+ (void)memset(p, '\0', _PASSWORD_LEN);
+ return grp->gr_gid;
+ }
+ (void)memset(p, '\0', _PASSWORD_LEN);
+ }
+
+ errno = EPERM;
+ return (gid_t)-1;
+}
+
+#ifdef GRUTIL_SETGROUPS_MAKESPACE
+# define ADDGRP_MAKESPACE 1
+#else
+# define ADDGRP_MAKESPACE 0
+#endif
+
+#ifdef GRUTIL_ALLOW_GROUP_ERRORS
+# define maybe_exit(e)
+#else
+# define maybe_exit(e) exit(e);
+#endif
+
+void
+addgroup(
+#ifdef LOGIN_CAP
+ login_cap_t *lc,
+#endif
+ const char *gname, struct passwd *pwd, uid_t ruid, const char *prompt)
+{
+ pwd->pw_gid = newgrp(gname, pwd, ruid, prompt);
+ if (pwd->pw_gid == (gid_t)-1) {
+ switch (errno) {
+ case EINVAL:
+ warnx("Unknown group `%s'", gname);
+ maybe_exit(EXIT_FAILURE);
+ break;
+ case EPERM: /* password failure */
+ warnx("Sorry");
+ maybe_exit(EXIT_FAILURE);
+ break;
+ default: /* XXX - should never happen */
+ err(EXIT_FAILURE, "unknown error");
+ break;
+ }
+ pwd->pw_gid = getgid();
+ }
+
+ switch (addgrp(pwd->pw_gid, ADDGRP_MAKESPACE)) {
+ case ADDGRP_NOERROR:
+ break;
+ case ADDGRP_EMALLOC:
+ err(EXIT_FAILURE, "malloc");
+ break;
+ case ADDGRP_EGETGROUPS:
+ err(EXIT_FAILURE, "getgroups");
+ break;
+ case ADDGRP_ESETGROUPS:
+ switch(errno) {
+ case EINVAL:
+ warnx("setgroups: ngroups > ngroupsmax");
+ maybe_exit(EXIT_FAILURE);
+ break;
+ case EPERM:
+ case EFAULT:
+ default:
+ warn("setgroups");
+ maybe_exit(EXIT_FAILURE);
+ break;
+ }
+ break;
+ }
+
+#ifdef LOGIN_CAP
+ if (setusercontext(lc, pwd, pwd->pw_uid, LOGIN_SETGID) == -1)
+ err(EXIT_FAILURE, "setting user context");
+#else
+ if (setgid(pwd->pw_gid) == -1)
+ err(EXIT_FAILURE, "setgid");
+#endif
+}
diff --git a/usr.bin/newgrp/grutil.h b/usr.bin/newgrp/grutil.h
new file mode 100644
index 0000000..957f798
--- /dev/null
+++ b/usr.bin/newgrp/grutil.h
@@ -0,0 +1,40 @@
+/* $NetBSD: grutil.h,v 1.2 2008/04/28 20:24:14 martin Exp $ */
+
+/*-
+ * Copyright (c) 2007 The NetBSD Foundation, Inc.
+ * All rights reserved.
+ *
+ * This code is derived from software contributed to The NetBSD Foundation
+ * by Brian Ginsbach.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
+ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+#ifndef _GRUTIL_H_
+#define _GRUTIL_H_
+
+void addgroup(
+#ifdef LOGIN_CAP
+ login_cap_t *,
+#endif
+ const char *, struct passwd *, uid_t, const char *);
+
+#endif /* _GRUTIL_H_ */
diff --git a/usr.bin/newgrp/newgrp.1 b/usr.bin/newgrp/newgrp.1
new file mode 100644
index 0000000..4bd4785
--- /dev/null
+++ b/usr.bin/newgrp/newgrp.1
@@ -0,0 +1,121 @@
+.\" $NetBSD: newgrp.1,v 1.5 2017/07/03 21:34:20 wiz Exp $
+.\"
+.\" Copyright (c) 2007, The NetBSD Foundation.
+.\" All Rights Reserved.
+.\"
+.\" This code is derived from software contributed to The NetBSD Foundation
+.\" by Brian Ginsbach.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\" notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must reproduce the above copyright
+.\" notice, this list of conditions and the following disclaimer in the
+.\" documentation and/or other materials provided with the distribution.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
+.\" ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+.\" TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+.\" PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
+.\" BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+.\" CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+.\" SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+.\" INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+.\" CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+.\" ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+.\" POSSIBILITY OF SUCH DAMAGE.
+.\"
+.Dd June 6, 2007
+.Dt NEWGRP 1
+.Os
+.Sh NAME
+.Nm newgrp
+.Nd change to a new primary group
+.Sh SYNOPSIS
+.Nm
+.Op Fl l
+.Op Ar group
+.Sh DESCRIPTION
+The
+.Nm
+command changes a user to a new primary group
+.Pq real and effective group ID
+by starting a new shell.
+The user remains logged in and the current directory
+and file creation mask remain unchanged.
+The user is always given a new shell even if
+the primary group change fails.
+.Pp
+The
+.Nm
+command accepts the following options:
+.Bl -tag -width indent
+.It Fl l
+The environment is changed to what would be expected if the user
+actually logged in again.
+This simulates a full login.
+.El
+.Pp
+The
+.Ar group
+is a group name or non-negative numeric group ID from the group database.
+The real and effective group IDs are set to
+.Ar group
+or the group ID associated with the group name.
+.Pp
+If
+.Ar group
+is not specified,
+.Nm
+restores the user's real and effective group IDs to the user's
+primary group specified in the password database.
+The user's supplementary group IDs are restored to the set specified
+for the user in the group database.
+.Pp
+If the user is not a member of the specified group, and the group
+requires a password, the user will be prompted for the group password.
+.Sh FILES
+.Bl -tag -width /etc/master.passwd -compact
+.It Pa /etc/group
+The group database
+.It Pa /etc/master.passwd
+The user database
+.It Pa /etc/passwd
+A Version 7 format password file
+.El
+.Sh EXIT STATUS
+If a new shell is started the exit status is the exit status of the shell.
+Otherwise the exit status will be >0.
+.Sh SEE ALSO
+.Xr csh 1 ,
+.Xr groups 1 ,
+.Xr login 1 ,
+.Xr sh 1 ,
+.Xr su 1 ,
+.Xr umask 2 ,
+.Xr group 5 ,
+.Xr passwd 5 ,
+.Xr environ 7
+.Sh STANDARDS
+The
+.Nm
+command conforms to
+.St -p1003.1-2001 .
+.Sh HISTORY
+A
+.Nm
+command appeared in
+.At v6 .
+A
+.Nm
+command appeared in
+.Nx 5.0 .
+.Sh BUGS
+There is no convenient way to enter a password into
+.Pa /etc/group .
+The use of group passwords is strongly discouraged
+since they are inherently insecure.
+It is not possible to stop users from obtaining the encrypted
+password from the group database.
diff --git a/usr.bin/newgrp/newgrp.c b/usr.bin/newgrp/newgrp.c
new file mode 100644
index 0000000..61309c3
--- /dev/null
+++ b/usr.bin/newgrp/newgrp.c
@@ -0,0 +1,195 @@
+/*-
+ * Copyright (c) 2007 The NetBSD Foundation, Inc.
+ * All rights reserved.
+ *
+ * This code is derived from software contributed to The NetBSD Foundation
+ * by Brian Ginsbach.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
+ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+
+#ifndef lint
+__RCSID("$NetBSD: newgrp.c,v 1.7 2011/09/16 15:39:27 joerg Exp $");
+#endif /* not lint */
+
+#include <sys/param.h>
+#include <sys/types.h>
+
+#include <err.h>
+#include <grp.h>
+#include <libgen.h>
+#include <paths.h>
+#include <pwd.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#ifdef LOGIN_CAP
+#include <login_cap.h>
+#endif
+
+#include "grutil.h"
+
+__dead static void
+usage(void)
+{
+ (void)fprintf(stderr, "usage: %s [-l] [group]\n", getprogname());
+ exit(EXIT_FAILURE);
+}
+
+int
+main(int argc, char *argv[])
+{
+ extern char **environ;
+ struct passwd *pwd;
+ int c, lflag;
+ char *shell, sbuf[MAXPATHLEN + 2];
+ uid_t uid;
+#ifdef LOGIN_CAP
+ login_cap_t *lc;
+ u_int flags = LOGIN_SETUSER;
+#endif
+
+ uid = getuid();
+ pwd = getpwuid(uid);
+ if (pwd == NULL)
+ errx(EXIT_FAILURE, "who are you?");
+
+#ifdef LOGIN_CAP
+ if ((lc = login_getclass(pwd->pw_class)) == NULL)
+ errx(EXIT_FAILURE, "%s: unknown login class", pwd->pw_class);
+#endif
+
+ (void)setprogname(argv[0]);
+ lflag = 0;
+ while ((c = getopt(argc, argv, "-l")) != -1) {
+ switch (c) {
+ case '-':
+ case 'l':
+ if (lflag)
+ usage();
+ lflag = 1;
+ break;
+ default:
+ usage();
+ break;
+ }
+ }
+
+ argc -= optind;
+ argv += optind;
+
+ if (argc > 0) {
+#if 0
+ pwd->pw_gid = newgrp(*argv, pwd);
+ addgrp(pwd->pw_gid);
+ if (setgid(pwd->pw_gid) < 0)
+ err(1, "setgid");
+#endif
+#ifdef LOGIN_CAP
+ addgroup(lc, *argv, pwd, getuid(), "Password:");
+#else
+ addgroup(*argv, pwd, getuid(), "Password:");
+#endif
+ } else {
+#ifdef LOGIN_CAP
+ flags |= LOGIN_SETGROUP;
+#else
+ if (initgroups(pwd->pw_name, pwd->pw_gid) == -1)
+ err(EXIT_FAILURE, "initgroups");
+ if (setgid(pwd->pw_gid) == -1)
+ err(EXIT_FAILURE, "setgid");
+#endif
+ }
+
+#ifdef LOGIN_CAP
+ if (setusercontext(lc, pwd, uid, flags) == -1)
+ err(EXIT_FAILURE, "setusercontext");
+ if (!lflag)
+ login_close(lc);
+#else
+ if (setuid(pwd->pw_uid) == -1)
+ err(EXIT_FAILURE, "setuid");
+#endif
+
+ if (*pwd->pw_shell == '\0') {
+#ifdef TRUST_ENV_SHELL
+ shell = getenv("SHELL");
+ if (shell != NULL)
+ pwd->pw_shell = shell;
+ else
+#endif
+ pwd->pw_shell = __UNCONST(_PATH_BSHELL);
+ }
+
+ shell = pwd->pw_shell;
+
+ if (lflag) {
+ char *term;
+#ifdef KERBEROS
+ char *krbtkfile;
+#endif
+
+ if (chdir(pwd->pw_dir) == -1)
+ warn("%s", pwd->pw_dir);
+
+ term = getenv("TERM");
+#ifdef KERBEROS
+ krbtkfile = getenv("KRBTKFILE");
+#endif
+
+ /* create an empty environment */
+ if ((environ = malloc(sizeof(char *))) == NULL)
+ err(EXIT_FAILURE, NULL);
+ environ[0] = NULL;
+#ifdef LOGIN_CAP
+ if (setusercontext(lc, pwd, uid, LOGIN_SETENV | LOGIN_SETPATH) == -1)
+ err(EXIT_FAILURE, "setusercontext");
+ login_close(lc);
+#else
+ (void)setenv("PATH", _PATH_DEFPATH, 1);
+#endif
+ if (term != NULL)
+ (void)setenv("TERM", term, 1);
+#ifdef KERBEROS
+ if (krbtkfile != NULL)
+ (void)setenv("KRBTKFILE", krbtkfile, 1);
+#endif
+
+ (void)setenv("LOGNAME", pwd->pw_name, 1);
+ (void)setenv("USER", pwd->pw_name, 1);
+ (void)setenv("HOME", pwd->pw_dir, 1);
+ (void)setenv("SHELL", pwd->pw_shell, 1);
+
+ sbuf[0] = '-';
+ (void)strlcpy(sbuf + 1, basename(pwd->pw_shell),
+ sizeof(sbuf) - 1);
+ shell = sbuf;
+ }
+
+ (void)execl(pwd->pw_shell, shell, NULL);
+ err(EXIT_FAILURE, "%s", pwd->pw_shell);
+ /* NOTREACHED */
+}
diff --git a/usr.bin/nice/nice.1 b/usr.bin/nice/nice.1
new file mode 100644
index 0000000..87c211a
--- /dev/null
+++ b/usr.bin/nice/nice.1
@@ -0,0 +1,120 @@
+.\" $NetBSD: nice.1,v 1.15 2012/03/22 07:58:19 wiz Exp $
+.\"
+.\" Copyright (c) 1980, 1990, 1993
+.\" The Regents of the University of California. All rights reserved.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\" notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must reproduce the above copyright
+.\" notice, this list of conditions and the following disclaimer in the
+.\" documentation and/or other materials provided with the distribution.
+.\" 3. Neither the name of the University nor the names of its contributors
+.\" may be used to endorse or promote products derived from this software
+.\" without specific prior written permission.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+.\" ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+.\" SUCH DAMAGE.
+.\"
+.\" @(#)nice.1 8.1 (Berkeley) 6/6/93
+.\"
+.Dd June 6, 1993
+.Dt NICE 1
+.Os
+.Sh NAME
+.Nm nice
+.Nd execute a utility with an altered scheduling priority
+.Sh SYNOPSIS
+.Nm
+.Op Fl n Ar increment
+.Ar utility
+.Op Ar argument ...
+.Sh DESCRIPTION
+.Nm
+runs
+.Ar utility
+at an altered scheduling priority.
+If an
+.Ar increment
+is given, it is used; otherwise
+an increment of 10 is assumed.
+The super-user can run utilities with priorities higher than normal by using
+a negative
+.Ar increment .
+The priority can be adjusted over a
+range of -20 (the highest) to 20 (the lowest).
+A priority of 19 or 20
+will prevent a process from taking any cycles from others at nice 0 or
+better.
+.Pp
+Available options:
+.Bl -tag -width indent
+.It Fl n Ar increment
+A positive or negative decimal integer used to modify the system scheduling
+priority of
+.Ar utility .
+.El
+.Sh EXIT STATUS
+The
+.Nm
+utility exits with one of the following values:
+.Bl -tag -width indent
+.It 1-125
+An error occurred in the
+.Nm
+utility.
+.It 126
+The
+.Ar utility
+was found but could not be invoked.
+.It 127
+The
+.Ar utility
+could not be found.
+.El
+.Pp
+Otherwise, the exit status of
+.Nm
+will be that of
+.Ar utility .
+.Sh COMPATIBILITY
+The historic
+.Fl Ns Ar increment
+option has been deprecated but is still supported in this implementation.
+.Sh SEE ALSO
+.Xr csh 1 ,
+.Xr getpriority 2 ,
+.Xr setpriority 2 ,
+.Xr renice 8
+.Sh STANDARDS
+The
+.Nm
+utility conforms to
+.St -p1003.2-92 .
+.Sh HISTORY
+A
+.Nm
+utility appeared in
+.At v6 .
+.Sh BUGS
+.Nm
+is built into
+.Xr csh 1
+with a slightly different syntax than described here.
+The form
+.Ql nice +10
+nices to positive nice, and
+.Ql nice \-10
+can be used
+by the super-user to give a process more of the processor.
diff --git a/usr.bin/nice/nice.c b/usr.bin/nice/nice.c
new file mode 100644
index 0000000..de5fe5b
--- /dev/null
+++ b/usr.bin/nice/nice.c
@@ -0,0 +1,123 @@
+/* $NetBSD: nice.c,v 1.15 2008/07/21 14:19:24 lukem Exp $ */
+
+/*
+ * Copyright (c) 1989 The Regents of the University of California.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+#ifndef lint
+__COPYRIGHT("@(#) Copyright (c) 1989\
+ The Regents of the University of California. All rights reserved.");
+#endif /* not lint */
+
+#ifndef lint
+#if 0
+static char sccsid[] = "@(#)nice.c 5.4 (Berkeley) 6/1/90";
+#endif
+__RCSID("$NetBSD: nice.c,v 1.15 2008/07/21 14:19:24 lukem Exp $");
+#endif /* not lint */
+
+#include <sys/time.h>
+#include <sys/resource.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <limits.h>
+#include <locale.h>
+#include <ctype.h>
+#include <errno.h>
+#include <err.h>
+#include <unistd.h>
+
+#define DEFNICE 10
+
+static void usage(void) __dead;
+
+int
+main(int argc, char **argv)
+{
+ char *ep;
+ int niceness = DEFNICE;
+ int c;
+ long tmp;
+
+ setprogname(argv[0]);
+ (void)setlocale(LC_ALL, "");
+
+ /* handle obsolete -number syntax */
+ if (argc > 1 && argv[1][0] == '-' &&
+ isdigit((unsigned char)argv[1][1])) {
+ niceness = atoi (argv[1] + 1);
+ argc--; argv++;
+ }
+
+ while ((c = getopt (argc, argv, "n:")) != -1) {
+ switch (c) {
+ case 'n':
+ errno = 0;
+ tmp = strtol(optarg, &ep, 10);
+ if (*ep != '\0' || tmp < INT_MIN || tmp > INT_MAX)
+ errx(EXIT_FAILURE, "invalid argument: `%s'",
+ optarg);
+ niceness = (int)tmp;
+ break;
+ default:
+ usage();
+ break;
+ }
+ }
+ argc -= optind;
+ argv += optind;
+
+ if (argc == 0)
+ usage();
+
+ errno = 0;
+ niceness += getpriority(PRIO_PROCESS, 0);
+ if (errno) {
+ err(EXIT_FAILURE, "getpriority");
+ /* NOTREACHED */
+ }
+ if (setpriority(PRIO_PROCESS, 0, niceness) == -1) {
+ warn("setpriority");
+ }
+
+ (void)execvp(argv[0], &argv[0]);
+ err((errno == ENOENT || errno == ENOTDIR) ? 127 : 126, "%s", argv[0]);
+ /* NOTREACHED */
+}
+
+static void
+usage(void)
+{
+ (void)fprintf(stderr,
+ "Usage: %s [ -n increment ] utility [ argument ...]\n",
+ getprogname());
+ exit(EXIT_FAILURE);
+}
diff --git a/usr.bin/nl/nl.1 b/usr.bin/nl/nl.1
new file mode 100644
index 0000000..d795390
--- /dev/null
+++ b/usr.bin/nl/nl.1
@@ -0,0 +1,214 @@
+.\" $NetBSD: nl.1,v 1.16 2013/09/24 22:08:06 wiz Exp $
+.\"
+.\" Copyright (c) 1999 The NetBSD Foundation, Inc.
+.\" All rights reserved.
+.\"
+.\" This code is derived from software contributed to The NetBSD Foundation
+.\" by Klaus Klein.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\" notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must reproduce the above copyright
+.\" notice, this list of conditions and the following disclaimer in the
+.\" documentation and/or other materials provided with the distribution.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
+.\" ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+.\" TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+.\" PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
+.\" BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+.\" CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+.\" SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+.\" INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+.\" CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+.\" ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+.\" POSSIBILITY OF SUCH DAMAGE.
+.\"
+.Dd May 1, 2013
+.Dt NL 1
+.Os
+.Sh NAME
+.Nm nl
+.Nd line numbering filter
+.Sh SYNOPSIS
+.Nm
+.Op Fl p
+.Op Fl b Ar type
+.Op Fl d Ar delim
+.Op Fl f Ar type
+.Op Fl h Ar type
+.Op Fl i Ar incr
+.Op Fl l Ar num
+.Op Fl n Ar format
+.Op Fl s Ar sep
+.Op Fl v Ar startnum
+.Op Fl w Ar width
+.Op Ar file
+.Sh DESCRIPTION
+The
+.Nm
+utility reads lines from the named
+.Ar file ,
+applies a configurable line numbering filter operation,
+and writes the result to the standard output.
+If
+.Ar file
+is a single dash
+.Pq Sq \&-
+or absent,
+.Nm
+reads from the standard input.
+.Pp
+The
+.Nm
+utility treats the text it reads in terms of logical pages.
+Unless specified otherwise, line numbering is reset at the start of each
+logical page.
+A logical page consists of a header, a body and a footer section; empty
+sections are valid.
+Different line numbering options are independently available for header,
+body and footer sections.
+.Pp
+The starts of logical page sections are signaled by input lines containing
+nothing but one of the following sequences of delimiter characters:
+.Bl -column "\e:\e:\e: " "header " -offset indent
+.It Em "Line" Ta Em "Start of"
+.It \e:\e:\e: header
+.It \e:\e: body
+.It \e: footer
+.El
+.Pp
+If the input does not contain any logical page section signaling directives,
+the text being read is assumed to consist of a single logical page body.
+.Pp
+The following options are available:
+.Bl -tag -width indent
+.It Fl b Ar type
+Specify the logical page body lines to be numbered.
+Recognized
+.Ar type
+arguments are:
+.Bl -tag -width pstringXX
+.It a
+Number all lines.
+.It t
+Number only non-empty lines.
+.It n
+No line numbering.
+.It p Ns Ar expr
+Number only those lines that contain the basic regular expression specified
+by
+.Ar expr .
+.El
+.Pp
+The default
+.Ar type
+for logical page body lines is t.
+.It Fl d Ar delim
+Specify the delimiter characters used to indicate the start of a logical
+page section in the input file.
+At most two characters may be specified; if only one character is specified,
+the first character is replaced and the second character remains unchanged.
+The default
+.Ar delim
+characters are ``\e:''.
+.It Fl f Ar type
+Specify the same as
+.Fl b Ar type
+except for logical page footer lines.
+The default
+.Ar type
+for logical page footer lines is n.
+.It Fl h Ar type
+Specify the same as
+.Fl b Ar type
+except for logical page header lines.
+The default
+.Ar type
+for logical page header lines is n.
+.It Fl i Ar incr
+Specify the increment value used to number logical page lines.
+The default
+.Ar incr
+value is 1.
+.It Fl l Ar num
+If numbering of all lines is specified for the current logical section
+using the corresponding
+.Fl b
+a,
+.Fl f
+a
+or
+.Fl h
+a
+option,
+specify the number of adjacent blank lines to be considered as one.
+For example,
+.Fl l
+2 results in only the second adjacent blank line being numbered.
+The default
+.Ar num
+value is 1.
+.It Fl n Ar format
+Specify the line numbering output format.
+Recognized
+.Ar format
+arguments are:
+.Pp
+.Bl -tag -width lnXX -compact -offset indent
+.It ln
+Left justified.
+.It rn
+Right justified, leading zeros suppressed.
+.It rz
+Right justified, leading zeros kept.
+.El
+.Pp
+The default
+.Ar format
+is rn.
+.It Fl p
+Specify that line numbering should not be restarted at logical page delimiters.
+.It Fl s Ar sep
+Specify the characters used in separating the line number and the corresponding
+text line.
+The default
+.Ar sep
+setting is a single tab character.
+.It Fl v Ar startnum
+Specify the initial value used to number logical page lines; see also the
+description of the
+.Fl p
+option.
+The default
+.Ar startnum
+value is 1.
+.It Fl w Ar width
+Specify the number of characters to be occupied by the line number;
+if the
+.Ar width
+is insufficient to hold the line number, it will be truncated to its
+.Ar width
+least significant digits.
+The default
+.Ar width
+is 6.
+.El
+.Sh EXIT STATUS
+.Ex -std
+.Sh SEE ALSO
+.Xr pr 1
+.Sh STANDARDS
+The
+.Nm
+utility is compliant with the
+.St -xpg4.2
+specification.
+.Sh HISTORY
+The
+.Nm
+utility first appeared in
+.At V.2 .
diff --git a/usr.bin/nl/nl.c b/usr.bin/nl/nl.c
new file mode 100644
index 0000000..14054ad
--- /dev/null
+++ b/usr.bin/nl/nl.c
@@ -0,0 +1,406 @@
+/* $NetBSD: nl.c,v 1.12 2013/09/17 20:00:50 wiz Exp $ */
+
+/*-
+ * Copyright (c) 1999 The NetBSD Foundation, Inc.
+ * All rights reserved.
+ *
+ * This code is derived from software contributed to The NetBSD Foundation
+ * by Klaus Klein.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
+ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+#ifndef lint
+__COPYRIGHT("@(#) Copyright (c) 1999\
+ The NetBSD Foundation, Inc. All rights reserved.");
+__RCSID("$NetBSD: nl.c,v 1.12 2013/09/17 20:00:50 wiz Exp $");
+#endif
+
+#include <errno.h>
+#include <limits.h>
+#include <locale.h>
+#include <regex.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <err.h>
+
+typedef enum {
+ number_all, /* number all lines */
+ number_nonempty, /* number non-empty lines */
+ number_none, /* no line numbering */
+ number_regex /* number lines matching regular expression */
+} numbering_type;
+
+struct numbering_property {
+ const char * const name; /* for diagnostics */
+ numbering_type type; /* numbering type */
+ regex_t expr; /* for type == number_regex */
+};
+
+/* line numbering formats */
+#define FORMAT_LN "%-*d" /* left justified, leading zeros suppressed */
+#define FORMAT_RN "%*d" /* right justified, leading zeros suppressed */
+#define FORMAT_RZ "%0*d" /* right justified, leading zeros kept */
+
+#define FOOTER 0
+#define BODY 1
+#define HEADER 2
+#define NP_LAST HEADER
+
+static struct numbering_property numbering_properties[NP_LAST + 1] = {
+ { "footer", number_none, { 0, 0, 0, 0 } },
+ { "body", number_nonempty, { 0, 0, 0, 0 } },
+ { "header", number_none, { 0, 0, 0, 0 } },
+};
+
+#define max(a, b) ((a) > (b) ? (a) : (b))
+
+/*
+ * Maximum number of characters required for a decimal representation of a
+ * (signed) int; courtesy of tzcode.
+ */
+#define INT_STRLEN_MAXIMUM \
+ ((sizeof (int) * CHAR_BIT - 1) * 302 / 1000 + 2)
+
+static void filter(void);
+static void parse_numbering(const char *, int);
+static void usage(void) __attribute__((__noreturn__));
+
+/*
+ * Pointer to dynamically allocated input line buffer, and its size.
+ */
+static char *buffer;
+static size_t buffersize;
+
+/*
+ * Dynamically allocated buffer suitable for string representation of ints.
+ */
+static char *intbuffer;
+static size_t intbuffersize;
+
+/*
+ * Configurable parameters.
+ */
+/* delimiter characters that indicate the start of a logical page section */
+static char delim[2] = { '\\', ':' };
+
+/* line numbering format */
+static const char *format = FORMAT_RN;
+
+/* increment value used to number logical page lines */
+static int incr = 1;
+
+/* number of adjacent blank lines to be considered (and numbered) as one */
+static unsigned int nblank = 1;
+
+/* whether to restart numbering at logical page delimiters */
+static int restart = 1;
+
+/* characters used in separating the line number and the corrsp. text line */
+static const char *sep = "\t";
+
+/* initial value used to number logical page lines */
+static int startnum = 1;
+
+/* number of characters to be used for the line number */
+/* should be unsigned but required signed by `*' precision conversion */
+static int width = 6;
+
+
+int
+main(int argc, char *argv[])
+{
+ int c;
+ long val;
+ unsigned long uval;
+ char *ep;
+
+ (void)setlocale(LC_ALL, "");
+
+ /*
+ * Note: this implementation strictly conforms to the XBD Utility
+ * Syntax Guidelines and does not permit the optional `file' operand
+ * to be intermingled with the options, which is defined in the
+ * XCU specification (Issue 5) but declared an obsolescent feature that
+ * will be removed from a future issue. It shouldn't matter, though.
+ */
+ while ((c = getopt(argc, argv, "pb:d:f:h:i:l:n:s:v:w:")) != -1) {
+ switch (c) {
+ case 'p':
+ restart = 0;
+ break;
+ case 'b':
+ parse_numbering(optarg, BODY);
+ break;
+ case 'd':
+ if (optarg[0] != '\0')
+ delim[0] = optarg[0];
+ if (optarg[1] != '\0')
+ delim[1] = optarg[1];
+ /* at most two delimiter characters */
+ if (optarg[2] != '\0') {
+ errx(EXIT_FAILURE,
+ "invalid delim argument -- %s",
+ optarg);
+ /* NOTREACHED */
+ }
+ break;
+ case 'f':
+ parse_numbering(optarg, FOOTER);
+ break;
+ case 'h':
+ parse_numbering(optarg, HEADER);
+ break;
+ case 'i':
+ errno = 0;
+ val = strtol(optarg, &ep, 10);
+ if ((ep != NULL && *ep != '\0') ||
+ ((val == LONG_MIN || val == LONG_MAX) && errno != 0))
+ errx(EXIT_FAILURE,
+ "invalid incr argument -- %s", optarg);
+ incr = (int)val;
+ break;
+ case 'l':
+ errno = 0;
+ uval = strtoul(optarg, &ep, 10);
+ if ((ep != NULL && *ep != '\0') ||
+ (uval == ULONG_MAX && errno != 0))
+ errx(EXIT_FAILURE,
+ "invalid num argument -- %s", optarg);
+ nblank = (unsigned int)uval;
+ break;
+ case 'n':
+ if (strcmp(optarg, "ln") == 0) {
+ format = FORMAT_LN;
+ } else if (strcmp(optarg, "rn") == 0) {
+ format = FORMAT_RN;
+ } else if (strcmp(optarg, "rz") == 0) {
+ format = FORMAT_RZ;
+ } else
+ errx(EXIT_FAILURE,
+ "illegal format -- %s", optarg);
+ break;
+ case 's':
+ sep = optarg;
+ break;
+ case 'v':
+ errno = 0;
+ val = strtol(optarg, &ep, 10);
+ if ((ep != NULL && *ep != '\0') ||
+ ((val == LONG_MIN || val == LONG_MAX) && errno != 0))
+ errx(EXIT_FAILURE,
+ "invalid startnum value -- %s", optarg);
+ startnum = (int)val;
+ break;
+ case 'w':
+ errno = 0;
+ val = strtol(optarg, &ep, 10);
+ if ((ep != NULL && *ep != '\0') ||
+ ((val == LONG_MIN || val == LONG_MAX) && errno != 0))
+ errx(EXIT_FAILURE,
+ "invalid width value -- %s", optarg);
+ width = (int)val;
+ if (!(width > 0))
+ errx(EXIT_FAILURE,
+ "width argument must be > 0 -- %d",
+ width);
+ break;
+ case '?':
+ default:
+ usage();
+ /* NOTREACHED */
+ }
+ }
+ argc -= optind;
+ argv += optind;
+
+ switch (argc) {
+ case 0:
+ break;
+ case 1:
+ if (strcmp(argv[0], "-") != 0 &&
+ freopen(argv[0], "r", stdin) == NULL)
+ err(EXIT_FAILURE, "Cannot open `%s'", argv[0]);
+ break;
+ default:
+ usage();
+ /* NOTREACHED */
+ }
+
+ /* Determine the maximum input line length to operate on. */
+ if ((val = sysconf(_SC_LINE_MAX)) == -1) /* ignore errno */
+ val = LINE_MAX;
+ /* Allocate sufficient buffer space (including the terminating NUL). */
+ buffersize = (size_t)val + 1;
+ if ((buffer = malloc(buffersize)) == NULL)
+ err(EXIT_FAILURE, "Cannot allocate input line buffer");
+
+ /* Allocate a buffer suitable for preformatting line number. */
+ intbuffersize = max((int)INT_STRLEN_MAXIMUM, width) + 1; /* NUL */
+ if ((intbuffer = malloc(intbuffersize)) == NULL)
+ err(EXIT_FAILURE, "cannot allocate preformatting buffer");
+
+ /* Do the work. */
+ filter();
+
+ return EXIT_SUCCESS;
+ /* NOTREACHED */
+}
+
+static void
+filter(void)
+{
+ int line; /* logical line number */
+ int section; /* logical page section */
+ unsigned int adjblank; /* adjacent blank lines */
+ int consumed; /* intbuffer measurement */
+ int donumber, idx;
+
+ adjblank = 0;
+ line = startnum;
+ section = BODY;
+#ifdef __GNUC__
+ donumber = 0; /* avoid bogus `uninitialized' warning */
+#endif
+
+ while (fgets(buffer, (int)buffersize, stdin) != NULL) {
+ for (idx = FOOTER; idx <= NP_LAST; idx++) {
+ /* Does it look like a delimiter? */
+ if (buffer[2 * idx + 0] == delim[0] &&
+ buffer[2 * idx + 1] == delim[1]) {
+ /* Was this the whole line? */
+ if (buffer[2 * idx + 2] == '\n') {
+ section = idx;
+ adjblank = 0;
+ if (restart)
+ line = startnum;
+ goto nextline;
+ }
+ } else {
+ break;
+ }
+ }
+
+ switch (numbering_properties[section].type) {
+ case number_all:
+ /*
+ * Doing this for number_all only is disputable, but
+ * the standard expresses an explicit dependency on
+ * `-b a' etc.
+ */
+ if (buffer[0] == '\n' && ++adjblank < nblank)
+ donumber = 0;
+ else
+ donumber = 1, adjblank = 0;
+ break;
+ case number_nonempty:
+ donumber = (buffer[0] != '\n');
+ break;
+ case number_none:
+ donumber = 0;
+ break;
+ case number_regex:
+ donumber =
+ (regexec(&numbering_properties[section].expr,
+ buffer, 0, NULL, 0) == 0);
+ break;
+ }
+
+ if (donumber) {
+ consumed = snprintf(intbuffer, intbuffersize, format,
+ width, line);
+ (void)printf("%s",
+ intbuffer + max(0, consumed - width));
+ line += incr;
+ } else {
+ (void)printf("%*s", width, "");
+ }
+ (void)printf("%s%s", sep, buffer);
+
+ if (ferror(stdout))
+ err(EXIT_FAILURE, "output error");
+nextline:
+ ;
+ }
+
+ if (ferror(stdin))
+ err(EXIT_FAILURE, "input error");
+}
+
+/*
+ * Various support functions.
+ */
+
+static void
+parse_numbering(const char *argstr, int section)
+{
+ int error;
+ char errorbuf[NL_TEXTMAX];
+
+ switch (argstr[0]) {
+ case 'a':
+ numbering_properties[section].type = number_all;
+ break;
+ case 'n':
+ numbering_properties[section].type = number_none;
+ break;
+ case 't':
+ numbering_properties[section].type = number_nonempty;
+ break;
+ case 'p':
+ /* If there was a previous expression, throw it away. */
+ if (numbering_properties[section].type == number_regex)
+ regfree(&numbering_properties[section].expr);
+ else
+ numbering_properties[section].type = number_regex;
+
+ /* Compile/validate the supplied regular expression. */
+ if ((error = regcomp(&numbering_properties[section].expr,
+ &argstr[1], REG_NEWLINE|REG_NOSUB)) != 0) {
+ (void)regerror(error,
+ &numbering_properties[section].expr,
+ errorbuf, sizeof (errorbuf));
+ errx(EXIT_FAILURE,
+ "%s expr: %s -- %s",
+ numbering_properties[section].name, errorbuf,
+ &argstr[1]);
+ }
+ break;
+ default:
+ errx(EXIT_FAILURE,
+ "illegal %s line numbering type -- %s",
+ numbering_properties[section].name, argstr);
+ exit(EXIT_FAILURE);
+ }
+}
+
+static void
+usage(void)
+{
+ (void)fprintf(stderr, "Usage: %s [-p] [-b type] [-d delim] [-f type] "
+ "[-h type] [-i incr] [-l num]\n\t[-n format] [-s sep] "
+ "[-v startnum] [-w width] [file]\n", getprogname());
+ exit(EXIT_FAILURE);
+}
diff --git a/usr.bin/nohup/nohup.1 b/usr.bin/nohup/nohup.1
new file mode 100644
index 0000000..71a76f1
--- /dev/null
+++ b/usr.bin/nohup/nohup.1
@@ -0,0 +1,115 @@
+.\" $NetBSD: nohup.1,v 1.15 2012/03/29 18:33:19 wiz Exp $
+.\"
+.\" Copyright (c) 1989, 1990, 1993
+.\" The Regents of the University of California. All rights reserved.
+.\"
+.\" This code is derived from software contributed to Berkeley by
+.\" the Institute of Electrical and Electronics Engineers, Inc.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\" notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must reproduce the above copyright
+.\" notice, this list of conditions and the following disclaimer in the
+.\" documentation and/or other materials provided with the distribution.
+.\" 3. Neither the name of the University nor the names of its contributors
+.\" may be used to endorse or promote products derived from this software
+.\" without specific prior written permission.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+.\" ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+.\" SUCH DAMAGE.
+.\"
+.\" @(#)nohup.1 8.1 (Berkeley) 6/6/93
+.\"
+.Dd July 15, 2005
+.Dt NOHUP 1
+.Os
+.Sh NAME
+.Nm nohup
+.Nd invoke a command immune to hangups
+.Sh SYNOPSIS
+.Nm
+.Ar utility
+.Op Ar arg ...
+.Sh DESCRIPTION
+The
+.Nm
+command allows the specified utility to be protected from termination
+if the user should become logged out
+(for example, due to a modem line or TCP/IP connection being dropped).
+To do this,
+.Nm
+sets the
+.Dv SIGHUP
+.Xr signal 3
+.Pq Dq terminal line hangup
+to be ignored,
+then executes
+.Ar utility
+along with any arguments.
+.Pp
+If the standard output is a terminal, the standard output is
+appended to the file
+.Pa nohup.out
+in the current directory.
+If standard error is a terminal, it is directed to the same place
+as the standard output.
+If the output file
+.Pa nohup.out
+cannot be created in the current directory,
+.Nm
+attempts to create the file in the user's home directory.
+If the file
+.Pa nohup.out
+cannot be created,
+either in the current directory or the user's home directory,
+.Nm
+will exit without invoking
+.Ar utility ,
+with an exit value as described below.
+.Sh ENVIRONMENT
+The following variable is used by
+.Nm :
+.Bl -tag -width flag
+.It Ev HOME
+User's home directory.
+.El
+.Sh EXIT STATUS
+The
+.Nm
+utility exits with one of the following values:
+.Bl -tag -width Ds
+.It 126
+The
+.Ar utility
+was found but could not be invoked.
+.It 127
+The
+.Ar utility
+could not be found or an error occurred in
+.Nm .
+.El
+.Pp
+Otherwise, the exit status of
+.Nm
+will be that of
+.Ar utility .
+.Sh SEE ALSO
+.Xr signal 3
+.Sh STANDARDS
+The
+.Nm
+command is expected to be
+.St -p1003.2
+compatible.
diff --git a/usr.bin/nohup/nohup.c b/usr.bin/nohup/nohup.c
new file mode 100644
index 0000000..d177c51
--- /dev/null
+++ b/usr.bin/nohup/nohup.c
@@ -0,0 +1,142 @@
+/* $NetBSD: nohup.c,v 1.15 2011/09/06 18:24:15 joerg Exp $ */
+
+/*
+ * Copyright (c) 1989 The Regents of the University of California.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+#ifndef lint
+__COPYRIGHT("@(#) Copyright (c) 1989\
+ The Regents of the University of California. All rights reserved.");
+#endif /* not lint */
+
+#ifndef lint
+#if 0
+static char sccsid[] = "@(#)nohup.c 5.4 (Berkeley) 6/1/90";
+#endif
+__RCSID("$NetBSD: nohup.c,v 1.15 2011/09/06 18:24:15 joerg Exp $");
+#endif /* not lint */
+
+#include <sys/param.h>
+#include <sys/file.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+
+static void dofile(void);
+__dead static void usage(void);
+
+/* nohup shall exit with one of the following values:
+ 126 - The utility was found but could not be invoked.
+ 127 - An error occurred in the nohup utility, or the utility could
+ not be found. */
+#define EXIT_NOEXEC 126
+#define EXIT_NOTFOUND 127
+#define EXIT_MISC 127
+
+int
+main(int argc, char **argv)
+{
+ int exit_status;
+
+ while (getopt(argc, argv, "") != -1) {
+ usage();
+ }
+ argc -= optind;
+ argv += optind;
+
+ if (argc < 1)
+ usage();
+
+ if (isatty(STDOUT_FILENO))
+ dofile();
+ if (isatty(STDERR_FILENO) && dup2(STDOUT_FILENO, STDERR_FILENO) == -1) {
+ /* may have just closed stderr */
+ (void)fprintf(stdin, "nohup: %s\n", strerror(errno));
+ exit(EXIT_MISC);
+ }
+
+ /* The nohup utility shall take the standard action for all signals
+ except that SIGHUP shall be ignored. */
+ (void)signal(SIGHUP, SIG_IGN);
+
+ execvp(argv[0], &argv[0]);
+ exit_status = (errno == ENOENT) ? EXIT_NOTFOUND : EXIT_NOEXEC;
+ (void)fprintf(stderr, "nohup: %s: %s\n", argv[0], strerror(errno));
+ exit(exit_status);
+}
+
+static void
+dofile(void)
+{
+ int fd;
+ char path[MAXPATHLEN];
+ const char *p;
+
+ /* If the standard output is a terminal, all output written to
+ its standard output shall be appended to the end of the file
+ nohup.out in the current directory. If nohup.out cannot be
+ created or opened for appending, the output shall be appended
+ to the end of the file nohup.out in the directory specified
+ by the HOME environment variable.
+
+ If a file is created, the file's permission bits shall be
+ set to S_IRUSR | S_IWUSR. */
+#define FILENAME "nohup.out"
+ p = FILENAME;
+ if ((fd = open(p, O_RDWR|O_CREAT|O_APPEND, S_IRUSR|S_IWUSR)) >= 0)
+ goto dupit;
+ if ((p = getenv("HOME")) != NULL) {
+ (void)strlcpy(path, p, sizeof(path));
+ (void)strlcat(path, "/", sizeof(path));
+ (void)strlcat(path, FILENAME, sizeof(path));
+ if ((fd = open(p = path, O_RDWR|O_CREAT|O_APPEND, S_IRUSR|S_IWUSR)) >= 0)
+ goto dupit;
+ }
+ (void)fprintf(stderr, "nohup: can't open a nohup.out file.\n");
+ exit(EXIT_MISC);
+
+dupit: (void)lseek(fd, 0L, SEEK_END);
+ if (dup2(fd, STDOUT_FILENO) == -1) {
+ (void)fprintf(stderr, "nohup: %s\n", strerror(errno));
+ exit(EXIT_MISC);
+ }
+ (void)fprintf(stderr, "sending output to %s\n", p);
+}
+
+static void
+usage(void)
+{
+ (void)fprintf(stderr, "usage: nohup utility [argument ...]\n");
+ exit(EXIT_MISC);
+}
diff --git a/usr.bin/paste/paste.1 b/usr.bin/paste/paste.1
new file mode 100644
index 0000000..d76ce5d
--- /dev/null
+++ b/usr.bin/paste/paste.1
@@ -0,0 +1,116 @@
+.\" $NetBSD: paste.1,v 1.9 2013/04/07 17:43:01 wiz Exp $
+.\"
+.\" Copyright (c) 1989, 1990, 1993
+.\" The Regents of the University of California. All rights reserved.
+.\"
+.\" This code is derived from software contributed to Berkeley by
+.\" Adam S. Moskowitz and the Institute of Electrical and Electronics
+.\" Engineers, Inc.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\" notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must reproduce the above copyright
+.\" notice, this list of conditions and the following disclaimer in the
+.\" documentation and/or other materials provided with the distribution.
+.\" 3. Neither the name of the University nor the names of its contributors
+.\" may be used to endorse or promote products derived from this software
+.\" without specific prior written permission.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+.\" ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+.\" SUCH DAMAGE.
+.\"
+.\" from: @(#)paste.1 8.1 (Berkeley) 6/6/93
+.\"
+.Dd June 6, 1993
+.Dt PASTE 1
+.Os
+.Sh NAME
+.Nm paste
+.Nd merge corresponding or subsequent lines of files
+.Sh SYNOPSIS
+.Nm
+.Op Fl s
+.Op Fl d Ar list
+.Ar file ...
+.Sh DESCRIPTION
+The
+.Nm
+utility concatenates the corresponding lines of the given input files,
+replacing all but the last file's newline characters with a single tab
+character, and writes the resulting lines to standard output.
+If end-of-file is reached on an input file while other input files
+still contain data, the file is treated as if it were an endless source
+of empty lines.
+.Pp
+The options are as follows:
+.Bl -tag -width Fl
+.It Fl d Ar list
+Use one or more of the provided characters to replace the newline
+characters instead of the default tab.
+The characters in
+.Ar list
+are used circularly, i.e., when
+.Ar list
+is exhausted the first character from
+.Ar list
+is reused.
+This continues until a line from the last input file (in default operation)
+or the last line in each file (using the -s option) is displayed, at which
+time
+.Nm
+begins selecting characters from the beginning of
+.Ar list
+again.
+.Pp
+The following special characters can also be used in list:
+.Pp
+.Bl -tag -width flag -compact
+.It Li \en
+newline character
+.It Li \et
+tab character
+.It Li \e\e
+backslash character
+.It Li \e0
+Empty string (not a null character).
+.El
+.Pp
+Any other character preceded by a backslash is equivalent to the
+character itself.
+.It Fl s
+Concatenate all of the lines of each separate input file in command line
+order.
+The newline character of every line except the last line in each input
+file is replaced with the tab character, unless otherwise specified by
+the -d option.
+.El
+.Pp
+If
+.Ql Fl
+is specified for one or more of the input files, the standard
+input is used; standard input is read one line at a time, circularly,
+for each instance of
+.Ql Fl .
+.Sh EXIT STATUS
+.Ex -std
+.Sh SEE ALSO
+.Xr cut 1 ,
+.Xr join 1
+.Sh STANDARDS
+The
+.Nm
+utility is expected to be
+.St -p1003.2
+compatible.
diff --git a/usr.bin/paste/paste.c b/usr.bin/paste/paste.c
new file mode 100644
index 0000000..5698131
--- /dev/null
+++ b/usr.bin/paste/paste.c
@@ -0,0 +1,233 @@
+/* $NetBSD: paste.c,v 1.16 2011/09/06 18:24:43 joerg Exp $ */
+
+/*
+ * Copyright (c) 1989, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Adam S. Moskowitz of Menlo Consulting.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+#ifndef lint
+__COPYRIGHT("@(#) Copyright (c) 1989, 1993\
+ The Regents of the University of California. All rights reserved.");
+#endif /* not lint */
+
+#ifndef lint
+/*static char sccsid[] = "from: @(#)paste.c 8.1 (Berkeley) 6/6/93";*/
+__RCSID("$NetBSD: paste.c,v 1.16 2011/09/06 18:24:43 joerg Exp $");
+#endif /* not lint */
+
+#include <sys/types.h>
+#include <err.h>
+#include <errno.h>
+#include <limits.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+static void parallel(int, char **);
+static void sequential(char **);
+static int tr(char *);
+__dead static void usage(void);
+
+static char dflt_delim[] = "\t";
+static char *delim = dflt_delim;
+static int delimcnt = 1;
+
+int
+main(int argc, char **argv)
+{
+ int ch, seq;
+
+ seq = 0;
+ while ((ch = getopt(argc, argv, "d:s")) != -1) {
+ switch (ch) {
+ case 'd':
+ delim = strdup(optarg);
+ delimcnt = tr(delim);
+ break;
+ case 's':
+ seq = 1;
+ break;
+ case '?':
+ default:
+ usage();
+ }
+ }
+ argc -= optind;
+ argv += optind;
+
+ if (seq)
+ sequential(argv);
+ else
+ parallel(argc, argv);
+ exit(0);
+}
+
+static void
+parallel(int argc, char **argv)
+{
+ char ch, *dp, *line;
+ FILE **fpp, *fp;
+ size_t line_len;
+ int cnt, output;
+
+ fpp = calloc(argc, sizeof *fpp);
+ if (fpp == NULL)
+ err(1, "calloc");
+
+ for (cnt = 0; cnt < argc; cnt++) {
+ if (strcmp(argv[cnt], "-") == 0)
+ fpp[cnt] = stdin;
+ else if (!(fpp[cnt] = fopen(argv[cnt], "r")))
+ err(1, "%s", argv[cnt]);
+ }
+
+ for (;;) {
+ /* Start with the NUL at the end of 'delim' ... */
+ dp = delim + delimcnt;
+ output = 0;
+ for (cnt = 0; cnt < argc; cnt++) {
+ fp = fpp[cnt];
+ if (fp == NULL)
+ continue;
+ line = fgetln(fp, &line_len);
+ if (line == NULL) {
+ /* Assume EOF */
+ if (fp != stdin)
+ fclose(fp);
+ fpp[cnt] = NULL;
+ continue;
+ }
+ /* Output enough separators to catch up */
+ do {
+ ch = *dp++;
+ if (ch)
+ putchar(ch);
+ if (dp >= delim + delimcnt)
+ dp = delim;
+ } while (++output <= cnt);
+ /* Remove any trailing newline - check for last line */
+ if (line[line_len - 1] == '\n')
+ line_len--;
+ printf("%.*s", (int)line_len, line);
+ }
+
+ if (!output)
+ break;
+
+ /* Add separators to end of line */
+ while (++output <= cnt) {
+ ch = *dp++;
+ if (ch)
+ putchar(ch);
+ if (dp >= delim + delimcnt)
+ dp = delim;
+ }
+ putchar('\n');
+ }
+
+ free(fpp);
+}
+
+static void
+sequential(char **argv)
+{
+ FILE *fp;
+ int cnt;
+ char ch, *p, *dp;
+ char buf[_POSIX2_LINE_MAX + 1];
+
+ for (; (p = *argv) != NULL; ++argv) {
+ if (p[0] == '-' && !p[1])
+ fp = stdin;
+ else if (!(fp = fopen(p, "r"))) {
+ warn("%s", p);
+ continue;
+ }
+ if (fgets(buf, sizeof(buf), fp)) {
+ for (cnt = 0, dp = delim;;) {
+ if (!(p = strchr(buf, '\n')))
+ err(1, "%s: input line too long.",
+ *argv);
+ *p = '\0';
+ (void)printf("%s", buf);
+ if (!fgets(buf, sizeof(buf), fp))
+ break;
+ if ((ch = *dp++) != 0)
+ putchar(ch);
+ if (++cnt == delimcnt) {
+ dp = delim;
+ cnt = 0;
+ }
+ }
+ putchar('\n');
+ }
+ if (fp != stdin)
+ (void)fclose(fp);
+ }
+}
+
+static int
+tr(char *arg)
+{
+ int cnt;
+ char ch, *p;
+
+ for (p = arg, cnt = 0; (ch = *p++); ++arg, ++cnt)
+ if (ch == '\\')
+ switch(ch = *p++) {
+ case 'n':
+ *arg = '\n';
+ break;
+ case 't':
+ *arg = '\t';
+ break;
+ case '0':
+ *arg = '\0';
+ break;
+ default:
+ *arg = ch;
+ break;
+ } else
+ *arg = ch;
+
+ if (!cnt)
+ errx(1, "no delimiters specified.");
+ *arg = '\0';
+ return(cnt);
+}
+
+static void
+usage(void)
+{
+ (void)fprintf(stderr, "paste: [-s] [-d delimiters] file ...\n");
+ exit(1);
+}
diff --git a/usr.bin/patch/backupfile.c b/usr.bin/patch/backupfile.c
new file mode 100644
index 0000000..25d6076
--- /dev/null
+++ b/usr.bin/patch/backupfile.c
@@ -0,0 +1,254 @@
+/*
+ * $OpenBSD: backupfile.c,v 1.19 2006/03/11 19:41:30 otto Exp $
+ * $DragonFly: src/usr.bin/patch/backupfile.c,v 1.5 2008/08/11 00:05:06 joerg Exp $
+ * $NetBSD: backupfile.c,v 1.15 2014/04/11 17:30:03 christos Exp $
+ */
+
+/*
+ * backupfile.c -- make Emacs style backup file names
+ *
+ * Copyright (C) 1990 Free Software Foundation, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * without restriction.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.
+ */
+
+/*
+ * David MacKenzie <djm@ai.mit.edu>. Some algorithms adapted from GNU Emacs.
+ */
+
+#include <sys/cdefs.h>
+__RCSID("$NetBSD: backupfile.c,v 1.15 2014/04/11 17:30:03 christos Exp $");
+
+#include <ctype.h>
+#include <dirent.h>
+#include <libgen.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "backupfile.h"
+
+
+#define ISDIGIT(c) (isascii ((unsigned char)c) && isdigit ((unsigned char)c))
+
+/* Which type of backup file names are generated. */
+enum backup_type backup_type = none;
+
+/*
+ * The extension added to file names to produce a simple (as opposed to
+ * numbered) backup file name.
+ */
+const char *simple_backup_suffix = "~";
+
+static char *concat(const char *, const char *);
+static char *make_version_name(const char *, int);
+static int max_backup_version(const char *, const char *);
+static int version_number(const char *, const char *, size_t);
+static int argmatch(const char *, const char **);
+static void invalid_arg(const char *, const char *, int);
+
+/*
+ * Return the name of the new backup file for file FILE, allocated with
+ * malloc. Return 0 if out of memory. FILE must not end with a '/' unless it
+ * is the root directory. Do not call this function if backup_type == none.
+ */
+char *
+find_backup_file_name(const char *file)
+{
+ char *dir, *base_versions, *tmp_file;
+ int highest_backup;
+
+ if (backup_type == simple)
+ return concat(file, simple_backup_suffix);
+ tmp_file = strdup(file);
+ if (tmp_file == NULL)
+ return NULL;
+ base_versions = concat(basename(tmp_file), ".~");
+ free(tmp_file);
+ if (base_versions == NULL)
+ return NULL;
+ tmp_file = strdup(file);
+ if (tmp_file == NULL) {
+ free(base_versions);
+ return NULL;
+ }
+ dir = dirname(tmp_file);
+ if (dir == NULL) {
+ free(base_versions);
+ free(tmp_file);
+ return NULL;
+ }
+ highest_backup = max_backup_version(base_versions, dir);
+ free(base_versions);
+ free(tmp_file);
+ if (backup_type == numbered_existing && highest_backup == 0)
+ return concat(file, simple_backup_suffix);
+ return make_version_name(file, highest_backup + 1);
+}
+
+/*
+ * Return the number of the highest-numbered backup file for file FILE in
+ * directory DIR. If there are no numbered backups of FILE in DIR, or an
+ * error occurs reading DIR, return 0. FILE should already have ".~" appended
+ * to it.
+ */
+static int
+max_backup_version(const char *file, const char *dir)
+{
+ DIR *dirp;
+ struct dirent *dp;
+ int highest_version, this_version;
+ size_t file_name_length;
+
+ dirp = opendir(dir);
+ if (dirp == NULL)
+ return 0;
+
+ highest_version = 0;
+ file_name_length = strlen(file);
+
+ while ((dp = readdir(dirp)) != NULL) {
+ if (dp->d_namlen <= file_name_length)
+ continue;
+
+ this_version = version_number(file, dp->d_name, file_name_length);
+ if (this_version > highest_version)
+ highest_version = this_version;
+ }
+ closedir(dirp);
+ return highest_version;
+}
+
+/*
+ * Return a string, allocated with malloc, containing "FILE.~VERSION~".
+ * Return 0 if out of memory.
+ */
+static char *
+make_version_name(const char *file, int version)
+{
+ char *backup_name;
+
+ if (asprintf(&backup_name, "%s.~%d~", file, version) == -1)
+ return NULL;
+ return backup_name;
+}
+
+/*
+ * If BACKUP is a numbered backup of BASE, return its version number;
+ * otherwise return 0. BASE_LENGTH is the length of BASE. BASE should
+ * already have ".~" appended to it.
+ */
+static int
+version_number(const char *base, const char *backup, size_t base_length)
+{
+ int version;
+ const char *p;
+
+ version = 0;
+ if (!strncmp(base, backup, base_length) && ISDIGIT(backup[base_length])) {
+ for (p = &backup[base_length]; ISDIGIT(*p); ++p)
+ version = version * 10 + *p - '0';
+ if (p[0] != '~' || p[1])
+ version = 0;
+ }
+ return version;
+}
+
+/*
+ * Return the newly-allocated concatenation of STR1 and STR2. If out of
+ * memory, return 0.
+ */
+static char *
+concat(const char *str1, const char *str2)
+{
+ char *newstr;
+
+ if (asprintf(&newstr, "%s%s", str1, str2) == -1)
+ return NULL;
+ return newstr;
+}
+
+/*
+ * If ARG is an unambiguous match for an element of the null-terminated array
+ * OPTLIST, return the index in OPTLIST of the matched element, else -1 if it
+ * does not match any element or -2 if it is ambiguous (is a prefix of more
+ * than one element).
+ */
+static int
+argmatch(const char *arg, const char **optlist)
+{
+ int i; /* Temporary index in OPTLIST. */
+ size_t arglen; /* Length of ARG. */
+ int matchind = -1; /* Index of first nonexact match. */
+ int ambiguous = 0; /* If nonzero, multiple nonexact match(es). */
+
+ arglen = strlen(arg);
+
+ /* Test all elements for either exact match or abbreviated matches. */
+ for (i = 0; optlist[i]; i++) {
+ if (!strncmp(optlist[i], arg, arglen)) {
+ if (strlen(optlist[i]) == arglen)
+ /* Exact match found. */
+ return i;
+ else if (matchind == -1)
+ /* First nonexact match found. */
+ matchind = i;
+ else
+ /* Second nonexact match found. */
+ ambiguous = 1;
+ }
+ }
+ if (ambiguous)
+ return -2;
+ else
+ return matchind;
+}
+
+/*
+ * Error reporting for argmatch. KIND is a description of the type of entity
+ * that was being matched. VALUE is the invalid value that was given. PROBLEM
+ * is the return value from argmatch.
+ */
+static void
+invalid_arg(const char *kind, const char *value, int problem)
+{
+ fprintf(stderr, "patch: ");
+ if (problem == -1)
+ fprintf(stderr, "invalid");
+ else /* Assume -2. */
+ fprintf(stderr, "ambiguous");
+ fprintf(stderr, " %s `%s'\n", kind, value);
+}
+
+static const char *backup_args[] = {
+ "none", "never", "simple", "nil", "existing", "t", "numbered", 0
+};
+
+static enum backup_type backup_types[] = {
+ none, simple, simple, numbered_existing,
+ numbered_existing, numbered, numbered
+};
+
+/*
+ * Return the type of backup indicated by VERSION. Unique abbreviations are
+ * accepted.
+ */
+enum backup_type
+get_version(const char *version)
+{
+ int i;
+
+ if (version == NULL || *version == '\0')
+ return numbered_existing;
+ i = argmatch(version, backup_args);
+ if (i >= 0)
+ return backup_types[i];
+ invalid_arg("version control type", version, i);
+ exit(2);
+}
diff --git a/usr.bin/patch/backupfile.h b/usr.bin/patch/backupfile.h
new file mode 100644
index 0000000..d146c3a
--- /dev/null
+++ b/usr.bin/patch/backupfile.h
@@ -0,0 +1,42 @@
+/*
+ * $OpenBSD: backupfile.h,v 1.6 2003/07/28 18:35:36 otto Exp $
+ * $DragonFly: src/usr.bin/patch/backupfile.h,v 1.3 2007/09/29 23:11:10 swildner Exp $
+ * $NetBSD: backupfile.h,v 1.6 2008/09/19 18:33:34 joerg Exp $
+ */
+
+/*
+ * backupfile.h -- declarations for making Emacs style backup file names
+ * Copyright (C) 1990 Free Software Foundation, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * without restriction.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.
+ *
+ */
+
+/* When to make backup files. */
+enum backup_type {
+ /* Never make backups. */
+ none,
+
+ /* Make simple backups of every file. */
+ simple,
+
+ /*
+ * Make numbered backups of files that already have numbered backups,
+ * and simple backups of the others.
+ */
+ numbered_existing,
+
+ /* Make numbered backups of every file. */
+ numbered
+};
+
+extern enum backup_type backup_type;
+extern const char *simple_backup_suffix;
+
+char *find_backup_file_name(const char *file);
+enum backup_type get_version(const char *version);
diff --git a/usr.bin/patch/common.h b/usr.bin/patch/common.h
new file mode 100644
index 0000000..c19d43b
--- /dev/null
+++ b/usr.bin/patch/common.h
@@ -0,0 +1,123 @@
+/*
+ * $OpenBSD: common.h,v 1.26 2006/03/11 19:41:30 otto Exp $
+ * $DragonFly: src/usr.bin/patch/common.h,v 1.5 2008/08/10 23:50:12 joerg Exp $
+ * $NetBSD: common.h,v 1.21 2015/07/24 18:56:44 christos Exp $
+ */
+
+/*
+ * patch - a program to apply diffs to original files
+ *
+ * Copyright 1986, Larry Wall
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following condition is met:
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this condition and the following disclaimer.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE FOR
+ * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * -C option added in 1998, original code by Marc Espie, based on FreeBSD
+ * behaviour
+ */
+
+#include <sys/types.h>
+
+#include <stdbool.h>
+#include <stdint.h>
+
+#define DEBUGGING
+
+/* constants */
+
+#define MAXHUNKSIZE 100000 /* is this enough lines? */
+#define INITHUNKMAX 125 /* initial dynamic allocation size */
+#define MAXLINELEN 8192
+#define BUFFERSIZE 1024
+#define LINENUM_MAX LONG_MAX
+
+#define SCCSPREFIX "s."
+#define GET "get -e %s"
+#define SCCSDIFF "get -p %s | diff - %s >/dev/null"
+
+#define RCSSUFFIX ",v"
+#define CHECKOUT "/usr/bin/co"
+#define RCSDIFF "/usr/bin/rcsdiff"
+
+#define ORIGEXT ".orig"
+#define REJEXT ".rej"
+
+/* handy definitions */
+
+#define strNE(s1,s2) (strcmp(s1, s2))
+#define strEQ(s1,s2) (!strcmp(s1, s2))
+#define strnNE(s1,s2,l) (strncmp(s1, s2, l))
+#define strnEQ(s1,s2,l) (!strncmp(s1, s2, l))
+
+/* typedefs */
+
+typedef long LINENUM; /* must be signed */
+
+/* globals */
+
+extern mode_t filemode;
+
+extern char buf[MAXLINELEN];/* general purpose buffer */
+extern size_t buf_len;
+
+extern bool using_plan_a; /* try to keep everything in memory */
+extern bool out_of_mem; /* ran out of memory in plan a */
+
+#define MAXFILEC 2
+
+extern char *filearg[MAXFILEC];
+extern bool ok_to_create_file;
+extern char *outname;
+extern char *origprae;
+
+extern char *TMPOUTNAME;
+extern char *TMPINNAME;
+extern char *TMPREJNAME;
+extern char *TMPPATNAME;
+extern bool toutkeep;
+extern bool trejkeep;
+
+#ifdef DEBUGGING
+extern int debug;
+#endif
+
+extern bool force;
+extern bool batch;
+extern bool verbose;
+extern bool reverse;
+extern bool noreverse;
+extern bool skip_rest_of_patch;
+extern int strippath;
+extern bool canonicalize;
+/* TRUE if -C was specified on command line. */
+extern bool check_only;
+extern bool warn_on_invalid_line;
+extern bool last_line_missing_eol;
+
+
+#define CONTEXT_DIFF 1
+#define NORMAL_DIFF 2
+#define ED_DIFF 3
+#define NEW_CONTEXT_DIFF 4
+#define UNI_DIFF 5
+
+extern int diff_type;
+extern char *revision; /* prerequisite revision, if any */
+extern LINENUM input_lines; /* how long is input file in lines */
+
+extern int posix;
+
diff --git a/usr.bin/patch/inp.c b/usr.bin/patch/inp.c
new file mode 100644
index 0000000..b434a88
--- /dev/null
+++ b/usr.bin/patch/inp.c
@@ -0,0 +1,516 @@
+/*
+ * $OpenBSD: inp.c,v 1.34 2006/03/11 19:41:30 otto Exp $
+ * $DragonFly: src/usr.bin/patch/inp.c,v 1.6 2007/09/29 23:11:10 swildner Exp $
+ * $NetBSD: inp.c,v 1.26 2018/06/18 18:33:31 christos Exp $
+ */
+
+/*
+ * patch - a program to apply diffs to original files
+ *
+ * Copyright 1986, Larry Wall
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following condition is met:
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this condition and the following disclaimer.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE FOR
+ * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * -C option added in 1998, original code by Marc Espie, based on FreeBSD
+ * behaviour
+ */
+
+#include <sys/cdefs.h>
+__RCSID("$NetBSD: inp.c,v 1.26 2018/06/18 18:33:31 christos Exp $");
+
+#include <sys/types.h>
+#include <sys/file.h>
+#include <sys/stat.h>
+#include <sys/mman.h>
+#include <sys/wait.h>
+
+#include <ctype.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <libgen.h>
+#include <limits.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "common.h"
+#include "util.h"
+#include "pch.h"
+#include "inp.h"
+
+
+/* Input-file-with-indexable-lines abstract type */
+
+static off_t i_size; /* size of the input file */
+static char *i_womp; /* plan a buffer for entire file */
+static char **i_ptr; /* pointers to lines in i_womp */
+static char empty_line[] = { '\0' };
+
+static int tifd = -1; /* plan b virtual string array */
+static char *tibuf[2]; /* plan b buffers */
+static LINENUM tiline[2] = {-1, -1}; /* 1st line in each buffer */
+static LINENUM lines_per_buf; /* how many lines per buffer */
+static int tireclen; /* length of records in tmp file */
+
+static bool rev_in_string(const char *);
+static bool reallocate_lines(size_t *);
+
+/* returns false if insufficient memory */
+static bool plan_a(const char *);
+
+static void plan_b(const char *);
+
+/* New patch--prepare to edit another file. */
+
+void
+re_input(void)
+{
+ if (using_plan_a) {
+ i_size = 0;
+ free(i_ptr);
+ i_ptr = NULL;
+ if (i_womp != NULL) {
+ munmap(i_womp, i_size);
+ i_womp = NULL;
+ }
+ } else {
+ using_plan_a = true; /* maybe the next one is smaller */
+ close(tifd);
+ tifd = -1;
+ free(tibuf[0]);
+ free(tibuf[1]);
+ tibuf[0] = tibuf[1] = NULL;
+ tiline[0] = tiline[1] = -1;
+ tireclen = 0;
+ }
+}
+
+/* Construct the line index, somehow or other. */
+
+void
+scan_input(const char *filename)
+{
+ if (!plan_a(filename))
+ plan_b(filename);
+ if (verbose) {
+ say("Patching file %s using Plan %s...\n", filename,
+ (using_plan_a ? "A" : "B"));
+ }
+}
+
+static bool
+reallocate_lines(size_t *lines_allocated)
+{
+ char **p;
+ size_t new_size;
+
+ new_size = *lines_allocated * 3 / 2;
+ p = pch_realloc(i_ptr, new_size + 2, sizeof(char *));
+ if (p == NULL) { /* shucks, it was a near thing */
+ munmap(i_womp, i_size);
+ i_womp = NULL;
+ free(i_ptr);
+ i_ptr = NULL;
+ *lines_allocated = 0;
+ return false;
+ }
+ *lines_allocated = new_size;
+ i_ptr = p;
+ return true;
+}
+
+/* Try keeping everything in memory. */
+
+static bool
+plan_a(const char *filename)
+{
+ int ifd, statfailed, devnull, pstat;
+ char *p, *s, lbuf[MAXLINELEN];
+ struct stat filestat;
+ off_t i;
+ ptrdiff_t sz;
+ size_t iline, lines_allocated;
+ pid_t pid;
+ char *argp[4] = {NULL};
+
+#ifdef DEBUGGING
+ if (debug & 8)
+ return false;
+#endif
+
+ if (filename == NULL || *filename == '\0')
+ return false;
+
+ statfailed = stat(filename, &filestat);
+ if (statfailed && ok_to_create_file) {
+ if (verbose)
+ say("(Creating file %s...)\n", filename);
+
+ /*
+ * in check_patch case, we still display `Creating file' even
+ * though we're not. The rule is that -C should be as similar
+ * to normal patch behavior as possible
+ */
+ if (check_only)
+ return true;
+ makedirs(filename, true);
+ close(creat(filename, 0666));
+ statfailed = stat(filename, &filestat);
+ }
+ if (statfailed && check_only)
+ fatal("%s not found, -C mode, can't probe further\n", filename);
+ /* For nonexistent or read-only files, look for RCS versions. */
+ if (statfailed ||
+ /* No one can write to it. */
+ (filestat.st_mode & 0222) == 0 ||
+ /* I can't write to it. */
+ ((filestat.st_mode & 0022) == 0 && filestat.st_uid != getuid())) {
+ char *filebase, *filedir;
+ struct stat cstat;
+ char *tmp_filename1, *tmp_filename2;
+
+ tmp_filename1 = strdup(filename);
+ tmp_filename2 = strdup(filename);
+ if (tmp_filename1 == NULL || tmp_filename2 == NULL)
+ fatal("strdupping filename");
+
+ filebase = basename(tmp_filename1);
+ filedir = dirname(tmp_filename2);
+
+#define try(f, a1, a2, a3) \
+ (snprintf(lbuf, sizeof lbuf, f, a1, a2, a3), stat(lbuf, &cstat) == 0)
+
+ /*
+ * else we can't write to it but it's not under a version
+ * control system, so just proceed.
+ */
+ if (try("%s/RCS/%s%s", filedir, filebase, RCSSUFFIX) ||
+ try("%s/RCS/%s%s", filedir, filebase, "") ||
+ try("%s/%s%s", filedir, filebase, RCSSUFFIX)) {
+ if (!statfailed) {
+ if ((filestat.st_mode & 0222) != 0)
+ /* The owner can write to it. */
+ fatal("file %s seems to be locked "
+ "by somebody else under RCS\n",
+ filename);
+ /*
+ * It might be checked out unlocked. See if
+ * it's safe to check out the default version
+ * locked.
+ */
+ if (verbose)
+ say("Comparing file %s to default "
+ "RCS version...\n", filename);
+
+ switch (pid = fork()) {
+ case -1:
+ fatal("can't fork: %s\n",
+ strerror(errno));
+ case 0:
+ devnull = open("/dev/null", O_RDONLY);
+ if (devnull == -1) {
+ fatal("can't open /dev/null: %s",
+ strerror(errno));
+ }
+ (void)dup2(devnull, STDOUT_FILENO);
+ argp[0] = __UNCONST(RCSDIFF);
+ argp[1] = __UNCONST(filename);
+ execv(RCSDIFF, argp);
+ exit(127);
+ }
+ pid = waitpid(pid, &pstat, 0);
+ if (pid == -1 || WEXITSTATUS(pstat) != 0) {
+ fatal("can't check out file %s: "
+ "differs from default RCS version\n",
+ filename);
+ }
+ }
+
+ if (verbose)
+ say("Checking out file %s from RCS...\n",
+ filename);
+
+ switch (pid = fork()) {
+ case -1:
+ fatal("can't fork: %s\n", strerror(errno));
+ case 0:
+ argp[0] = __UNCONST(CHECKOUT);
+ argp[1] = __UNCONST("-l");
+ argp[2] = __UNCONST(filename);
+ execv(CHECKOUT, argp);
+ exit(127);
+ }
+ pid = waitpid(pid, &pstat, 0);
+ if (pid == -1 || WEXITSTATUS(pstat) != 0 ||
+ stat(filename, &filestat)) {
+ fatal("can't check out file %s from RCS\n",
+ filename);
+ }
+ } else if (statfailed) {
+ fatal("can't find %s\n", filename);
+ }
+ free(tmp_filename1);
+ free(tmp_filename2);
+ }
+
+ filemode = filestat.st_mode;
+ if (!S_ISREG(filemode))
+ fatal("%s is not a normal file--can't patch\n", filename);
+ i_size = filestat.st_size;
+ if (out_of_mem) {
+ set_hunkmax(); /* make sure dynamic arrays are allocated */
+ out_of_mem = false;
+ return false; /* force plan b because plan a bombed */
+ }
+ if ((uintmax_t)i_size > (uintmax_t)SIZE_MAX) {
+ say("block too large to mmap\n");
+ return false;
+ }
+ if ((ifd = open(filename, O_RDONLY)) < 0)
+ pfatal("can't open file %s", filename);
+
+ if (i_size) {
+ i_womp = mmap(NULL, i_size, PROT_READ, MAP_PRIVATE, ifd, 0);
+ if (i_womp == MAP_FAILED) {
+ perror("mmap failed");
+ i_womp = NULL;
+ close(ifd);
+ return false;
+ }
+ } else {
+ i_womp = NULL;
+ }
+
+ close(ifd);
+ if (i_size)
+ madvise(i_womp, i_size, MADV_SEQUENTIAL);
+
+ /* estimate the number of lines */
+ lines_allocated = i_size / 25;
+ if (lines_allocated < 100)
+ lines_allocated = 100;
+
+ if (!reallocate_lines(&lines_allocated))
+ return false;
+
+ /* now scan the buffer and build pointer array */
+ iline = 1;
+ i_ptr[iline] = i_womp;
+ /* test for NUL too, to maintain the behavior of the original code */
+ for (s = i_womp, i = 0; i < i_size && *s != '\0'; s++, i++) {
+ if (*s == '\n') {
+ if (iline == lines_allocated) {
+ if (!reallocate_lines(&lines_allocated))
+ return false;
+ }
+ /* these are NOT NUL terminated */
+ i_ptr[++iline] = s + 1;
+ }
+ }
+ /* if the last line contains no EOL, append one */
+ if (i_size > 0 && i_womp[i_size - 1] != '\n') {
+ last_line_missing_eol = true;
+ /* fix last line */
+ sz = s - i_ptr[iline];
+ p = malloc(sz + 1);
+ if (p == NULL) {
+ free(i_ptr);
+ i_ptr = NULL;
+ munmap(i_womp, i_size);
+ i_womp = NULL;
+ return false;
+ }
+
+ memcpy(p, i_ptr[iline], sz);
+ p[sz] = '\n';
+ i_ptr[iline] = p;
+ /* count the extra line and make it point to some valid mem */
+ i_ptr[++iline] = empty_line;
+ } else
+ last_line_missing_eol = false;
+
+ input_lines = iline - 1;
+
+ /* now check for revision, if any */
+
+ if (revision != NULL) {
+ if (!rev_in_string(i_womp)) {
+ if (force) {
+ if (verbose)
+ say("Warning: this file doesn't appear "
+ "to be the %s version--patching anyway.\n",
+ revision);
+ } else if (batch) {
+ fatal("this file doesn't appear to be the "
+ "%s version--aborting.\n",
+ revision);
+ } else {
+ ask("This file doesn't appear to be the "
+ "%s version--patch anyway? [n] ",
+ revision);
+ if (*buf != 'y')
+ fatal("aborted\n");
+ }
+ } else if (verbose)
+ say("Good. This file appears to be the %s version.\n",
+ revision);
+ }
+ return true; /* plan a will work */
+}
+
+/* Keep (virtually) nothing in memory. */
+
+static void
+plan_b(const char *filename)
+{
+ FILE *ifp;
+ size_t i = 0, j, maxlen = 1;
+ char *p;
+ bool found_revision = (revision == NULL);
+
+ using_plan_a = false;
+ if ((ifp = fopen(filename, "r")) == NULL)
+ pfatal("can't open file %s", filename);
+ unlink(TMPINNAME);
+ if ((tifd = open(TMPINNAME, O_EXCL | O_CREAT | O_WRONLY, 0666)) < 0)
+ pfatal("can't open file %s", TMPINNAME);
+ while (fgets(buf, buf_len, ifp) != NULL) {
+ if (revision != NULL && !found_revision && rev_in_string(buf))
+ found_revision = true;
+ if ((i = strlen(buf)) > maxlen)
+ maxlen = i; /* find longest line */
+ }
+ last_line_missing_eol = i > 0 && buf[i - 1] != '\n';
+ if (last_line_missing_eol && maxlen == i)
+ maxlen++;
+
+ if (revision != NULL) {
+ if (!found_revision) {
+ if (force) {
+ if (verbose)
+ say("Warning: this file doesn't appear "
+ "to be the %s version--patching anyway.\n",
+ revision);
+ } else if (batch) {
+ fatal("this file doesn't appear to be the "
+ "%s version--aborting.\n",
+ revision);
+ } else {
+ ask("This file doesn't appear to be the %s "
+ "version--patch anyway? [n] ",
+ revision);
+ if (*buf != 'y')
+ fatal("aborted\n");
+ }
+ } else if (verbose)
+ say("Good. This file appears to be the %s version.\n",
+ revision);
+ }
+ fseek(ifp, 0L, SEEK_SET); /* rewind file */
+ lines_per_buf = BUFFERSIZE / maxlen;
+ tireclen = maxlen;
+ tibuf[0] = malloc(BUFFERSIZE + 1);
+ if (tibuf[0] == NULL)
+ fatal("out of memory\n");
+ tibuf[1] = malloc(BUFFERSIZE + 1);
+ if (tibuf[1] == NULL)
+ fatal("out of memory\n");
+ for (i = 1;; i++) {
+ p = tibuf[0] + maxlen * (i % lines_per_buf);
+ if (i % lines_per_buf == 0) /* new block */
+ if (write(tifd, tibuf[0], BUFFERSIZE) < BUFFERSIZE)
+ pfatal("can't write temp file");
+ if (fgets(p, maxlen + 1, ifp) == NULL) {
+ input_lines = i - 1;
+ if (i % lines_per_buf != 0)
+ if (write(tifd, tibuf[0], BUFFERSIZE) < BUFFERSIZE)
+ pfatal("can't write temp file");
+ break;
+ }
+ j = strlen(p);
+ /* These are '\n' terminated strings, so no need to add a NUL */
+ if (j == 0 || p[j - 1] != '\n')
+ p[j] = '\n';
+ }
+ fclose(ifp);
+ close(tifd);
+ if ((tifd = open(TMPINNAME, O_RDONLY)) < 0)
+ pfatal("can't reopen file %s", TMPINNAME);
+}
+
+/*
+ * Fetch a line from the input file, \n terminated, not necessarily \0.
+ */
+char *
+ifetch(LINENUM line, int whichbuf)
+{
+ if (line < 1 || line > input_lines) {
+ if (warn_on_invalid_line) {
+ say("No such line %ld in input file, ignoring\n", line);
+ warn_on_invalid_line = false;
+ }
+ return NULL;
+ }
+ if (using_plan_a)
+ return i_ptr[line];
+ else {
+ LINENUM offline = line % lines_per_buf;
+ LINENUM baseline = line - offline;
+
+ if (tiline[0] == baseline)
+ whichbuf = 0;
+ else if (tiline[1] == baseline)
+ whichbuf = 1;
+ else {
+ tiline[whichbuf] = baseline;
+
+ if (lseek(tifd, (off_t) (baseline / lines_per_buf *
+ BUFFERSIZE), SEEK_SET) < 0)
+ pfatal("cannot seek in the temporary input file");
+
+ if (read(tifd, tibuf[whichbuf], BUFFERSIZE) < 0)
+ pfatal("error reading tmp file %s", TMPINNAME);
+ }
+ return tibuf[whichbuf] + (tireclen * offline);
+ }
+}
+
+/*
+ * True if the string argument contains the revision number we want.
+ */
+static bool
+rev_in_string(const char *string)
+{
+ const char *s;
+ size_t patlen;
+
+ if (revision == NULL)
+ return true;
+ patlen = strlen(revision);
+ if (strnEQ(string, revision, patlen) && isspace((unsigned char)string[patlen]))
+ return true;
+ for (s = string; *s; s++) {
+ if (isspace((unsigned char)*s) && strnEQ(s + 1, revision, patlen) &&
+ isspace((unsigned char)s[patlen + 1])) {
+ return true;
+ }
+ }
+ return false;
+}
diff --git a/usr.bin/patch/inp.h b/usr.bin/patch/inp.h
new file mode 100644
index 0000000..d010526
--- /dev/null
+++ b/usr.bin/patch/inp.h
@@ -0,0 +1,35 @@
+/*
+ * $OpenBSD: inp.h,v 1.8 2003/08/15 08:00:51 otto Exp $
+ * $DragonFly: src/usr.bin/patch/inp.h,v 1.1 2004/09/24 18:44:28 joerg Exp $
+ * $NetBSD: inp.h,v 1.10 2008/09/19 18:33:34 joerg Exp $
+ */
+
+/*
+ * patch - a program to apply diffs to original files
+ *
+ * Copyright 1986, Larry Wall
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following condition is met:
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this condition and the following disclaimer.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE FOR
+ * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * -C option added in 1998, original code by Marc Espie, based on FreeBSD
+ * behaviour
+ */
+
+void re_input(void);
+void scan_input(const char *);
+char *ifetch(LINENUM, int);
diff --git a/usr.bin/patch/mkpath.c b/usr.bin/patch/mkpath.c
new file mode 100644
index 0000000..3d18036
--- /dev/null
+++ b/usr.bin/patch/mkpath.c
@@ -0,0 +1,84 @@
+/*
+ * $OpenBSD: mkpath.c,v 1.2 2005/06/20 07:14:06 otto Exp $
+ * $DragonFly: src/usr.bin/patch/mkpath.c,v 1.1 2007/09/29 23:11:10 swildner Exp $
+ * $NetBSD: mkpath.c,v 1.1 2008/09/19 18:33:34 joerg Exp $
+ */
+
+/*
+ * Copyright (c) 1983, 1992, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+__RCSID("$NetBSD: mkpath.c,v 1.1 2008/09/19 18:33:34 joerg Exp $");
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <err.h>
+#include <errno.h>
+#include <string.h>
+
+int mkpath(char *);
+
+/* Code taken directly from mkdir(1).
+
+ * mkpath -- create directories.
+ * path - path
+ */
+int
+mkpath(char *path)
+{
+ struct stat sb;
+ char *slash;
+ int done = 0;
+
+ slash = path;
+
+ while (!done) {
+ slash += strspn(slash, "/");
+ slash += strcspn(slash, "/");
+
+ done = (*slash == '\0');
+ *slash = '\0';
+
+ if (stat(path, &sb)) {
+ if (errno != ENOENT || (mkdir(path, 0777) &&
+ errno != EEXIST)) {
+ warn("%s", path);
+ return (-1);
+ }
+ } else if (!S_ISDIR(sb.st_mode)) {
+ warnx("%s: %s", path, strerror(ENOTDIR));
+ return (-1);
+ }
+
+ *slash = '/';
+ }
+
+ return (0);
+}
+
diff --git a/usr.bin/patch/patch.1 b/usr.bin/patch/patch.1
new file mode 100644
index 0000000..1f434c7
--- /dev/null
+++ b/usr.bin/patch/patch.1
@@ -0,0 +1,663 @@
+.\" $OpenBSD: patch.1,v 1.22 2008/06/06 20:44:00 jmc Exp $
+.\" $DragonFly: src/usr.bin/patch/patch.1,v 1.10 2008/08/18 19:15:55 joerg Exp $
+.\" $NetBSD: patch.1,v 1.21 2017/07/03 21:34:20 wiz Exp $
+.\" Copyright 1986, Larry Wall
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following condition
+.\" is met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\" notice, this condition and the following disclaimer.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+.\" ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+.\" SUCH DAMAGE.
+.\"
+.Dd November 7, 2015
+.Dt PATCH 1
+.Os
+.Sh NAME
+.Nm patch
+.Nd apply a diff file to an original
+.Sh SYNOPSIS
+.Nm
+.Op Fl bCcEeflNnRstuv
+.Op Fl B Ar backup-prefix
+.Op Fl D Ar symbol
+.Op Fl d Ar directory
+.Op Fl F Ar max-fuzz
+.Op Fl i Ar patchfile
+.Op Fl o Ar out-file
+.Op Fl p Ar strip-count
+.Op Fl r Ar rej-name
+.Op Fl V Cm t | nil | never | none
+.Op Fl x Ar number
+.Op Fl z Ar backup-ext
+.Op Fl Fl posix
+.Op Ar origfile Op Ar patchfile
+.Nm
+.Pf \*(Lt Ar patchfile
+.Sh DESCRIPTION
+.Nm
+will take a patch file containing any of the four forms of difference
+listing produced by the
+.Xr diff 1
+program and apply those differences to an original file,
+producing a patched version.
+If
+.Ar patchfile
+is omitted, or is a hyphen, the patch will be read from the standard input.
+.Pp
+.Nm
+will attempt to determine the type of the diff listing, unless over-ruled by a
+.Fl c ,
+.Fl e ,
+.Fl n ,
+or
+.Fl u
+option.
+Context diffs (old-style, new-style, and unified) and
+normal diffs are applied directly by the
+.Nm
+program itself, whereas ed diffs are simply fed to the
+.Xr ed 1
+editor via a pipe.
+.Pp
+If the
+.Ar patchfile
+contains more than one patch,
+.Nm
+will try to apply each of them as if they came from separate patch files.
+This means, among other things, that it is assumed that the name of the file
+to patch must be determined for each diff listing, and that the garbage before
+each diff listing will be examined for interesting things such as file names
+and revision level (see the section on
+.Sx Filename Determination
+below).
+.Pp
+The options are as follows:
+.Bl -tag -width Ds
+.It Fl B Ar backup-prefix , Fl Fl prefix Ar backup-prefix
+Causes the next argument to be interpreted as a prefix to the backup file
+name.
+If this argument is specified, any argument to
+.Fl z
+will be ignored.
+.It Fl b , Fl Fl backup
+Save a backup copy of the file before it is modified.
+By default the original file is saved with a backup extension of
+.Qq .orig
+unless the file already has a numbered backup, in which case a numbered
+backup is made.
+This is equivalent to specifying
+.Qo Fl V Cm existing Qc .
+This option is currently the default, unless
+.Fl -posix
+is specified.
+.It Fl C , Fl Fl check
+Checks that the patch would apply cleanly, but does not modify anything.
+.It Fl c , Fl Fl context
+Forces
+.Nm
+to interpret the patch file as a context diff.
+.It Fl D Ar symbol , Fl Fl ifdef Ar symbol
+Causes
+.Nm
+to use the
+.Qq #ifdef...#endif
+construct to mark changes.
+The argument following will be used as the differentiating symbol.
+Note that, unlike the C compiler, there must be a space between the
+.Fl D
+and the argument.
+.It Fl d Ar directory , Fl Fl directory Ar directory
+Causes
+.Nm
+to interpret the next argument as a directory,
+and change the working directory to it before doing anything else.
+.It Fl E , Fl Fl remove-empty-files
+Causes
+.Nm
+to remove output files that are empty after the patches have been applied.
+This option is useful when applying patches that create or remove files.
+.It Fl e , Fl Fl ed
+Forces
+.Nm
+to interpret the patch file as an
+.Xr ed 1
+script.
+.It Fl F Ar max-fuzz , Fl Fl fuzz Ar max-fuzz
+Sets the maximum fuzz factor.
+This option only applies to context diffs, and causes
+.Nm
+to ignore up to that many lines in looking for places to install a hunk.
+Note that a larger fuzz factor increases the odds of a faulty patch.
+The default fuzz factor is 2, and it may not be set to more than
+the number of lines of context in the context diff, ordinarily 3.
+.It Fl f , Fl Fl force
+Forces
+.Nm
+to assume that the user knows exactly what he or she is doing, and to not
+ask any questions.
+It assumes the following:
+skip patches for which a file to patch can't be found;
+patch files even though they have the wrong version for the
+.Qq Prereq:
+line in the patch;
+and assume that patches are not reversed even if they look like they are.
+This option does not suppress commentary; use
+.Fl s
+for that.
+.It Fl i Ar patchfile , Fl Fl input Ar patchfile
+Causes the next argument to be interpreted as the input file name
+(i.e., a patchfile).
+This option may be specified multiple times.
+.It Fl l , Fl Fl ignore-whitespace
+Causes the pattern matching to be done loosely, in case the tabs and
+spaces have been munged in your input file.
+Any sequence of whitespace in the pattern line will match any sequence
+in the input file.
+Normal characters must still match exactly.
+Each line of the context must still match a line in the input file.
+.It Fl N , Fl Fl forward
+Causes
+.Nm
+to ignore patches that it thinks are reversed or already applied.
+See also
+.Fl R .
+.It Fl n , Fl Fl normal
+Forces
+.Nm
+to interpret the patch file as a normal diff.
+.It Fl o Ar out-file , Fl Fl output Ar out-file
+Causes the next argument to be interpreted as the output file name.
+.It Fl p Ar strip-count , Fl Fl strip Ar strip-count
+Sets the pathname strip count,
+which controls how pathnames found in the patch file are treated,
+in case you keep your files in a different directory than the person who sent
+out the patch.
+The strip count specifies how many slashes are to be stripped from
+the front of the pathname.
+(Any intervening directory names also go away.)
+For example, supposing the file name in the patch file was
+.Pa /u/howard/src/blurfl/blurfl.c :
+.Pp
+Setting
+.Fl p Ns Ar 0
+gives the entire pathname unmodified.
+.Pp
+.Fl p Ns Ar 1
+gives
+.Pp
+.D1 Pa u/howard/src/blurfl/blurfl.c
+.Pp
+without the leading slash.
+.Pp
+.Fl p Ns Ar 4
+gives
+.Pp
+.D1 Pa blurfl/blurfl.c
+.Pp
+Not specifying
+.Fl p
+at all just gives you
+.Pa blurfl.c ,
+unless all of the directories in the leading path
+.Pq Pa u/howard/src/blurfl
+exist and that path is relative,
+in which case you get the entire pathname unmodified.
+Whatever you end up with is looked for either in the current directory,
+or the directory specified by the
+.Fl d
+option.
+.It Fl R , Fl Fl reverse
+Tells
+.Nm
+that this patch was created with the old and new files swapped.
+(Yes, I'm afraid that does happen occasionally, human nature being what it
+is.)
+.Nm
+will attempt to swap each hunk around before applying it.
+Rejects will come out in the swapped format.
+The
+.Fl R
+option will not work with ed diff scripts because there is too little
+information to reconstruct the reverse operation.
+.Pp
+If the first hunk of a patch fails,
+.Nm
+will reverse the hunk to see if it can be applied that way.
+If it can, you will be asked if you want to have the
+.Fl R
+option set.
+If it can't, the patch will continue to be applied normally.
+(Note: this method cannot detect a reversed patch if it is a normal diff
+and if the first command is an append (i.e., it should have been a delete)
+since appends always succeed, due to the fact that a null context will match
+anywhere.
+Luckily, most patches add or change lines rather than delete them, so most
+reversed normal diffs will begin with a delete, which will fail, triggering
+the heuristic.)
+.It Fl r Ar rej-name , Fl Fl reject-file Ar rej-name
+Causes the next argument to be interpreted as the reject file name.
+.It Fl s , Fl Fl quiet , Fl Fl silent
+Makes
+.Nm
+do its work silently, unless an error occurs.
+.It Fl t , Fl Fl batch
+Similar to
+.Fl f ,
+in that it suppresses questions, but makes some different assumptions:
+skip patches for which a file to patch can't be found (the same as
+.Fl f ) ;
+skip patches for which the file has the wrong version for the
+.Qq Prereq:
+line in the patch;
+and assume that patches are reversed if they look like they are.
+.It Fl u , Fl Fl unified
+Forces
+.Nm
+to interpret the patch file as a unified context diff (a unidiff).
+.It Fl V Cm t | nil | never | none , Fl Fl version-control Cm t | nil | never | none
+Causes the next argument to be interpreted as a method for creating
+backup file names.
+The type of backups made can also be given in the
+.Ev PATCH_VERSION_CONTROL
+or
+.Ev VERSION_CONTROL
+environment variables, which are overridden by this option.
+The
+.Fl B
+option overrides this option, causing the prefix to always be used for
+making backup file names.
+The values of the
+.Ev PATCH_VERSION_CONTROL
+and
+.Ev VERSION_CONTROL
+environment variables and the argument to the
+.Fl V
+option are like the GNU Emacs
+.Dq version-control
+variable; they also recognize synonyms that are more descriptive.
+The valid values are (unique abbreviations are accepted):
+.Bl -tag -width Ds -offset indent
+.It Cm t , numbered
+Always make numbered backups.
+.It Cm nil , existing
+Make numbered backups of files that already have them,
+simple backups of the others.
+.It Cm never , simple
+Always make simple backups.
+.It Cm none
+No backups are created.
+.El
+.It Fl v , Fl Fl version
+Causes
+.Nm
+to print out its revision header and patch level.
+.It Fl x Ar number , Fl Fl debug Ar number
+Sets internal debugging flags, and is of interest only to
+.Nm
+patchers.
+.It Fl z Ar backup-ext , Fl Fl suffix Ar backup-ext
+Causes the next argument to be interpreted as the backup extension, to be
+used in place of
+.Qq .orig .
+.It Fl Fl posix
+Enables strict
+.St -p1003.1-2004
+conformance, specifically:
+.Bl -enum
+.It
+Backup files are not created unless the
+.Fl b
+option is specified.
+.It
+If unspecified, the file name used is the first of the old, new and
+index files that exists.
+.El
+.El
+.Ss Patch Application
+.Nm
+will try to skip any leading garbage, apply the diff,
+and then skip any trailing garbage.
+Thus you could feed an article or message containing a
+diff listing to
+.Nm ,
+and it should work.
+If the entire diff is indented by a consistent amount,
+this will be taken into account.
+.Pp
+With context diffs, and to a lesser extent with normal diffs,
+.Nm
+can detect when the line numbers mentioned in the patch are incorrect,
+and will attempt to find the correct place to apply each hunk of the patch.
+As a first guess, it takes the line number mentioned for the hunk, plus or
+minus any offset used in applying the previous hunk.
+If that is not the correct place,
+.Nm
+will scan both forwards and backwards for a set of lines matching the context
+given in the hunk.
+First
+.Nm
+looks for a place where all lines of the context match.
+If no such place is found, and it's a context diff, and the maximum fuzz factor
+is set to 1 or more, then another scan takes place ignoring the first and last
+line of context.
+If that fails, and the maximum fuzz factor is set to 2 or more,
+the first two and last two lines of context are ignored,
+and another scan is made.
+.Pq The default maximum fuzz factor is 2.
+.Pp
+If
+.Nm
+cannot find a place to install that hunk of the patch, it will put the hunk
+out to a reject file, which normally is the name of the output file plus
+.Qq .rej .
+(Note that the rejected hunk will come out in context diff form whether the
+input patch was a context diff or a normal diff.
+If the input was a normal diff, many of the contexts will simply be null.)
+The line numbers on the hunks in the reject file may be different than
+in the patch file: they reflect the approximate location patch thinks the
+failed hunks belong in the new file rather than the old one.
+.Pp
+As each hunk is completed, you will be told whether the hunk succeeded or
+failed, and which line (in the new file)
+.Nm
+thought the hunk should go on.
+If this is different from the line number specified in the diff,
+you will be told the offset.
+A single large offset MAY be an indication that a hunk was installed in the
+wrong place.
+You will also be told if a fuzz factor was used to make the match, in which
+case you should also be slightly suspicious.
+.Ss Filename Determination
+If no original file is specified on the command line,
+.Nm
+will try to figure out from the leading garbage what the name of the file
+to edit is.
+When checking a prospective file name, pathname components are stripped
+as specified by the
+.Fl p
+option and the file's existence and writability are checked relative
+to the current working directory (or the directory specified by the
+.Fl d
+option).
+.Pp
+If the diff is a context or unified diff,
+.Nm
+is able to determine the old and new file names from the diff header.
+For context diffs, the
+.Dq old
+file is specified in the line beginning with
+.Qq ***
+and the
+.Dq new
+file is specified in the line beginning with
+.Qq --- .
+For a unified diff, the
+.Dq old
+file is specified in the line beginning with
+.Qq ---
+and the
+.Dq new
+file is specified in the line beginning with
+.Qq +++ .
+If there is an
+.Qq Index:
+line in the leading garbage (regardless of the diff type),
+.Nm
+will use the file name from that line as the
+.Dq index
+file.
+.Pp
+.Nm
+will choose the file name by performing the following steps, with the first
+match used:
+.Bl -enum
+.It
+If
+.Nm
+is operating in strict
+.St -p1003.1-2004
+mode, the first of the
+.Dq old ,
+.Dq new
+and
+.Dq index
+file names that exist is used.
+Otherwise,
+.Nm
+will examine either the
+.Dq old
+and
+.Dq new
+file names or, for a non-context diff, the
+.Dq index
+file name, and choose the file name with the fewest path components,
+the shortest basename, and the shortest total file name length (in that order).
+.It
+If no file exists,
+.Nm
+checks for the existence of the files in an RCS directory using the criteria
+specified above.
+If found,
+.Nm
+will attempt to get or check out the file.
+.It
+If no suitable file was found to patch, the patch file is a context or
+unified diff, and the old file was zero length, the new file name is
+created and used.
+.It
+If the file name still cannot be determined,
+.Nm
+will prompt the user for the file name to use.
+.El
+.Pp
+Additionally, if the leading garbage contains a
+.Qq Prereq:\ \&
+line,
+.Nm
+will take the first word from the prerequisites line (normally a version
+number) and check the input file to see if that word can be found.
+If not,
+.Nm
+will ask for confirmation before proceeding.
+.Pp
+The upshot of all this is that you should be able to say, while in a news
+interface, the following:
+.Pp
+.Dl | patch -d /usr/src/local/blurfl
+.Pp
+and patch a file in the blurfl directory directly from the article containing
+the patch.
+.Ss Backup Files
+By default, the patched version is put in place of the original, with
+the original file backed up to the same name with the extension
+.Qq .orig ,
+or as specified by the
+.Fl B ,
+.Fl V ,
+or
+.Fl z
+options.
+The extension used for making backup files may also be specified in the
+.Ev SIMPLE_BACKUP_SUFFIX
+environment variable, which is overridden by the options above.
+.Pp
+If the backup file is a symbolic or hard link to the original file,
+.Nm
+creates a new backup file name by changing the first lowercase letter
+in the last component of the file's name into uppercase.
+If there are no more lowercase letters in the name,
+it removes the first character from the name.
+It repeats this process until it comes up with a
+backup file that does not already exist or is not linked to the original file.
+.Pp
+You may also specify where you want the output to go with the
+.Fl o
+option; if that file already exists, it is backed up first.
+.Ss Notes For Patch Senders
+There are several things you should bear in mind if you are going to
+be sending out patches:
+.Pp
+First, you can save people a lot of grief by keeping a
+.Pa patchlevel.h
+file which is patched to increment the patch level as the first diff in the
+patch file you send out.
+If you put a
+.Qq Prereq:
+line in with the patch, it won't let them apply
+patches out of order without some warning.
+.Pp
+Second, make sure you've specified the file names right, either in a
+context diff header, or with an
+.Qq Index:
+line.
+If you are patching something in a subdirectory, be sure to tell the patch
+user to specify a
+.Fl p
+option as needed.
+.Pp
+Third, you can create a file by sending out a diff that compares a
+null file to the file you want to create.
+This will only work if the file you want to create doesn't exist already in
+the target directory.
+.Pp
+Fourth, take care not to send out reversed patches, since it makes people wonder
+whether they already applied the patch.
+.Pp
+Fifth, while you may be able to get away with putting 582 diff listings into
+one file, it is probably wiser to group related patches into separate files in
+case something goes haywire.
+.Sh ENVIRONMENT
+.Bl -tag -width "PATCH_VERSION_CONTROL" -compact
+.It Ev POSIXLY_CORRECT
+When set,
+.Nm
+behaves as if the
+.Fl Fl posix
+option has been specified.
+.It Ev SIMPLE_BACKUP_SUFFIX
+Extension to use for backup file names instead of
+.Qq .orig .
+.It Ev TMPDIR
+Directory to put temporary files in; default is
+.Pa /tmp .
+.It Ev PATCH_VERSION_CONTROL
+Selects when numbered backup files are made.
+.It Ev VERSION_CONTROL
+Same as
+.Ev PATCH_VERSION_CONTROL .
+.El
+.Sh FILES
+.Bl -tag -width "$TMPDIR/patch*" -compact
+.It Pa $TMPDIR/patch*
+.Nm
+temporary files
+.It Pa /dev/tty
+used to read input when
+.Nm
+prompts the user
+.El
+.Sh DIAGNOSTICS
+Too many to list here, but generally indicative that
+.Nm
+couldn't parse your patch file.
+.Pp
+The message
+.Qq Hmm...
+indicates that there is unprocessed text in the patch file and that
+.Nm
+is attempting to intuit whether there is a patch in that text and, if so,
+what kind of patch it is.
+.Pp
+The
+.Nm
+utility exits with one of the following values:
+.Pp
+.Bl -tag -width Ds -compact -offset indent
+.It \&0
+Successful completion.
+.It \&1
+One or more lines were written to a reject file.
+.It >\&1
+An error occurred.
+.El
+.Pp
+When applying a set of patches in a loop it behooves you to check this
+exit status so you don't apply a later patch to a partially patched file.
+.Sh SEE ALSO
+.Xr diff 1
+.Sh STANDARDS
+The
+.Nm
+utility is compliant with the
+.St -p1003.1-2004
+specification
+(except as detailed above for the
+.Fl -posix
+option),
+though the presence of
+.Nm
+itself is optional.
+.Pp
+The flags
+.Op Fl CEfstuvBFVxz
+and
+.Op Fl -posix
+are extensions to that specification.
+.Sh AUTHORS
+.An Larry Wall
+with many other contributors.
+.Sh CAVEATS
+.Nm
+cannot tell if the line numbers are off in an ed script, and can only detect
+bad line numbers in a normal diff when it finds a
+.Qq change
+or a
+.Qq delete
+command.
+A context diff using fuzz factor 3 may have the same problem.
+Until a suitable interactive interface is added, you should probably do
+a context diff in these cases to see if the changes made sense.
+Of course, compiling without errors is a pretty good indication that the patch
+worked, but not always.
+.Pp
+.Nm
+usually produces the correct results, even when it has to do a lot of
+guessing.
+However, the results are guaranteed to be correct only when the patch is
+applied to exactly the same version of the file that the patch was
+generated from.
+.Sh BUGS
+Could be smarter about partial matches, excessively deviant offsets and
+swapped code, but that would take an extra pass.
+.Pp
+Check patch mode
+.Pq Fl C
+will fail if you try to check several patches in succession that build on
+each other.
+The entire
+.Nm
+code would have to be restructured to keep temporary files around so that it
+can handle this situation.
+.Pp
+If code has been duplicated (for instance with #ifdef OLDCODE ... #else ...
+#endif),
+.Nm
+is incapable of patching both versions, and, if it works at all, will likely
+patch the wrong one, and tell you that it succeeded to boot.
+.Pp
+If you apply a patch you've already applied,
+.Nm
+will think it is a reversed patch, and offer to un-apply the patch.
+This could be construed as a feature.
diff --git a/usr.bin/patch/patch.c b/usr.bin/patch/patch.c
new file mode 100644
index 0000000..9f49e7f
--- /dev/null
+++ b/usr.bin/patch/patch.c
@@ -0,0 +1,1064 @@
+/*
+ * $OpenBSD: patch.c,v 1.45 2007/04/18 21:52:24 sobrado Exp $
+ * $DragonFly: src/usr.bin/patch/patch.c,v 1.10 2008/08/10 23:39:56 joerg Exp $
+ * $NetBSD: patch.c,v 1.29 2011/09/06 18:25:14 joerg Exp $
+ */
+
+/*
+ * patch - a program to apply diffs to original files
+ *
+ * Copyright 1986, Larry Wall
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following condition is met:
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this condition and the following disclaimer.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE FOR
+ * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * -C option added in 1998, original code by Marc Espie, based on FreeBSD
+ * behaviour
+ */
+
+#include <sys/cdefs.h>
+__RCSID("$NetBSD: patch.c,v 1.29 2011/09/06 18:25:14 joerg Exp $");
+
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#include <ctype.h>
+#include <getopt.h>
+#include <limits.h>
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+#include "common.h"
+#include "util.h"
+#include "pch.h"
+#include "inp.h"
+#include "backupfile.h"
+#include "pathnames.h"
+
+mode_t filemode = 0644;
+
+char buf[MAXLINELEN]; /* general purpose buffer */
+size_t buf_len = sizeof(buf);
+
+bool using_plan_a = true; /* try to keep everything in memory */
+bool out_of_mem = false; /* ran out of memory in plan a */
+
+#define MAXFILEC 2
+
+char *filearg[MAXFILEC];
+bool ok_to_create_file = false;
+char *outname = NULL;
+char *origprae = NULL;
+char *TMPOUTNAME;
+char *TMPINNAME;
+char *TMPREJNAME;
+char *TMPPATNAME;
+bool toutkeep = false;
+bool trejkeep = false;
+bool warn_on_invalid_line;
+bool last_line_missing_eol;
+
+#ifdef DEBUGGING
+int debug = 0;
+#endif
+
+bool force = false;
+bool batch = false;
+bool verbose = true;
+bool reverse = false;
+bool noreverse = false;
+bool skip_rest_of_patch = false;
+int strippath = 957;
+bool canonicalize = false;
+bool check_only = false;
+int diff_type = 0;
+char *revision = NULL; /* prerequisite revision, if any */
+LINENUM input_lines = 0; /* how long is input file in lines */
+int posix = 0; /* strict POSIX mode? */
+
+static void reinitialize_almost_everything(void);
+static void get_some_switches(void);
+static LINENUM locate_hunk(LINENUM);
+static void abort_context_hunk(void);
+static void rej_line(int, LINENUM);
+static void abort_hunk(void);
+static void apply_hunk(LINENUM);
+static void init_output(const char *);
+static void init_reject(const char *);
+static void copy_till(LINENUM, bool);
+static bool spew_output(void);
+static void dump_line(LINENUM, bool);
+static bool patch_match(LINENUM, LINENUM, LINENUM);
+static bool similar(const char *, const char *, int);
+__dead static void usage(void);
+
+/* true if -E was specified on command line. */
+static bool remove_empty_files = false;
+
+/* true if -R was specified on command line. */
+static bool reverse_flag_specified = false;
+
+/* buffer holding the name of the rejected patch file. */
+static char rejname[NAME_MAX + 1];
+
+/* buffer for stderr */
+static char serrbuf[BUFSIZ];
+
+/* how many input lines have been irretractibly output */
+static LINENUM last_frozen_line = 0;
+
+static int Argc; /* guess */
+static char **Argv;
+static int Argc_last; /* for restarting plan_b */
+static char **Argv_last;
+
+static FILE *ofp = NULL; /* output file pointer */
+static FILE *rejfp = NULL; /* reject file pointer */
+
+static int filec = 0; /* how many file arguments? */
+static LINENUM last_offset = 0;
+static LINENUM maxfuzz = 2;
+
+/* patch using ifdef, ifndef, etc. */
+static bool do_defines = false;
+/* #ifdef xyzzy */
+static char if_defined[128];
+/* #ifndef xyzzy */
+static char not_defined[128];
+/* #else */
+static const char else_defined[] = "#else\n";
+/* #endif xyzzy */
+static char end_defined[128];
+
+
+/* Apply a set of diffs as appropriate. */
+
+int
+main(int argc, char *argv[])
+{
+ int error = 0, hunk, failed, i, fd;
+ LINENUM where = 0, newwhere, fuzz, mymaxfuzz;
+ const char *tmpdir;
+ char *v;
+
+ setbuf(stderr, serrbuf);
+ for (i = 0; i < MAXFILEC; i++)
+ filearg[i] = NULL;
+
+ /* Cons up the names of the temporary files. */
+ if ((tmpdir = getenv("TMPDIR")) == NULL || *tmpdir == '\0')
+ tmpdir = _PATH_TMP;
+ for (i = strlen(tmpdir) - 1; i > 0 && tmpdir[i] == '/'; i--)
+ ;
+ i++;
+ if (asprintf(&TMPOUTNAME, "%.*s/patchoXXXXXXXXXX", i, tmpdir) == -1)
+ fatal("cannot allocate memory");
+ if ((fd = mkstemp(TMPOUTNAME)) < 0)
+ pfatal("can't create %s", TMPOUTNAME);
+ close(fd);
+
+ if (asprintf(&TMPINNAME, "%.*s/patchiXXXXXXXXXX", i, tmpdir) == -1)
+ fatal("cannot allocate memory");
+ if ((fd = mkstemp(TMPINNAME)) < 0)
+ pfatal("can't create %s", TMPINNAME);
+ close(fd);
+
+ if (asprintf(&TMPREJNAME, "%.*s/patchrXXXXXXXXXX", i, tmpdir) == -1)
+ fatal("cannot allocate memory");
+ if ((fd = mkstemp(TMPREJNAME)) < 0)
+ pfatal("can't create %s", TMPREJNAME);
+ close(fd);
+
+ if (asprintf(&TMPPATNAME, "%.*s/patchpXXXXXXXXXX", i, tmpdir) == -1)
+ fatal("cannot allocate memory");
+ if ((fd = mkstemp(TMPPATNAME)) < 0)
+ pfatal("can't create %s", TMPPATNAME);
+ close(fd);
+
+ v = getenv("SIMPLE_BACKUP_SUFFIX");
+ if (v)
+ simple_backup_suffix = v;
+ else
+ simple_backup_suffix = ORIGEXT;
+
+ /* parse switches */
+ Argc = argc;
+ Argv = argv;
+ get_some_switches();
+
+ if (backup_type == none) {
+ if ((v = getenv("PATCH_VERSION_CONTROL")) == NULL)
+ v = getenv("VERSION_CONTROL");
+ if (v != NULL || !posix)
+ backup_type = get_version(v); /* OK to pass NULL. */
+ }
+
+ /* make sure we clean up /tmp in case of disaster */
+ set_signals(0);
+
+ for (open_patch_file(filearg[1]); there_is_another_patch();
+ reinitialize_almost_everything()) {
+ /* for each patch in patch file */
+
+ warn_on_invalid_line = true;
+
+ if (outname == NULL)
+ outname = savestr(filearg[0]);
+
+ /* for ed script just up and do it and exit */
+ if (diff_type == ED_DIFF) {
+ do_ed_script();
+ continue;
+ }
+ /* initialize the patched file */
+ if (!skip_rest_of_patch)
+ init_output(TMPOUTNAME);
+
+ /* initialize reject file */
+ init_reject(TMPREJNAME);
+
+ /* find out where all the lines are */
+ if (!skip_rest_of_patch)
+ scan_input(filearg[0]);
+
+ /* from here on, open no standard i/o files, because malloc */
+ /* might misfire and we can't catch it easily */
+
+ /* apply each hunk of patch */
+ hunk = 0;
+ failed = 0;
+ out_of_mem = false;
+ while (another_hunk()) {
+ hunk++;
+ fuzz = 0;
+ mymaxfuzz = pch_context();
+ if (maxfuzz < mymaxfuzz)
+ mymaxfuzz = maxfuzz;
+ if (!skip_rest_of_patch) {
+ do {
+ where = locate_hunk(fuzz);
+ if (hunk == 1 && where == 0 && !force) {
+ /* dwim for reversed patch? */
+ if (!pch_swap()) {
+ if (fuzz == 0)
+ say("Not enough memory to try swapped hunk! Assuming unswapped.\n");
+ continue;
+ }
+ reverse = !reverse;
+ /* try again */
+ where = locate_hunk(fuzz);
+ if (where == 0) {
+ /* didn't find it swapped */
+ if (!pch_swap())
+ /* put it back to normal */
+ fatal("lost hunk on alloc error!\n");
+ reverse = !reverse;
+ } else if (noreverse) {
+ if (!pch_swap())
+ /* put it back to normal */
+ fatal("lost hunk on alloc error!\n");
+ reverse = !reverse;
+ say("Ignoring previously applied (or reversed) patch.\n");
+ skip_rest_of_patch = true;
+ } else if (batch) {
+ if (verbose)
+ say("%seversed (or previously applied) patch detected! %s -R.",
+ reverse ? "R" : "Unr",
+ reverse ? "Assuming" : "Ignoring");
+ } else {
+ ask("%seversed (or previously applied) patch detected! %s -R? [y] ",
+ reverse ? "R" : "Unr",
+ reverse ? "Assume" : "Ignore");
+ if (*buf == 'n') {
+ ask("Apply anyway? [n] ");
+ if (*buf != 'y')
+ skip_rest_of_patch = true;
+ where = 0;
+ reverse = !reverse;
+ if (!pch_swap())
+ /* put it back to normal */
+ fatal("lost hunk on alloc error!\n");
+ }
+ }
+ }
+ } while (!skip_rest_of_patch && where == 0 &&
+ ++fuzz <= mymaxfuzz);
+
+ if (skip_rest_of_patch) { /* just got decided */
+ if (ferror(ofp) || fclose(ofp)) {
+ say("Error writing %s\n",
+ TMPOUTNAME);
+ error = 1;
+ }
+ ofp = NULL;
+ }
+ }
+ newwhere = pch_newfirst() + last_offset;
+ if (skip_rest_of_patch) {
+ abort_hunk();
+ failed++;
+ if (verbose)
+ say("Hunk #%d ignored at %ld.\n",
+ hunk, newwhere);
+ } else if (where == 0) {
+ abort_hunk();
+ failed++;
+ if (verbose)
+ say("Hunk #%d failed at %ld.\n",
+ hunk, newwhere);
+ } else {
+ apply_hunk(where);
+ if (verbose) {
+ say("Hunk #%d succeeded at %ld",
+ hunk, newwhere);
+ if (fuzz != 0)
+ say(" with fuzz %ld", fuzz);
+ if (last_offset)
+ say(" (offset %ld line%s)",
+ last_offset,
+ last_offset == 1L ? "" : "s");
+ say(".\n");
+ }
+ }
+ }
+
+ if (out_of_mem && using_plan_a) {
+ Argc = Argc_last;
+ Argv = Argv_last;
+ say("\n\nRan out of memory using Plan A--trying again...\n\n");
+ if (ofp)
+ fclose(ofp);
+ ofp = NULL;
+ if (rejfp)
+ fclose(rejfp);
+ rejfp = NULL;
+ continue;
+ }
+ if (hunk == 0)
+ fatal("Internal error: hunk should not be 0\n");
+
+ /* finish spewing out the new file */
+ if (!skip_rest_of_patch && !spew_output()) {
+ say("Can't write %s\n", TMPOUTNAME);
+ error = 1;
+ }
+
+ /* and put the output where desired */
+ ignore_signals();
+ if (!skip_rest_of_patch) {
+ struct stat statbuf;
+ char *realout = outname;
+
+ if (!check_only) {
+ if (move_file(TMPOUTNAME, outname) < 0) {
+ toutkeep = true;
+ realout = TMPOUTNAME;
+ chmod(TMPOUTNAME, filemode);
+ } else
+ chmod(outname, filemode);
+
+ if (remove_empty_files &&
+ stat(realout, &statbuf) == 0 &&
+ statbuf.st_size == 0) {
+ if (verbose)
+ say("Removing %s (empty after patching).\n",
+ realout);
+ unlink(realout);
+ }
+ }
+ }
+ if (ferror(rejfp) || fclose(rejfp)) {
+ say("Error writing %s\n", rejname);
+ error = 1;
+ }
+ rejfp = NULL;
+ if (failed) {
+ error = 1;
+ if (*rejname == '\0') {
+ if (strlcpy(rejname, outname,
+ sizeof(rejname)) >= sizeof(rejname))
+ fatal("filename %s is too long\n", outname);
+ if (strlcat(rejname, REJEXT,
+ sizeof(rejname)) >= sizeof(rejname))
+ fatal("filename %s is too long\n", outname);
+ }
+ if (skip_rest_of_patch) {
+ say("%d out of %d hunks ignored--saving rejects to %s\n",
+ failed, hunk, rejname);
+ } else {
+ say("%d out of %d hunks failed--saving rejects to %s\n",
+ failed, hunk, rejname);
+ }
+ if (!check_only && move_file(TMPREJNAME, rejname) < 0)
+ trejkeep = true;
+ }
+ set_signals(1);
+ }
+ my_exit(error);
+ /* NOTREACHED */
+}
+
+/* Prepare to find the next patch to do in the patch file. */
+
+static void
+reinitialize_almost_everything(void)
+{
+ re_patch();
+ re_input();
+
+ input_lines = 0;
+ last_frozen_line = 0;
+
+ filec = 0;
+ if (!out_of_mem) {
+ free(filearg[0]);
+ filearg[0] = NULL;
+ }
+
+ free(outname);
+ outname = NULL;
+
+ last_offset = 0;
+ diff_type = 0;
+
+ free(revision);
+ revision = NULL;
+
+ reverse = reverse_flag_specified;
+ skip_rest_of_patch = false;
+
+ get_some_switches();
+}
+
+/* Process switches and filenames. */
+
+static void
+get_some_switches(void)
+{
+ const char *options = "b::B:cCd:D:eEfF:i:lnNo:p:r:RstuvV:x:z:";
+ static struct option longopts[] = {
+ {"backup", no_argument, 0, 'b'},
+ {"batch", no_argument, 0, 't'},
+ {"check", no_argument, 0, 'C'},
+ {"context", no_argument, 0, 'c'},
+ {"debug", required_argument, 0, 'x'},
+ {"directory", required_argument, 0, 'd'},
+ {"ed", no_argument, 0, 'e'},
+ {"force", no_argument, 0, 'f'},
+ {"forward", no_argument, 0, 'N'},
+ {"fuzz", required_argument, 0, 'F'},
+ {"ifdef", required_argument, 0, 'D'},
+ {"input", required_argument, 0, 'i'},
+ {"ignore-whitespace", no_argument, 0, 'l'},
+ {"normal", no_argument, 0, 'n'},
+ {"output", required_argument, 0, 'o'},
+ {"prefix", required_argument, 0, 'B'},
+ {"quiet", no_argument, 0, 's'},
+ {"reject-file", required_argument, 0, 'r'},
+ {"remove-empty-files", no_argument, 0, 'E'},
+ {"reverse", no_argument, 0, 'R'},
+ {"silent", no_argument, 0, 's'},
+ {"strip", required_argument, 0, 'p'},
+ {"suffix", required_argument, 0, 'z'},
+ {"unified", no_argument, 0, 'u'},
+ {"version", no_argument, 0, 'v'},
+ {"version-control", required_argument, 0, 'V'},
+ {"posix", no_argument, &posix, 1},
+ {NULL, 0, 0, 0}
+ };
+ int ch;
+
+ rejname[0] = '\0';
+ Argc_last = Argc;
+ Argv_last = Argv;
+ if (!Argc)
+ return;
+ optreset = optind = 1;
+ while ((ch = getopt_long(Argc, Argv, options, longopts, NULL)) != -1) {
+ switch (ch) {
+ case 'b':
+ if (backup_type == none)
+ backup_type = numbered_existing;
+ if (optarg == NULL)
+ break;
+ if (verbose)
+ say("Warning, the ``-b suffix'' option has been"
+ " obsoleted by the -z option.\n");
+ /* FALLTHROUGH */
+ case 'z':
+ /* must directly follow 'b' case for backwards compat */
+ simple_backup_suffix = savestr(optarg);
+ break;
+ case 'B':
+ origprae = savestr(optarg);
+ break;
+ case 'c':
+ diff_type = CONTEXT_DIFF;
+ break;
+ case 'C':
+ check_only = true;
+ break;
+ case 'd':
+ if (chdir(optarg) < 0)
+ pfatal("can't cd to %s", optarg);
+ break;
+ case 'D':
+ do_defines = true;
+ if (!isalpha((unsigned char)*optarg) && *optarg != '_')
+ fatal("argument to -D is not an identifier\n");
+ snprintf(if_defined, sizeof if_defined,
+ "#ifdef %s\n", optarg);
+ snprintf(not_defined, sizeof not_defined,
+ "#ifndef %s\n", optarg);
+ snprintf(end_defined, sizeof end_defined,
+ "#endif /* %s */\n", optarg);
+ break;
+ case 'e':
+ diff_type = ED_DIFF;
+ break;
+ case 'E':
+ remove_empty_files = true;
+ break;
+ case 'f':
+ force = true;
+ break;
+ case 'F':
+ maxfuzz = atoi(optarg);
+ break;
+ case 'i':
+ if (++filec == MAXFILEC)
+ fatal("too many file arguments\n");
+ filearg[filec] = savestr(optarg);
+ break;
+ case 'l':
+ canonicalize = true;
+ break;
+ case 'n':
+ diff_type = NORMAL_DIFF;
+ break;
+ case 'N':
+ noreverse = true;
+ break;
+ case 'o':
+ outname = savestr(optarg);
+ break;
+ case 'p':
+ strippath = atoi(optarg);
+ break;
+ case 'r':
+ if (strlcpy(rejname, optarg,
+ sizeof(rejname)) >= sizeof(rejname))
+ fatal("argument for -r is too long\n");
+ break;
+ case 'R':
+ reverse = true;
+ reverse_flag_specified = true;
+ break;
+ case 's':
+ verbose = false;
+ break;
+ case 't':
+ batch = true;
+ break;
+ case 'u':
+ diff_type = UNI_DIFF;
+ break;
+ case 'v':
+ version();
+ break;
+ case 'V':
+ backup_type = get_version(optarg);
+ break;
+#ifdef DEBUGGING
+ case 'x':
+ debug = atoi(optarg);
+ break;
+#endif
+ default:
+ if (ch != '\0')
+ usage();
+ break;
+ }
+ }
+ Argc -= optind;
+ Argv += optind;
+
+ if (Argc > 0) {
+ filearg[0] = savestr(*Argv++);
+ Argc--;
+ while (Argc > 0) {
+ if (++filec == MAXFILEC)
+ fatal("too many file arguments\n");
+ filearg[filec] = savestr(*Argv++);
+ Argc--;
+ }
+ }
+
+ if (getenv("POSIXLY_CORRECT") != NULL)
+ posix = 1;
+}
+
+static void
+usage(void)
+{
+ fprintf(stderr,
+"usage: patch [-bCcEeflNnRstuv] [-B backup-prefix] [-D symbol] [-d directory]\n"
+" [-F max-fuzz] [-i patchfile] [-o out-file] [-p strip-count]\n"
+" [-r rej-name] [-V t | nil | never] [-x number] [-z backup-ext]\n"
+" [--posix] [origfile [patchfile]]\n"
+" patch <patchfile\n");
+ my_exit(EXIT_FAILURE);
+}
+
+/*
+ * Attempt to find the right place to apply this hunk of patch.
+ */
+static LINENUM
+locate_hunk(LINENUM fuzz)
+{
+ LINENUM first_guess = pch_first() + last_offset;
+ LINENUM offset;
+ LINENUM pat_lines = pch_ptrn_lines();
+ LINENUM max_pos_offset = input_lines - first_guess - pat_lines + 1;
+ LINENUM max_neg_offset = first_guess - last_frozen_line - 1 + pch_context();
+
+ if (pat_lines == 0) { /* null range matches always */
+ if (verbose && fuzz == 0 && (diff_type == CONTEXT_DIFF
+ || diff_type == NEW_CONTEXT_DIFF
+ || diff_type == UNI_DIFF)) {
+ say("Empty context always matches.\n");
+ }
+ return (first_guess);
+ }
+ if (max_neg_offset >= first_guess) /* do not try lines < 0 */
+ max_neg_offset = first_guess - 1;
+ if (first_guess <= input_lines && patch_match(first_guess, 0, fuzz))
+ return first_guess;
+ for (offset = 1; ; offset++) {
+ bool check_after = (offset <= max_pos_offset);
+ bool check_before = (offset <= max_neg_offset);
+
+ if (check_after && patch_match(first_guess, offset, fuzz)) {
+#ifdef DEBUGGING
+ if (debug & 1)
+ say("Offset changing from %ld to %ld\n",
+ last_offset, offset);
+#endif
+ last_offset = offset;
+ return first_guess + offset;
+ } else if (check_before && patch_match(first_guess, -offset, fuzz)) {
+#ifdef DEBUGGING
+ if (debug & 1)
+ say("Offset changing from %ld to %ld\n",
+ last_offset, -offset);
+#endif
+ last_offset = -offset;
+ return first_guess - offset;
+ } else if (!check_before && !check_after)
+ return 0;
+ }
+}
+
+/* We did not find the pattern, dump out the hunk so they can handle it. */
+
+static void
+abort_context_hunk(void)
+{
+ LINENUM i;
+ const LINENUM pat_end = pch_end();
+ /*
+ * add in last_offset to guess the same as the previous successful
+ * hunk
+ */
+ const LINENUM oldfirst = pch_first() + last_offset;
+ const LINENUM newfirst = pch_newfirst() + last_offset;
+ const LINENUM oldlast = oldfirst + pch_ptrn_lines() - 1;
+ const LINENUM newlast = newfirst + pch_repl_lines() - 1;
+ const char *stars = (diff_type >= NEW_CONTEXT_DIFF ? " ****" : "");
+ const char *minuses = (diff_type >= NEW_CONTEXT_DIFF ? " ----" : " -----");
+
+ fprintf(rejfp, "***************\n");
+ for (i = 0; i <= pat_end; i++) {
+ switch (pch_char(i)) {
+ case '*':
+ if (oldlast < oldfirst)
+ fprintf(rejfp, "*** 0%s\n", stars);
+ else if (oldlast == oldfirst)
+ fprintf(rejfp, "*** %ld%s\n", oldfirst, stars);
+ else
+ fprintf(rejfp, "*** %ld,%ld%s\n", oldfirst,
+ oldlast, stars);
+ break;
+ case '=':
+ if (newlast < newfirst)
+ fprintf(rejfp, "--- 0%s\n", minuses);
+ else if (newlast == newfirst)
+ fprintf(rejfp, "--- %ld%s\n", newfirst, minuses);
+ else
+ fprintf(rejfp, "--- %ld,%ld%s\n", newfirst,
+ newlast, minuses);
+ break;
+ case '\n':
+ fprintf(rejfp, "%s", pfetch(i));
+ break;
+ case ' ':
+ case '-':
+ case '+':
+ case '!':
+ fprintf(rejfp, "%c %s", pch_char(i), pfetch(i));
+ break;
+ default:
+ fatal("fatal internal error in abort_context_hunk\n");
+ }
+ }
+}
+
+static void
+rej_line(int ch, LINENUM i)
+{
+ size_t len;
+ const char *line = pfetch(i);
+
+ len = strlen(line);
+
+ fprintf(rejfp, "%c%s", ch, line);
+ if (len == 0 || line[len-1] != '\n')
+ fprintf(rejfp, "\n\\ No newline at end of file\n");
+}
+
+static void
+abort_hunk(void)
+{
+ LINENUM i, j, split;
+ int ch1, ch2;
+ const LINENUM pat_end = pch_end();
+ const LINENUM oldfirst = pch_first() + last_offset;
+ const LINENUM newfirst = pch_newfirst() + last_offset;
+
+ if (diff_type != UNI_DIFF) {
+ abort_context_hunk();
+ return;
+ }
+ split = -1;
+ for (i = 0; i <= pat_end; i++) {
+ if (pch_char(i) == '=') {
+ split = i;
+ break;
+ }
+ }
+ if (split == -1) {
+ fprintf(rejfp, "malformed hunk: no split found\n");
+ return;
+ }
+ i = 0;
+ j = split + 1;
+ fprintf(rejfp, "@@ -%ld,%ld +%ld,%ld @@\n",
+ pch_ptrn_lines() ? oldfirst : 0,
+ pch_ptrn_lines(), newfirst, pch_repl_lines());
+ while (i < split || j <= pat_end) {
+ ch1 = i < split ? pch_char(i) : -1;
+ ch2 = j <= pat_end ? pch_char(j) : -1;
+ if (ch1 == '-') {
+ rej_line('-', i);
+ i++;
+ } else if (ch1 == ' ' && ch2 == ' ') {
+ rej_line(' ', i);
+ i++;
+ j++;
+ } else if (ch1 == '!' && ch2 == '!') {
+ while (i < split && ch1 == '!') {
+ rej_line('-', i);
+ i++;
+ ch1 = i < split ? pch_char(i) : -1;
+ }
+ while (j <= pat_end && ch2 == '!') {
+ rej_line('+', j);
+ j++;
+ ch2 = j <= pat_end ? pch_char(j) : -1;
+ }
+ } else if (ch1 == '*') {
+ i++;
+ } else if (ch2 == '+' || ch2 == ' ') {
+ rej_line(ch2, j);
+ j++;
+ } else {
+ fprintf(rejfp, "internal error on (%ld %ld %ld)\n",
+ i, split, j);
+ rej_line(ch1, i);
+ rej_line(ch2, j);
+ return;
+ }
+ }
+}
+
+/* We found where to apply it (we hope), so do it. */
+
+static void
+apply_hunk(LINENUM where)
+{
+ LINENUM old = 1;
+ const LINENUM lastline = pch_ptrn_lines();
+ LINENUM new = lastline + 1;
+#define OUTSIDE 0
+#define IN_IFNDEF 1
+#define IN_IFDEF 2
+#define IN_ELSE 3
+ int def_state = OUTSIDE;
+ const LINENUM pat_end = pch_end();
+
+ where--;
+ while (pch_char(new) == '=' || pch_char(new) == '\n')
+ new++;
+
+ while (old <= lastline) {
+ if (pch_char(old) == '-') {
+ copy_till(where + old - 1, false);
+ if (do_defines) {
+ if (def_state == OUTSIDE) {
+ fputs(not_defined, ofp);
+ def_state = IN_IFNDEF;
+ } else if (def_state == IN_IFDEF) {
+ fputs(else_defined, ofp);
+ def_state = IN_ELSE;
+ }
+ fputs(pfetch(old), ofp);
+ }
+ last_frozen_line++;
+ old++;
+ } else if (new > pat_end) {
+ break;
+ } else if (pch_char(new) == '+') {
+ copy_till(where + old - 1, false);
+ if (do_defines) {
+ if (def_state == IN_IFNDEF) {
+ fputs(else_defined, ofp);
+ def_state = IN_ELSE;
+ } else if (def_state == OUTSIDE) {
+ fputs(if_defined, ofp);
+ def_state = IN_IFDEF;
+ }
+ }
+ fputs(pfetch(new), ofp);
+ new++;
+ } else if (pch_char(new) != pch_char(old)) {
+ say("Out-of-sync patch, lines %ld,%ld--mangled text or line numbers, maybe?\n",
+ pch_hunk_beg() + old,
+ pch_hunk_beg() + new);
+#ifdef DEBUGGING
+ say("oldchar = '%c', newchar = '%c'\n",
+ pch_char(old), pch_char(new));
+#endif
+ my_exit(2);
+ } else if (pch_char(new) == '!') {
+ copy_till(where + old - 1, false);
+ if (do_defines) {
+ fputs(not_defined, ofp);
+ def_state = IN_IFNDEF;
+ }
+ while (pch_char(old) == '!') {
+ if (do_defines) {
+ fputs(pfetch(old), ofp);
+ }
+ last_frozen_line++;
+ old++;
+ }
+ if (do_defines) {
+ fputs(else_defined, ofp);
+ def_state = IN_ELSE;
+ }
+ while (pch_char(new) == '!') {
+ fputs(pfetch(new), ofp);
+ new++;
+ }
+ } else {
+ if (pch_char(new) != ' ')
+ fatal("Internal error: expected ' '\n");
+ old++;
+ new++;
+ if (do_defines && def_state != OUTSIDE) {
+ fputs(end_defined, ofp);
+ def_state = OUTSIDE;
+ }
+ }
+ }
+ if (new <= pat_end && pch_char(new) == '+') {
+ copy_till(where + old - 1, false);
+ if (do_defines) {
+ if (def_state == OUTSIDE) {
+ fputs(if_defined, ofp);
+ def_state = IN_IFDEF;
+ } else if (def_state == IN_IFNDEF) {
+ fputs(else_defined, ofp);
+ def_state = IN_ELSE;
+ }
+ }
+ while (new <= pat_end && pch_char(new) == '+') {
+ fputs(pfetch(new), ofp);
+ new++;
+ }
+ }
+ if (do_defines && def_state != OUTSIDE) {
+ fputs(end_defined, ofp);
+ }
+}
+
+/*
+ * Open the new file.
+ */
+static void
+init_output(const char *name)
+{
+ ofp = fopen(name, "w");
+ if (ofp == NULL)
+ pfatal("can't create %s", name);
+}
+
+/*
+ * Open a file to put hunks we can't locate.
+ */
+static void
+init_reject(const char *name)
+{
+ rejfp = fopen(name, "w");
+ if (rejfp == NULL)
+ pfatal("can't create %s", name);
+}
+
+/*
+ * Copy input file to output, up to wherever hunk is to be applied.
+ * If endoffile is true, treat the last line specially since it may
+ * lack a newline.
+ */
+static void
+copy_till(LINENUM lastline, bool endoffile)
+{
+ if (last_frozen_line > lastline)
+ fatal("misordered hunks! output would be garbled\n");
+ while (last_frozen_line < lastline) {
+ if (++last_frozen_line == lastline && endoffile)
+ dump_line(last_frozen_line, !last_line_missing_eol);
+ else
+ dump_line(last_frozen_line, true);
+ }
+}
+
+/*
+ * Finish copying the input file to the output file.
+ */
+static bool
+spew_output(void)
+{
+ int rv;
+
+#ifdef DEBUGGING
+ if (debug & 256)
+ say("il=%ld lfl=%ld\n", input_lines, last_frozen_line);
+#endif
+ if (input_lines)
+ copy_till(input_lines, true); /* dump remainder of file */
+ rv = ferror(ofp) == 0 && fclose(ofp) == 0;
+ ofp = NULL;
+ return rv;
+}
+
+/*
+ * Copy one line from input to output.
+ */
+static void
+dump_line(LINENUM line, bool write_newline)
+{
+ char *s;
+
+ s = ifetch(line, 0);
+ if (s == NULL)
+ return;
+ /* Note: string is not NUL terminated. */
+ for (; *s != '\n'; s++)
+ putc(*s, ofp);
+ if (write_newline)
+ putc('\n', ofp);
+}
+
+/*
+ * Does the patch pattern match at line base+offset?
+ */
+static bool
+patch_match(LINENUM base, LINENUM offset, LINENUM fuzz)
+{
+ LINENUM pline = 1 + fuzz;
+ LINENUM iline;
+ LINENUM pat_lines = pch_ptrn_lines() - fuzz;
+ const char *ilineptr;
+ const char *plineptr;
+ short plinelen;
+
+ for (iline = base + offset + fuzz; pline <= pat_lines; pline++, iline++) {
+ ilineptr = ifetch(iline, offset >= 0);
+ if (ilineptr == NULL)
+ return false;
+ plineptr = pfetch(pline);
+ plinelen = pch_line_len(pline);
+ if (canonicalize) {
+ if (!similar(ilineptr, plineptr, plinelen))
+ return false;
+ } else if (strnNE(ilineptr, plineptr, plinelen))
+ return false;
+ if (iline == input_lines) {
+ /*
+ * We are looking at the last line of the file.
+ * If the file has no eol, the patch line should
+ * not have one either and vice-versa. Note that
+ * plinelen > 0.
+ */
+ if (last_line_missing_eol) {
+ if (plineptr[plinelen - 1] == '\n')
+ return false;
+ } else {
+ if (plineptr[plinelen - 1] != '\n')
+ return false;
+ }
+ }
+ }
+ return true;
+}
+
+/*
+ * Do two lines match with canonicalized white space?
+ */
+static bool
+similar(const char *a, const char *b, int len)
+{
+ while (len) {
+ if (isspace((unsigned char)*b)) { /* whitespace (or \n) to match? */
+ if (!isspace((unsigned char)*a)) /* no corresponding whitespace? */
+ return false;
+ while (len && isspace((unsigned char)*b) && *b != '\n')
+ b++, len--; /* skip pattern whitespace */
+ while (isspace((unsigned char)*a) && *a != '\n')
+ a++; /* skip target whitespace */
+ if (*a == '\n' || *b == '\n')
+ return (*a == *b); /* should end in sync */
+ } else if (*a++ != *b++) /* match non-whitespace chars */
+ return false;
+ else
+ len--; /* probably not necessary */
+ }
+ return true; /* actually, this is not reached */
+ /* since there is always a \n */
+}
diff --git a/usr.bin/patch/pathnames.h b/usr.bin/patch/pathnames.h
new file mode 100644
index 0000000..e9d3ccb
--- /dev/null
+++ b/usr.bin/patch/pathnames.h
@@ -0,0 +1,14 @@
+/*
+ * $OpenBSD: pathnames.h,v 1.1 2003/07/29 20:10:17 millert Exp $
+ * $DragonFly: src/usr.bin/patch/pathnames.h,v 1.2 2008/08/11 00:04:12 joerg Exp $
+ * $NetBSD: pathnames.h,v 1.1 2008/09/19 18:33:34 joerg Exp $
+ */
+
+/*
+ * Placed in the public domain by Todd C. Miller <Todd.Miller@courtesan.com>
+ * on July 29, 2003.
+ */
+
+#include <paths.h>
+
+#define _PATH_ED "/bin/ed"
diff --git a/usr.bin/patch/pch.c b/usr.bin/patch/pch.c
new file mode 100644
index 0000000..8a635ad
--- /dev/null
+++ b/usr.bin/patch/pch.c
@@ -0,0 +1,1600 @@
+/*
+ * $OpenBSD: pch.c,v 1.37 2007/09/02 15:19:33 deraadt Exp $
+ * $DragonFly: src/usr.bin/patch/pch.c,v 1.6 2008/08/10 23:35:40 joerg Exp $
+ * $NetBSD: pch.c,v 1.30 2018/06/18 18:33:31 christos Exp $
+ */
+
+/*
+ * patch - a program to apply diffs to original files
+ *
+ * Copyright 1986, Larry Wall
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following condition is met:
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this condition and the following disclaimer.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE FOR
+ * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * -C option added in 1998, original code by Marc Espie, based on FreeBSD
+ * behaviour
+ */
+
+#include <sys/cdefs.h>
+__RCSID("$NetBSD: pch.c,v 1.30 2018/06/18 18:33:31 christos Exp $");
+
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#include <ctype.h>
+#include <libgen.h>
+#include <limits.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "common.h"
+#include "util.h"
+#include "pch.h"
+#include "pathnames.h"
+
+/* Patch (diff listing) abstract type. */
+
+static long p_filesize; /* size of the patch file */
+static LINENUM p_first; /* 1st line number */
+static LINENUM p_newfirst; /* 1st line number of replacement */
+static LINENUM p_ptrn_lines; /* # lines in pattern */
+static LINENUM p_repl_lines; /* # lines in replacement text */
+static LINENUM p_end = -1; /* last line in hunk */
+static LINENUM p_max; /* max allowed value of p_end */
+static LINENUM p_context = 3; /* # of context lines */
+static LINENUM p_input_line = 0; /* current line # from patch file */
+static char **p_line = NULL;/* the text of the hunk */
+static short *p_len = NULL; /* length of each line */
+static char *p_char = NULL; /* +, -, and ! */
+static int hunkmax = INITHUNKMAX; /* size of above arrays to begin with */
+static int p_indent; /* indent to patch */
+static LINENUM p_base; /* where to intuit this time */
+static LINENUM p_bline; /* line # of p_base */
+static LINENUM p_start; /* where intuit found a patch */
+static LINENUM p_sline; /* and the line number for it */
+static LINENUM p_hunk_beg; /* line number of current hunk */
+static LINENUM p_efake = -1; /* end of faked up lines--don't free */
+static LINENUM p_bfake = -1; /* beg of faked up lines */
+static FILE *pfp = NULL; /* patch file pointer */
+static char *bestguess = NULL; /* guess at correct filename */
+
+static void grow_hunkmax(void);
+static int intuit_diff_type(void);
+static void next_intuit_at(LINENUM, LINENUM);
+static void skip_to(LINENUM, LINENUM);
+static char *pgets(char *, int, FILE *);
+static char *best_name(const struct file_name *, bool);
+static char *posix_name(const struct file_name *, bool);
+static size_t num_components(const char *);
+
+/*
+ * Prepare to look for the next patch in the patch file.
+ */
+void
+re_patch(void)
+{
+ p_first = 0;
+ p_newfirst = 0;
+ p_ptrn_lines = 0;
+ p_repl_lines = 0;
+ p_end = (LINENUM) - 1;
+ p_max = 0;
+ p_indent = 0;
+}
+
+/*
+ * Open the patch file at the beginning of time.
+ */
+void
+open_patch_file(const char *filename)
+{
+ struct stat filestat;
+
+ if (filename == NULL || *filename == '\0' || strEQ(filename, "-")) {
+ pfp = fopen(TMPPATNAME, "w");
+ if (pfp == NULL)
+ pfatal("can't create %s", TMPPATNAME);
+ while (fgets(buf, buf_len, stdin) != NULL)
+ fputs(buf, pfp);
+ if (ferror(pfp) || fclose(pfp))
+ pfatal("can't write %s", TMPPATNAME);
+ filename = TMPPATNAME;
+ }
+ pfp = fopen(filename, "r");
+ if (pfp == NULL)
+ pfatal("patch file %s not found", filename);
+ fstat(fileno(pfp), &filestat);
+ p_filesize = filestat.st_size;
+ next_intuit_at(0L, 1L); /* start at the beginning */
+ set_hunkmax();
+}
+
+/*
+ * Make sure our dynamically realloced tables are malloced to begin with.
+ */
+void
+set_hunkmax(void)
+{
+ if (p_line == NULL)
+ p_line = calloc((size_t) hunkmax, sizeof(char *));
+ if (p_len == NULL)
+ p_len = calloc((size_t) hunkmax, sizeof(short));
+ if (p_char == NULL)
+ p_char = calloc((size_t) hunkmax, sizeof(char));
+}
+
+/*
+ * Enlarge the arrays containing the current hunk of patch.
+ */
+static void
+grow_hunkmax(void)
+{
+ int new_hunkmax;
+ char **new_p_line;
+ short *new_p_len;
+ char *new_p_char;
+
+ new_hunkmax = hunkmax * 2;
+
+ if (p_line == NULL || p_len == NULL || p_char == NULL)
+ fatal("Internal memory allocation error\n");
+
+ new_p_line = pch_realloc(p_line, new_hunkmax, sizeof(char *));
+ if (new_p_line == NULL)
+ free(p_line);
+
+ new_p_len = pch_realloc(p_len, new_hunkmax, sizeof(short));
+ if (new_p_len == NULL)
+ free(p_len);
+
+ new_p_char = pch_realloc(p_char, new_hunkmax, sizeof(char));
+ if (new_p_char == NULL)
+ free(p_char);
+
+ p_char = new_p_char;
+ p_len = new_p_len;
+ p_line = new_p_line;
+
+ if (p_line != NULL && p_len != NULL && p_char != NULL) {
+ hunkmax = new_hunkmax;
+ return;
+ }
+
+ if (!using_plan_a)
+ fatal("out of memory\n");
+ out_of_mem = true; /* whatever is null will be allocated again */
+ /* from within plan_a(), of all places */
+}
+
+/* True if the remainder of the patch file contains a diff of some sort. */
+
+bool
+there_is_another_patch(void)
+{
+ bool exists = false;
+
+ if (p_base != 0L && p_base >= p_filesize) {
+ if (verbose)
+ say("done\n");
+ return false;
+ }
+ if (verbose)
+ say("Hmm...");
+ diff_type = intuit_diff_type();
+ if (!diff_type) {
+ if (p_base != 0L) {
+ if (verbose)
+ say(" Ignoring the trailing garbage.\ndone\n");
+ } else
+ say(" I can't seem to find a patch in there anywhere.\n");
+ return false;
+ }
+ if (verbose)
+ say(" %sooks like %s to me...\n",
+ (p_base == 0L ? "L" : "The next patch l"),
+ diff_type == UNI_DIFF ? "a unified diff" :
+ diff_type == CONTEXT_DIFF ? "a context diff" :
+ diff_type == NEW_CONTEXT_DIFF ? "a new-style context diff" :
+ diff_type == NORMAL_DIFF ? "a normal diff" :
+ "an ed script");
+ if (p_indent && verbose)
+ say("(Patch is indented %d space%s.)\n", p_indent,
+ p_indent == 1 ? "" : "s");
+ skip_to(p_start, p_sline);
+ while (filearg[0] == NULL) {
+ if (force || batch) {
+ say("No file to patch. Skipping...\n");
+ filearg[0] = savestr(bestguess);
+ skip_rest_of_patch = true;
+ return true;
+ }
+ ask("File to patch: ");
+ if (*buf != '\n') {
+ free(bestguess);
+ bestguess = savestr(buf);
+ filearg[0] = fetchname(buf, &exists, 0);
+ }
+ if (!exists) {
+ ask("No file found--skip this patch? [n] ");
+ if (*buf != 'y')
+ continue;
+ if (verbose)
+ say("Skipping patch...\n");
+ free(filearg[0]);
+ filearg[0] = fetchname(bestguess, &exists, 0);
+ skip_rest_of_patch = true;
+ return true;
+ }
+ }
+ return true;
+}
+
+/* Determine what kind of diff is in the remaining part of the patch file. */
+
+static int
+intuit_diff_type(void)
+{
+ long this_line = 0, previous_line;
+ long first_command_line = -1;
+ LINENUM fcl_line = -1;
+ bool last_line_was_command = false, this_is_a_command = false;
+ bool stars_last_line = false, stars_this_line = false;
+ char *s, *t;
+ int indent, retval;
+ struct file_name names[MAX_FILE];
+
+ memset(names, 0, sizeof(names));
+ ok_to_create_file = false;
+ fseek(pfp, p_base, SEEK_SET);
+ p_input_line = p_bline - 1;
+ for (;;) {
+ previous_line = this_line;
+ last_line_was_command = this_is_a_command;
+ stars_last_line = stars_this_line;
+ this_line = ftell(pfp);
+ indent = 0;
+ p_input_line++;
+ if (fgets(buf, buf_len, pfp) == NULL) {
+ if (first_command_line >= 0L) {
+ /* nothing but deletes!? */
+ p_start = first_command_line;
+ p_sline = fcl_line;
+ retval = ED_DIFF;
+ goto scan_exit;
+ } else {
+ p_start = this_line;
+ p_sline = p_input_line;
+ retval = 0;
+ goto scan_exit;
+ }
+ }
+ for (s = buf; *s == ' ' || *s == '\t' || *s == 'X'; s++) {
+ if (*s == '\t')
+ indent += 8 - (indent % 8);
+ else
+ indent++;
+ }
+ for (t = s; isdigit((unsigned char)*t) || *t == ','; t++)
+ ;
+ this_is_a_command = (isdigit((unsigned char)*s) &&
+ (*t == 'd' || *t == 'c' || *t == 'a'));
+ if (first_command_line < 0L && this_is_a_command) {
+ first_command_line = this_line;
+ fcl_line = p_input_line;
+ p_indent = indent; /* assume this for now */
+ }
+ if (!stars_last_line && strnEQ(s, "*** ", 4))
+ names[OLD_FILE].path = fetchname(s + 4,
+ &names[OLD_FILE].exists, strippath);
+ else if (strnEQ(s, "--- ", 4))
+ names[NEW_FILE].path = fetchname(s + 4,
+ &names[NEW_FILE].exists, strippath);
+ else if (strnEQ(s, "+++ ", 4))
+ /* pretend it is the old name */
+ names[OLD_FILE].path = fetchname(s + 4,
+ &names[OLD_FILE].exists, strippath);
+ else if (strnEQ(s, "Index:", 6))
+ names[INDEX_FILE].path = fetchname(s + 6,
+ &names[INDEX_FILE].exists, strippath);
+ else if (strnEQ(s, "Prereq:", 7)) {
+ for (t = s + 7; isspace((unsigned char)*t); t++)
+ ;
+ revision = savestr(t);
+ for (t = revision; *t && !isspace((unsigned char)*t); t++)
+ ;
+ *t = '\0';
+ if (*revision == '\0') {
+ free(revision);
+ revision = NULL;
+ }
+ }
+ if ((!diff_type || diff_type == ED_DIFF) &&
+ first_command_line >= 0L &&
+ strEQ(s, ".\n")) {
+ p_indent = indent;
+ p_start = first_command_line;
+ p_sline = fcl_line;
+ retval = ED_DIFF;
+ goto scan_exit;
+ }
+ if ((!diff_type || diff_type == UNI_DIFF) && strnEQ(s, "@@ -", 4)) {
+ if (strnEQ(s + 4, "0,0", 3))
+ ok_to_create_file = true;
+ p_indent = indent;
+ p_start = this_line;
+ p_sline = p_input_line;
+ retval = UNI_DIFF;
+ goto scan_exit;
+ }
+ stars_this_line = strnEQ(s, "********", 8);
+ if ((!diff_type || diff_type == CONTEXT_DIFF) && stars_last_line &&
+ strnEQ(s, "*** ", 4)) {
+ if (atol(s + 4) == 0)
+ ok_to_create_file = true;
+ /*
+ * If this is a new context diff the character just
+ * before the newline is a '*'.
+ */
+ while (*s != '\n')
+ s++;
+ p_indent = indent;
+ p_start = previous_line;
+ p_sline = p_input_line - 1;
+ retval = (*(s - 1) == '*' ? NEW_CONTEXT_DIFF : CONTEXT_DIFF);
+ goto scan_exit;
+ }
+ if ((!diff_type || diff_type == NORMAL_DIFF) &&
+ last_line_was_command &&
+ (strnEQ(s, "< ", 2) || strnEQ(s, "> ", 2))) {
+ p_start = previous_line;
+ p_sline = p_input_line - 1;
+ p_indent = indent;
+ retval = NORMAL_DIFF;
+ goto scan_exit;
+ }
+ }
+scan_exit:
+ if (retval == UNI_DIFF) {
+ /* unswap old and new */
+ struct file_name tmp = names[OLD_FILE];
+ names[OLD_FILE] = names[NEW_FILE];
+ names[NEW_FILE] = tmp;
+ }
+ if (filearg[0] == NULL) {
+ if (posix)
+ filearg[0] = posix_name(names, ok_to_create_file);
+ else {
+ /* Ignore the Index: name for context diffs, like GNU */
+ if (names[OLD_FILE].path != NULL ||
+ names[NEW_FILE].path != NULL) {
+ free(names[INDEX_FILE].path);
+ names[INDEX_FILE].path = NULL;
+ }
+ filearg[0] = best_name(names, ok_to_create_file);
+ }
+ }
+
+ free(bestguess);
+ bestguess = NULL;
+ if (filearg[0] != NULL)
+ bestguess = savestr(filearg[0]);
+ else if (!ok_to_create_file) {
+ /*
+ * We don't want to create a new file but we need a
+ * filename to set bestguess. Avoid setting filearg[0]
+ * so the file is not created automatically.
+ */
+ if (posix)
+ bestguess = posix_name(names, true);
+ else
+ bestguess = best_name(names, true);
+ }
+ free(names[OLD_FILE].path);
+ free(names[NEW_FILE].path);
+ free(names[INDEX_FILE].path);
+ return retval;
+}
+
+/*
+ * Remember where this patch ends so we know where to start up again.
+ */
+static void
+next_intuit_at(LINENUM file_pos, LINENUM file_line)
+{
+ p_base = file_pos;
+ p_bline = file_line;
+}
+
+/*
+ * Basically a verbose fseek() to the actual diff listing.
+ */
+static void
+skip_to(LINENUM file_pos, LINENUM file_line)
+{
+ char *ret;
+
+ if (p_base > file_pos)
+ fatal("Internal error: seek %ld>%ld\n", p_base, file_pos);
+ if (verbose && p_base < file_pos) {
+ fseek(pfp, p_base, SEEK_SET);
+ say("The text leading up to this was:\n--------------------------\n");
+ while (ftell(pfp) < file_pos) {
+ ret = fgets(buf, buf_len, pfp);
+ if (ret == NULL)
+ fatal("Unexpected end of file\n");
+ say("|%s", buf);
+ }
+ say("--------------------------\n");
+ } else
+ fseek(pfp, file_pos, SEEK_SET);
+ p_input_line = file_line - 1;
+}
+
+/* Make this a function for better debugging. */
+__dead static void
+malformed(void)
+{
+ fatal("malformed patch at line %ld: %s", p_input_line, buf);
+ /* about as informative as "Syntax error" in C */
+}
+
+static LINENUM
+getlinenum(const char *s)
+{
+ LINENUM l = (LINENUM)atol(s);
+ if (l < 0) {
+ l = 0;
+ malformed();
+ }
+ return l;
+}
+
+static LINENUM
+getskiplinenum(char **p)
+{
+ char *s = *p;
+ LINENUM l = getlinenum(s);
+ while (isdigit((unsigned char)*s))
+ s++;
+ *p = s;
+ return l;
+}
+
+/*
+ * True if the line has been discarded (i.e., it is a line saying
+ * "\ No newline at end of file".)
+ */
+static bool
+remove_special_line(void)
+{
+ int c;
+
+ c = fgetc(pfp);
+ if (c == '\\') {
+ do {
+ c = fgetc(pfp);
+ } while (c != EOF && c != '\n');
+
+ return true;
+ }
+ if (c != EOF)
+ fseek(pfp, -1L, SEEK_CUR);
+
+ return false;
+}
+
+/*
+ * True if there is more of the current diff listing to process.
+ */
+bool
+another_hunk(void)
+{
+ long line_beginning; /* file pos of the current line */
+ LINENUM repl_beginning; /* index of --- line */
+ LINENUM fillcnt; /* #lines of missing ptrn or repl */
+ LINENUM fillsrc; /* index of first line to copy */
+ LINENUM filldst; /* index of first missing line */
+ bool ptrn_spaces_eaten; /* ptrn was slightly misformed */
+ bool repl_could_be_missing; /* no + or ! lines in this hunk */
+ bool repl_missing; /* we are now backtracking */
+ long repl_backtrack_position; /* file pos of first repl line */
+ LINENUM repl_patch_line; /* input line number for same */
+ LINENUM ptrn_copiable; /* # of copiable lines in ptrn */
+ char *s, *ret;
+ int context = 0;
+
+ while (p_end >= 0) {
+ if (p_end == p_efake)
+ p_end = p_bfake; /* don't free twice */
+ else
+ free(p_line[p_end]);
+ p_end--;
+ }
+ p_efake = -1;
+
+ p_max = hunkmax; /* gets reduced when --- found */
+ if (diff_type == CONTEXT_DIFF || diff_type == NEW_CONTEXT_DIFF) {
+ line_beginning = ftell(pfp);
+ repl_beginning = 0;
+ fillcnt = 0;
+ fillsrc = 0;
+ filldst = 0;
+ ptrn_spaces_eaten = false;
+ repl_could_be_missing = true;
+ repl_missing = false;
+ repl_backtrack_position = 0;
+ repl_patch_line = 0;
+ ptrn_copiable = 0;
+
+ ret = pgets(buf, buf_len, pfp);
+ p_input_line++;
+ if (ret == NULL || strnNE(buf, "********", 8)) {
+ next_intuit_at(line_beginning, p_input_line);
+ return false;
+ }
+ p_context = 100;
+ p_hunk_beg = p_input_line + 1;
+ while (p_end < p_max) {
+ line_beginning = ftell(pfp);
+ ret = pgets(buf, buf_len, pfp);
+ p_input_line++;
+ if (ret == NULL) {
+ if (p_max - p_end < 4) {
+ /* assume blank lines got chopped */
+ strlcpy(buf, " \n", buf_len);
+ } else {
+ if (repl_beginning && repl_could_be_missing) {
+ repl_missing = true;
+ goto hunk_done;
+ }
+ fatal("unexpected end of file in patch\n");
+ }
+ }
+ p_end++;
+ if (p_end >= hunkmax)
+ fatal("Internal error: hunk larger than hunk "
+ "buffer size");
+ p_char[p_end] = *buf;
+ p_line[p_end] = NULL;
+ switch (*buf) {
+ case '*':
+ if (strnEQ(buf, "********", 8)) {
+ if (repl_beginning && repl_could_be_missing) {
+ repl_missing = true;
+ goto hunk_done;
+ } else
+ fatal("unexpected end of hunk "
+ "at line %ld\n",
+ p_input_line);
+ }
+ if (p_end != 0) {
+ if (repl_beginning && repl_could_be_missing) {
+ repl_missing = true;
+ goto hunk_done;
+ }
+ fatal("unexpected *** at line %ld: %s",
+ p_input_line, buf);
+ }
+ context = 0;
+ p_line[p_end] = savestr(buf);
+ if (out_of_mem) {
+ p_end--;
+ return false;
+ }
+ for (s = buf; *s && !isdigit((unsigned char)*s); s++)
+ ;
+ if (!*s)
+ malformed();
+ if (strnEQ(s, "0,0", 3))
+ memmove(s, s + 2, strlen(s + 2) + 1);
+ p_first = getskiplinenum(&s);
+ if (*s == ',') {
+ for (; *s && !isdigit((unsigned char)*s); s++)
+ ;
+ if (!*s)
+ malformed();
+ p_ptrn_lines = (getlinenum(s)) - p_first + 1;
+ if (p_ptrn_lines < 0)
+ malformed();
+ } else if (p_first)
+ p_ptrn_lines = 1;
+ else {
+ p_ptrn_lines = 0;
+ p_first = 1;
+ }
+ if (p_first >= LINENUM_MAX - p_ptrn_lines ||
+ p_ptrn_lines >= LINENUM_MAX - 6)
+ malformed();
+
+ /* we need this much at least */
+ p_max = p_ptrn_lines + 6;
+ while (p_max >= hunkmax)
+ grow_hunkmax();
+ p_max = hunkmax;
+ break;
+ case '-':
+ if (buf[1] == '-') {
+ if (repl_beginning ||
+ (p_end != p_ptrn_lines + 1 +
+ (p_char[p_end - 1] == '\n'))) {
+ if (p_end == 1) {
+ /*
+ * `old' lines were omitted;
+ * set up to fill them in
+ * from 'new' context lines.
+ */
+ p_end = p_ptrn_lines + 1;
+ fillsrc = p_end + 1;
+ filldst = 1;
+ fillcnt = p_ptrn_lines;
+ } else {
+ if (repl_beginning) {
+ if (repl_could_be_missing) {
+ repl_missing = true;
+ goto hunk_done;
+ }
+ fatal("duplicate \"---\" at line %ld--check line numbers at line %ld\n",
+ p_input_line, p_hunk_beg + repl_beginning);
+ } else {
+ fatal("%s \"---\" at line %ld--check line numbers at line %ld\n",
+ (p_end <= p_ptrn_lines
+ ? "Premature"
+ : "Overdue"),
+ p_input_line, p_hunk_beg);
+ }
+ }
+ }
+ repl_beginning = p_end;
+ repl_backtrack_position = ftell(pfp);
+ repl_patch_line = p_input_line;
+ p_line[p_end] = savestr(buf);
+ if (out_of_mem) {
+ p_end--;
+ return false;
+ }
+ p_char[p_end] = '=';
+ for (s = buf; *s && !isdigit((unsigned char)*s); s++)
+ ;
+ if (!*s)
+ malformed();
+ p_newfirst = getskiplinenum(&s);
+ if (*s == ',') {
+ for (; *s && !isdigit((unsigned char)*s); s++)
+ ;
+ if (!*s)
+ malformed();
+ p_repl_lines = (getlinenum(s)) -
+ p_newfirst + 1;
+ if (p_repl_lines < 0)
+ malformed();
+ } else if (p_newfirst)
+ p_repl_lines = 1;
+ else {
+ p_repl_lines = 0;
+ p_newfirst = 1;
+ }
+ if (p_newfirst >= LINENUM_MAX - p_repl_lines ||
+ p_repl_lines >= LINENUM_MAX - p_end)
+ malformed();
+ p_max = p_repl_lines + p_end;
+ if (p_max > MAXHUNKSIZE)
+ fatal("hunk too large (%ld lines) at line %ld: %s",
+ p_max, p_input_line, buf);
+ while (p_max >= hunkmax)
+ grow_hunkmax();
+ if (p_repl_lines != ptrn_copiable &&
+ (p_context != 0 || p_repl_lines != 1))
+ repl_could_be_missing = false;
+ break;
+ }
+ goto change_line;
+ case '+':
+ case '!':
+ repl_could_be_missing = false;
+ change_line:
+ if (buf[1] == '\n' && canonicalize)
+ strlcpy(buf + 1, " \n", buf_len - 1);
+ if (!isspace((unsigned char)buf[1]) && buf[1] != '>' &&
+ buf[1] != '<' &&
+ repl_beginning && repl_could_be_missing) {
+ repl_missing = true;
+ goto hunk_done;
+ }
+ if (context >= 0) {
+ if (context < p_context)
+ p_context = context;
+ context = -1000;
+ }
+ p_line[p_end] = savestr(buf + 2);
+ if (out_of_mem) {
+ p_end--;
+ return false;
+ }
+ if (p_end == p_ptrn_lines) {
+ if (remove_special_line()) {
+ int len;
+
+ len = strlen(p_line[p_end]) - 1;
+ (p_line[p_end])[len] = 0;
+ }
+ }
+ break;
+ case '\t':
+ case '\n': /* assume the 2 spaces got eaten */
+ if (repl_beginning && repl_could_be_missing &&
+ (!ptrn_spaces_eaten ||
+ diff_type == NEW_CONTEXT_DIFF)) {
+ repl_missing = true;
+ goto hunk_done;
+ }
+ p_line[p_end] = savestr(buf);
+ if (out_of_mem) {
+ p_end--;
+ return false;
+ }
+ if (p_end != p_ptrn_lines + 1) {
+ ptrn_spaces_eaten |= (repl_beginning != 0);
+ context++;
+ if (!repl_beginning)
+ ptrn_copiable++;
+ p_char[p_end] = ' ';
+ }
+ break;
+ case ' ':
+ if (!isspace((unsigned char)buf[1]) &&
+ repl_beginning && repl_could_be_missing) {
+ repl_missing = true;
+ goto hunk_done;
+ }
+ context++;
+ if (!repl_beginning)
+ ptrn_copiable++;
+ p_line[p_end] = savestr(buf + 2);
+ if (out_of_mem) {
+ p_end--;
+ return false;
+ }
+ break;
+ default:
+ if (repl_beginning && repl_could_be_missing) {
+ repl_missing = true;
+ goto hunk_done;
+ }
+ malformed();
+ }
+ /* set up p_len for strncmp() so we don't have to */
+ /* assume null termination */
+ if (p_line[p_end])
+ p_len[p_end] = strlen(p_line[p_end]);
+ else
+ p_len[p_end] = 0;
+ }
+
+hunk_done:
+ if (p_end >= 0 && !repl_beginning)
+ fatal("no --- found in patch at line %ld\n", pch_hunk_beg());
+
+ if (repl_missing) {
+
+ /* reset state back to just after --- */
+ p_input_line = repl_patch_line;
+ for (p_end--; p_end > repl_beginning; p_end--)
+ free(p_line[p_end]);
+ fseek(pfp, repl_backtrack_position, SEEK_SET);
+
+ /* redundant 'new' context lines were omitted - set */
+ /* up to fill them in from the old file context */
+ if (!p_context && p_repl_lines == 1) {
+ p_repl_lines = 0;
+ p_max--;
+ }
+ fillsrc = 1;
+ filldst = repl_beginning + 1;
+ fillcnt = p_repl_lines;
+ p_end = p_max;
+ } else if (!p_context && fillcnt == 1) {
+ /* the first hunk was a null hunk with no context */
+ /* and we were expecting one line -- fix it up. */
+ while (filldst < p_end) {
+ p_line[filldst] = p_line[filldst + 1];
+ p_char[filldst] = p_char[filldst + 1];
+ p_len[filldst] = p_len[filldst + 1];
+ filldst++;
+ }
+#if 0
+ repl_beginning--; /* this doesn't need to be fixed */
+#endif
+ p_end--;
+ p_first++; /* do append rather than insert */
+ fillcnt = 0;
+ p_ptrn_lines = 0;
+ }
+ if (diff_type == CONTEXT_DIFF &&
+ (fillcnt || (p_first > 1 && ptrn_copiable > 2 * p_context))) {
+ if (verbose)
+ say("%s\n%s\n%s\n",
+ "(Fascinating--this is really a new-style context diff but without",
+ "the telltale extra asterisks on the *** line that usually indicate",
+ "the new style...)");
+ diff_type = NEW_CONTEXT_DIFF;
+ }
+ /* if there were omitted context lines, fill them in now */
+ if (fillcnt) {
+ p_bfake = filldst; /* remember where not to free() */
+ p_efake = filldst + fillcnt - 1;
+ while (fillcnt-- > 0) {
+ while (fillsrc <= p_end && p_char[fillsrc] != ' ')
+ fillsrc++;
+ if (fillsrc > p_end)
+ fatal("replacement text or line numbers mangled in hunk at line %ld\n",
+ p_hunk_beg);
+ p_line[filldst] = p_line[fillsrc];
+ p_char[filldst] = p_char[fillsrc];
+ p_len[filldst] = p_len[fillsrc];
+ fillsrc++;
+ filldst++;
+ }
+ while (fillsrc <= p_end && fillsrc != repl_beginning &&
+ p_char[fillsrc] != ' ')
+ fillsrc++;
+#ifdef DEBUGGING
+ if (debug & 64)
+ printf("fillsrc %ld, filldst %ld, rb %ld, e+1 %ld\n",
+ fillsrc, filldst, repl_beginning, p_end + 1);
+#endif
+ if (fillsrc != p_end + 1 && fillsrc != repl_beginning)
+ malformed();
+ if (filldst != p_end + 1 && filldst != repl_beginning)
+ malformed();
+ }
+ if (p_line[p_end] != NULL) {
+ if (remove_special_line()) {
+ p_len[p_end] -= 1;
+ (p_line[p_end])[p_len[p_end]] = 0;
+ }
+ }
+ } else if (diff_type == UNI_DIFF) {
+ LINENUM fillold; /* index of old lines */
+ LINENUM fillnew; /* index of new lines */
+ char ch;
+
+ line_beginning = ftell(pfp); /* file pos of the current line */
+ ret = pgets(buf, buf_len, pfp);
+ p_input_line++;
+ if (ret == NULL || strnNE(buf, "@@ -", 4)) {
+ next_intuit_at(line_beginning, p_input_line);
+ return false;
+ }
+ s = buf + 4;
+ if (!*s)
+ malformed();
+ p_first = getskiplinenum(&s);
+ if (*s == ',') {
+ s++;
+ p_ptrn_lines = getskiplinenum(&s);
+ } else
+ p_ptrn_lines = 1;
+ if (p_first >= LINENUM_MAX - p_ptrn_lines)
+ malformed();
+ if (*s == ' ')
+ s++;
+ if (*s != '+' || !*++s)
+ malformed();
+ p_newfirst = getskiplinenum(&s);
+ if (*s == ',') {
+ s++;
+ p_repl_lines = getskiplinenum(&s);
+ } else
+ p_repl_lines = 1;
+ if (*s == ' ')
+ s++;
+ if (*s != '@')
+ malformed();
+ if (p_first >= LINENUM_MAX - p_ptrn_lines ||
+ p_newfirst > LINENUM_MAX - p_repl_lines ||
+ p_ptrn_lines >= LINENUM_MAX - p_repl_lines - 1)
+ malformed();
+ if (!p_ptrn_lines)
+ p_first++; /* do append rather than insert */
+ p_max = p_ptrn_lines + p_repl_lines + 1;
+ while (p_max >= hunkmax)
+ grow_hunkmax();
+ fillold = 1;
+ fillnew = fillold + p_ptrn_lines;
+ p_end = fillnew + p_repl_lines;
+ snprintf(buf, buf_len, "*** %ld,%ld ****\n", p_first,
+ p_first + p_ptrn_lines - 1);
+ p_line[0] = savestr(buf);
+ if (out_of_mem) {
+ p_end = -1;
+ return false;
+ }
+ p_char[0] = '*';
+ snprintf(buf, buf_len, "--- %ld,%ld ----\n", p_newfirst,
+ p_newfirst + p_repl_lines - 1);
+ p_line[fillnew] = savestr(buf);
+ if (out_of_mem) {
+ p_end = 0;
+ return false;
+ }
+ p_char[fillnew++] = '=';
+ p_context = 100;
+ context = 0;
+ p_hunk_beg = p_input_line + 1;
+ while (fillold <= p_ptrn_lines || fillnew <= p_end) {
+ line_beginning = ftell(pfp);
+ ret = pgets(buf, buf_len, pfp);
+ p_input_line++;
+ if (ret == NULL) {
+ if (p_max - fillnew < 3) {
+ /* assume blank lines got chopped */
+ strlcpy(buf, " \n", buf_len);
+ } else {
+ fatal("unexpected end of file in patch\n");
+ }
+ }
+ if (*buf == '\t' || *buf == '\n') {
+ ch = ' '; /* assume the space got eaten */
+ s = savestr(buf);
+ } else {
+ ch = *buf;
+ s = savestr(buf + 1);
+ }
+ if (out_of_mem) {
+ while (--fillnew > p_ptrn_lines)
+ free(p_line[fillnew]);
+ p_end = fillold - 1;
+ return false;
+ }
+ switch (ch) {
+ case '-':
+ if (fillold > p_ptrn_lines) {
+ free(s);
+ p_end = fillnew - 1;
+ malformed();
+ }
+ p_char[fillold] = ch;
+ p_line[fillold] = s;
+ p_len[fillold++] = strlen(s);
+ if (fillold > p_ptrn_lines) {
+ if (remove_special_line()) {
+ p_len[fillold - 1] -= 1;
+ s[p_len[fillold - 1]] = 0;
+ }
+ }
+ break;
+ case '=':
+ ch = ' ';
+ /* FALL THROUGH */
+ case ' ':
+ if (fillold > p_ptrn_lines) {
+ free(s);
+ while (--fillnew > p_ptrn_lines)
+ free(p_line[fillnew]);
+ p_end = fillold - 1;
+ malformed();
+ }
+ context++;
+ p_char[fillold] = ch;
+ p_line[fillold] = s;
+ p_len[fillold++] = strlen(s);
+ s = savestr(s);
+ if (out_of_mem) {
+ while (--fillnew > p_ptrn_lines)
+ free(p_line[fillnew]);
+ p_end = fillold - 1;
+ return false;
+ }
+ if (fillold > p_ptrn_lines) {
+ if (remove_special_line()) {
+ p_len[fillold - 1] -= 1;
+ s[p_len[fillold - 1]] = 0;
+ }
+ }
+ /* FALL THROUGH */
+ case '+':
+ if (fillnew > p_end) {
+ free(s);
+ while (--fillnew > p_ptrn_lines)
+ free(p_line[fillnew]);
+ p_end = fillold - 1;
+ malformed();
+ }
+ p_char[fillnew] = ch;
+ p_line[fillnew] = s;
+ p_len[fillnew++] = strlen(s);
+ if (fillold > p_ptrn_lines) {
+ if (remove_special_line()) {
+ p_len[fillnew - 1] -= 1;
+ s[p_len[fillnew - 1]] = 0;
+ }
+ }
+ break;
+ default:
+ p_end = fillnew;
+ malformed();
+ }
+ if (ch != ' ' && context > 0) {
+ if (context < p_context)
+ p_context = context;
+ context = -1000;
+ }
+ } /* while */
+ } else { /* normal diff--fake it up */
+ char hunk_type;
+ int i;
+ LINENUM min, max;
+
+ line_beginning = ftell(pfp);
+ p_context = 0;
+ ret = pgets(buf, buf_len, pfp);
+ p_input_line++;
+ if (ret == NULL || !isdigit((unsigned char)*buf)) {
+ next_intuit_at(line_beginning, p_input_line);
+ return false;
+ }
+ s = buf;
+ p_first = getskiplinenum(&s);
+ if (*s == ',') {
+ s++;
+ p_ptrn_lines = getskiplinenum(&s) - p_first + 1;
+ } else
+ p_ptrn_lines = (*s != 'a');
+ if (p_first >= LINENUM_MAX - p_ptrn_lines)
+ malformed();
+ hunk_type = *s++;
+ if (hunk_type == 'a')
+ p_first++; /* do append rather than insert */
+ min = getskiplinenum(&s);
+ if (*s == ',')
+ max = getlinenum(++s);
+ else
+ max = min;
+ if (min < 0 || min > max || max - min == LINENUM_MAX)
+ malformed();
+ if (hunk_type == 'd')
+ min++;
+ p_end = p_ptrn_lines + 1 + max - min + 1;
+ p_newfirst = min;
+ p_repl_lines = max - min + 1;
+ if (p_newfirst > LINENUM_MAX - p_repl_lines ||
+ p_ptrn_lines >= LINENUM_MAX - p_repl_lines - 1)
+ malformed();
+ p_end = p_ptrn_lines + p_repl_lines + 1;
+ if (p_end > MAXHUNKSIZE)
+ fatal("hunk too large (%ld lines) at line %ld: %s",
+ p_end, p_input_line, buf);
+ while (p_end >= hunkmax)
+ grow_hunkmax();
+ snprintf(buf, buf_len, "*** %ld,%ld\n", p_first,
+ p_first + p_ptrn_lines - 1);
+ p_line[0] = savestr(buf);
+ if (out_of_mem) {
+ p_end = -1;
+ return false;
+ }
+ p_char[0] = '*';
+ for (i = 1; i <= p_ptrn_lines; i++) {
+ ret = pgets(buf, buf_len, pfp);
+ p_input_line++;
+ if (ret == NULL)
+ fatal("unexpected end of file in patch at line %ld\n",
+ p_input_line);
+ if (*buf != '<')
+ fatal("< expected at line %ld of patch\n",
+ p_input_line);
+ p_line[i] = savestr(buf + 2);
+ if (out_of_mem) {
+ p_end = i - 1;
+ return false;
+ }
+ p_len[i] = strlen(p_line[i]);
+ p_char[i] = '-';
+ }
+
+ if (remove_special_line()) {
+ p_len[i - 1] -= 1;
+ (p_line[i - 1])[p_len[i - 1]] = 0;
+ }
+ if (hunk_type == 'c') {
+ ret = pgets(buf, buf_len, pfp);
+ p_input_line++;
+ if (ret == NULL)
+ fatal("unexpected end of file in patch at line %ld\n",
+ p_input_line);
+ if (*buf != '-')
+ fatal("--- expected at line %ld of patch\n",
+ p_input_line);
+ }
+ snprintf(buf, buf_len, "--- %ld,%ld\n", min, max);
+ p_line[i] = savestr(buf);
+ if (out_of_mem) {
+ p_end = i - 1;
+ return false;
+ }
+ p_char[i] = '=';
+ for (i++; i <= p_end; i++) {
+ ret = pgets(buf, buf_len, pfp);
+ p_input_line++;
+ if (ret == NULL)
+ fatal("unexpected end of file in patch at line %ld\n",
+ p_input_line);
+ if (*buf != '>')
+ fatal("> expected at line %ld of patch\n",
+ p_input_line);
+ p_line[i] = savestr(buf + 2);
+ if (out_of_mem) {
+ p_end = i - 1;
+ return false;
+ }
+ p_len[i] = strlen(p_line[i]);
+ p_char[i] = '+';
+ }
+
+ if (remove_special_line()) {
+ p_len[i - 1] -= 1;
+ (p_line[i - 1])[p_len[i - 1]] = 0;
+ }
+ }
+ if (reverse) /* backwards patch? */
+ if (!pch_swap())
+ say("Not enough memory to swap next hunk!\n");
+#ifdef DEBUGGING
+ if (debug & 2) {
+ int i;
+ char special;
+
+ for (i = 0; i <= p_end; i++) {
+ if (i == p_ptrn_lines)
+ special = '^';
+ else
+ special = ' ';
+ fprintf(stderr, "%3d %c %c %s", i, p_char[i],
+ special, p_line[i]);
+ fflush(stderr);
+ }
+ }
+#endif
+ if (p_end + 1 < hunkmax)/* paranoia reigns supreme... */
+ p_char[p_end + 1] = '^'; /* add a stopper for apply_hunk */
+ return true;
+}
+
+/*
+ * Input a line from the patch file, worrying about indentation.
+ */
+static char *
+pgets(char *bf, int sz, FILE *fp)
+{
+ char *s, *ret = fgets(bf, sz, fp);
+ int indent = 0;
+
+ if (p_indent && ret != NULL) {
+ for (s = buf;
+ indent < p_indent && (*s == ' ' || *s == '\t' || *s == 'X');
+ s++) {
+ if (*s == '\t')
+ indent += 8 - (indent % 7);
+ else
+ indent++;
+ }
+ if (buf != s && strlcpy(buf, s, buf_len) >= buf_len)
+ fatal("buffer too small in pgets()\n");
+ }
+ return ret;
+}
+
+/*
+ * Reverse the old and new portions of the current hunk.
+ */
+bool
+pch_swap(void)
+{
+ char **tp_line; /* the text of the hunk */
+ short *tp_len; /* length of each line */
+ char *tp_char; /* +, -, and ! */
+ LINENUM i;
+ LINENUM n;
+ bool blankline = false;
+ char *s;
+
+ i = p_first;
+ p_first = p_newfirst;
+ p_newfirst = i;
+
+ /* make a scratch copy */
+
+ tp_line = p_line;
+ tp_len = p_len;
+ tp_char = p_char;
+ p_line = NULL; /* force set_hunkmax to allocate again */
+ p_len = NULL;
+ p_char = NULL;
+ set_hunkmax();
+ if (p_line == NULL || p_len == NULL || p_char == NULL) {
+
+ free(p_line);
+ p_line = tp_line;
+ free(p_len);
+ p_len = tp_len;
+ free(p_char);
+ p_char = tp_char;
+ return false; /* not enough memory to swap hunk! */
+ }
+ /* now turn the new into the old */
+
+ i = p_ptrn_lines + 1;
+ if (tp_char[i] == '\n') { /* account for possible blank line */
+ blankline = true;
+ i++;
+ }
+ if (p_efake >= 0) { /* fix non-freeable ptr range */
+ if (p_efake <= i)
+ n = p_end - i + 1;
+ else
+ n = -i;
+ p_efake += n;
+ p_bfake += n;
+ }
+ for (n = 0; i <= p_end; i++, n++) {
+ p_line[n] = tp_line[i];
+ p_char[n] = tp_char[i];
+ if (p_char[n] == '+')
+ p_char[n] = '-';
+ p_len[n] = tp_len[i];
+ }
+ if (blankline) {
+ i = p_ptrn_lines + 1;
+ p_line[n] = tp_line[i];
+ p_char[n] = tp_char[i];
+ p_len[n] = tp_len[i];
+ n++;
+ }
+ if (p_char[0] != '=')
+ fatal("Malformed patch at line %ld: expected '=' found '%c'\n",
+ p_input_line, p_char[0]);
+ p_char[0] = '*';
+ for (s = p_line[0]; *s; s++)
+ if (*s == '-')
+ *s = '*';
+
+ /* now turn the old into the new */
+
+ if (p_char[0] != '*')
+ fatal("Malformed patch at line %ld: expected '*' found '%c'\n",
+ p_input_line, p_char[0]);
+ tp_char[0] = '=';
+ for (s = tp_line[0]; *s; s++)
+ if (*s == '*')
+ *s = '-';
+ for (i = 0; n <= p_end; i++, n++) {
+ p_line[n] = tp_line[i];
+ p_char[n] = tp_char[i];
+ if (p_char[n] == '-')
+ p_char[n] = '+';
+ p_len[n] = tp_len[i];
+ }
+
+ if (i != p_ptrn_lines + 1)
+ fatal("Malformed patch at line %ld: expected %ld lines, "
+ "got %ld\n",
+ p_input_line, p_ptrn_lines + 1, i);
+
+ i = p_ptrn_lines;
+ p_ptrn_lines = p_repl_lines;
+ p_repl_lines = i;
+
+ free(tp_line);
+ free(tp_len);
+ free(tp_char);
+
+ return true;
+}
+
+/*
+ * Return the specified line position in the old file of the old context.
+ */
+LINENUM
+pch_first(void)
+{
+ return p_first;
+}
+
+/*
+ * Return the number of lines of old context.
+ */
+LINENUM
+pch_ptrn_lines(void)
+{
+ return p_ptrn_lines;
+}
+
+/*
+ * Return the probable line position in the new file of the first line.
+ */
+LINENUM
+pch_newfirst(void)
+{
+ return p_newfirst;
+}
+
+/*
+ * Return the number of lines in the replacement text including context.
+ */
+LINENUM
+pch_repl_lines(void)
+{
+ return p_repl_lines;
+}
+
+/*
+ * Return the number of lines in the whole hunk.
+ */
+LINENUM
+pch_end(void)
+{
+ return p_end;
+}
+
+/*
+ * Return the number of context lines before the first changed line.
+ */
+LINENUM
+pch_context(void)
+{
+ return p_context;
+}
+
+/*
+ * Return the length of a particular patch line.
+ */
+short
+pch_line_len(LINENUM line)
+{
+ return p_len[line];
+}
+
+/*
+ * Return the control character (+, -, *, !, etc) for a patch line.
+ */
+char
+pch_char(LINENUM line)
+{
+ return p_char[line];
+}
+
+/*
+ * Return a pointer to a particular patch line.
+ */
+char *
+pfetch(LINENUM line)
+{
+ return p_line[line];
+}
+
+/*
+ * Return where in the patch file this hunk began, for error messages.
+ */
+LINENUM
+pch_hunk_beg(void)
+{
+ return p_hunk_beg;
+}
+
+/*
+ * Apply an ed script by feeding ed itself.
+ */
+void
+do_ed_script(void)
+{
+ char *t;
+ long beginning_of_this_line;
+ FILE *pipefp = NULL;
+ int continuation;
+
+ if (!skip_rest_of_patch) {
+ if (copy_file(filearg[0], TMPOUTNAME) < 0) {
+ unlink(TMPOUTNAME);
+ fatal("can't create temp file %s", TMPOUTNAME);
+ }
+ snprintf(buf, buf_len, "%s -S%s %s", _PATH_ED,
+ verbose ? "" : "s", TMPOUTNAME);
+ pipefp = popen(buf, "w");
+ }
+ for (;;) {
+ beginning_of_this_line = ftell(pfp);
+ if (pgets(buf, buf_len, pfp) == NULL) {
+ next_intuit_at(beginning_of_this_line, p_input_line);
+ break;
+ }
+ p_input_line++;
+ for (t = buf; isdigit((unsigned char)*t) || *t == ','; t++)
+ ;
+ /* POSIX defines allowed commands as {a,c,d,i,s} */
+ if (isdigit((unsigned char)*buf) && (*t == 'a' || *t == 'c' ||
+ *t == 'd' || *t == 'i' || *t == 's')) {
+ if (pipefp != NULL)
+ fputs(buf, pipefp);
+ if (*t == 's') {
+ for (;;) {
+ continuation = 0;
+ t = strchr(buf, '\0') - 1;
+ while (--t >= buf && *t == '\\')
+ continuation = !continuation;
+ if (!continuation ||
+ pgets(buf, sizeof buf, pfp) == NULL)
+ break;
+ if (pipefp != NULL)
+ fputs(buf, pipefp);
+ }
+ } else if (*t != 'd') {
+ while (pgets(buf, buf_len, pfp) != NULL) {
+ p_input_line++;
+ if (pipefp != NULL)
+ fputs(buf, pipefp);
+ if (strEQ(buf, ".\n"))
+ break;
+ }
+ }
+ } else {
+ next_intuit_at(beginning_of_this_line, p_input_line);
+ break;
+ }
+ }
+ if (pipefp == NULL)
+ return;
+ fprintf(pipefp, "w\n");
+ fprintf(pipefp, "q\n");
+ fflush(pipefp);
+ pclose(pipefp);
+ ignore_signals();
+ if (!check_only) {
+ if (move_file(TMPOUTNAME, outname) < 0) {
+ toutkeep = true;
+ chmod(TMPOUTNAME, filemode);
+ } else
+ chmod(outname, filemode);
+ }
+ set_signals(1);
+}
+
+/*
+ * Choose the name of the file to be patched based on POSIX rules.
+ * NOTE: the POSIX rules are amazingly stupid and we only follow them
+ * if the user specified --posix or set POSIXLY_CORRECT.
+ */
+static char *
+posix_name(const struct file_name *names, bool assume_exists)
+{
+ char *path = NULL;
+ int i;
+
+ /*
+ * POSIX states that the filename will be chosen from one
+ * of the old, new and index names (in that order) if
+ * the file exists relative to CWD after -p stripping.
+ */
+ for (i = 0; i < MAX_FILE; i++) {
+ if (names[i].path != NULL && names[i].exists) {
+ path = names[i].path;
+ break;
+ }
+ }
+ if (path == NULL && !assume_exists) {
+ /*
+ * No files found, look for something we can checkout from
+ * RCS/SCCS dirs. Same order as above.
+ */
+ for (i = 0; i < MAX_FILE; i++) {
+ if (names[i].path != NULL &&
+ (path = checked_in(names[i].path)) != NULL)
+ break;
+ }
+ /*
+ * Still no match? Check to see if the diff could be creating
+ * a new file.
+ */
+ if (path == NULL && ok_to_create_file &&
+ names[NEW_FILE].path != NULL)
+ path = names[NEW_FILE].path;
+ }
+
+ return path ? savestr(path) : NULL;
+}
+
+/*
+ * Choose the name of the file to be patched based the "best" one
+ * available.
+ */
+static char *
+best_name(const struct file_name *names, bool assume_exists)
+{
+ size_t min_components, min_baselen, min_len, tmp;
+ char *best = NULL;
+ int i;
+
+ /*
+ * The "best" name is the one with the fewest number of path
+ * components, the shortest basename length, and the shortest
+ * overall length (in that order). We only use the Index: file
+ * if neither of the old or new files could be intuited from
+ * the diff header.
+ */
+ min_components = min_baselen = min_len = SIZE_MAX;
+ for (i = INDEX_FILE; i >= OLD_FILE; i--) {
+ if (names[i].path == NULL ||
+ (!names[i].exists && !assume_exists))
+ continue;
+ if ((tmp = num_components(names[i].path)) > min_components)
+ continue;
+ min_components = tmp;
+ if ((tmp = strlen(basename(names[i].path))) > min_baselen)
+ continue;
+ min_baselen = tmp;
+ if ((tmp = strlen(names[i].path)) > min_len)
+ continue;
+ min_len = tmp;
+ best = names[i].path;
+ }
+ if (best == NULL) {
+ /*
+ * No files found, look for something we can checkout from
+ * RCS/SCCS dirs. Logic is identical to that above...
+ */
+ min_components = min_baselen = min_len = SIZE_MAX;
+ for (i = INDEX_FILE; i >= OLD_FILE; i--) {
+ if (names[i].path == NULL ||
+ checked_in(names[i].path) == NULL)
+ continue;
+ if ((tmp = num_components(names[i].path)) > min_components)
+ continue;
+ min_components = tmp;
+ if ((tmp = strlen(basename(names[i].path))) > min_baselen)
+ continue;
+ min_baselen = tmp;
+ if ((tmp = strlen(names[i].path)) > min_len)
+ continue;
+ min_len = tmp;
+ best = names[i].path;
+ }
+ /*
+ * Still no match? Check to see if the diff could be creating
+ * a new file.
+ */
+ if (best == NULL && ok_to_create_file &&
+ names[NEW_FILE].path != NULL)
+ best = names[NEW_FILE].path;
+ }
+
+ return best ? savestr(best) : NULL;
+}
+
+static size_t
+num_components(const char *path)
+{
+ size_t n;
+ const char *cp;
+
+ for (n = 0, cp = path; (cp = strchr(cp, '/')) != NULL; n++, cp++) {
+ while (*cp == '/')
+ cp++; /* skip consecutive slashes */
+ }
+ return n;
+}
diff --git a/usr.bin/patch/pch.h b/usr.bin/patch/pch.h
new file mode 100644
index 0000000..a2d45a7
--- /dev/null
+++ b/usr.bin/patch/pch.h
@@ -0,0 +1,59 @@
+/*
+ * $OpenBSD: pch.h,v 1.9 2003/10/31 20:20:45 millert Exp $
+ * $DragonFly: src/usr.bin/patch/pch.h,v 1.1 2004/09/24 18:44:28 joerg Exp $
+ * $NetBSD: pch.h,v 1.10 2008/09/19 18:33:34 joerg Exp $
+ */
+
+/*
+ * patch - a program to apply diffs to original files
+ *
+ * Copyright 1986, Larry Wall
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following condition is met:
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this condition and the following disclaimer.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE FOR
+ * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * -C option added in 1998, original code by Marc Espie, based on FreeBSD
+ * behaviour
+ */
+
+#define OLD_FILE 0
+#define NEW_FILE 1
+#define INDEX_FILE 2
+#define MAX_FILE 3
+
+struct file_name {
+ char *path;
+ bool exists;
+};
+
+void re_patch(void);
+void open_patch_file(const char *);
+void set_hunkmax(void);
+bool there_is_another_patch(void);
+bool another_hunk(void);
+bool pch_swap(void);
+char *pfetch(LINENUM);
+short pch_line_len(LINENUM);
+LINENUM pch_first(void);
+LINENUM pch_ptrn_lines(void);
+LINENUM pch_newfirst(void);
+LINENUM pch_repl_lines(void);
+LINENUM pch_end(void);
+LINENUM pch_context(void);
+LINENUM pch_hunk_beg(void);
+char pch_char(LINENUM);
+void do_ed_script(void);
diff --git a/usr.bin/patch/util.c b/usr.bin/patch/util.c
new file mode 100644
index 0000000..30e4f3c
--- /dev/null
+++ b/usr.bin/patch/util.c
@@ -0,0 +1,447 @@
+/*
+ * $OpenBSD: util.c,v 1.32 2006/03/11 19:41:30 otto Exp $
+ * $DragonFly: src/usr.bin/patch/util.c,v 1.9 2007/09/29 23:11:10 swildner Exp $
+ * $NetBSD: util.c,v 1.28 2018/06/18 18:33:31 christos Exp $
+ */
+
+/*
+ * patch - a program to apply diffs to original files
+ *
+ * Copyright 1986, Larry Wall
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following condition is met:
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this condition and the following disclaimer.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE FOR
+ * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * -C option added in 1998, original code by Marc Espie, based on FreeBSD
+ * behaviour
+ */
+
+#include <sys/cdefs.h>
+__RCSID("$NetBSD: util.c,v 1.28 2018/06/18 18:33:31 christos Exp $");
+
+#include <sys/param.h>
+#include <sys/stat.h>
+
+#include <ctype.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <libgen.h>
+#include <signal.h>
+#include <stdarg.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "common.h"
+#include "util.h"
+#include "backupfile.h"
+#include "pathnames.h"
+
+/* Rename a file, copying it if necessary. */
+
+int
+move_file(const char *from, const char *to)
+{
+ int fromfd;
+ ssize_t i;
+
+ /* to stdout? */
+
+ if (strEQ(to, "-")) {
+#ifdef DEBUGGING
+ if (debug & 4)
+ say("Moving %s to stdout.\n", from);
+#endif
+ fromfd = open(from, O_RDONLY);
+ if (fromfd < 0)
+ pfatal("internal error, can't reopen %s", from);
+ while ((i = read(fromfd, buf, buf_len)) > 0)
+ if (write(STDOUT_FILENO, buf, i) != i)
+ pfatal("write failed");
+ close(fromfd);
+ return 0;
+ }
+ if (backup_file(to) < 0) {
+ say("Can't backup %s, output is in %s: %s\n", to, from,
+ strerror(errno));
+ return -1;
+ }
+#ifdef DEBUGGING
+ if (debug & 4)
+ say("Moving %s to %s.\n", from, to);
+#endif
+ if (rename(from, to) < 0) {
+ if (errno != EXDEV || copy_file(from, to) < 0) {
+ say("Can't create %s, output is in %s: %s\n",
+ to, from, strerror(errno));
+ return -1;
+ }
+ }
+ return 0;
+}
+
+/* Backup the original file. */
+
+int
+backup_file(const char *orig)
+{
+ struct stat filestat;
+ char bakname[MAXPATHLEN], *s, *simplename;
+ dev_t orig_device;
+ ino_t orig_inode;
+
+ if (backup_type == none || stat(orig, &filestat) != 0)
+ return 0; /* nothing to do */
+ /*
+ * If the user used zero prefixes or suffixes, then
+ * he doesn't want backups. Yet we have to remove
+ * orig to break possible hardlinks.
+ */
+ if ((origprae && *origprae == 0) || *simple_backup_suffix == 0) {
+ unlink(orig);
+ return 0;
+ }
+ orig_device = filestat.st_dev;
+ orig_inode = filestat.st_ino;
+
+ if (origprae) {
+ if (strlcpy(bakname, origprae, sizeof(bakname)) >= sizeof(bakname) ||
+ strlcat(bakname, orig, sizeof(bakname)) >= sizeof(bakname))
+ fatal("filename %s too long for buffer\n", origprae);
+ } else {
+ if ((s = find_backup_file_name(orig)) == NULL)
+ fatal("out of memory\n");
+ if (strlcpy(bakname, s, sizeof(bakname)) >= sizeof(bakname))
+ fatal("filename %s too long for buffer\n", s);
+ free(s);
+ }
+
+ if ((simplename = strrchr(bakname, '/')) != NULL)
+ simplename = simplename + 1;
+ else
+ simplename = bakname;
+
+ /*
+ * Find a backup name that is not the same file. Change the
+ * first lowercase char into uppercase; if that isn't
+ * sufficient, chop off the first char and try again.
+ */
+ while (stat(bakname, &filestat) == 0 &&
+ orig_device == filestat.st_dev && orig_inode == filestat.st_ino) {
+ /* Skip initial non-lowercase chars. */
+ for (s = simplename; *s && !islower((unsigned char)*s); s++)
+ ;
+ if (*s)
+ *s = toupper((unsigned char)*s);
+ else
+ memmove(simplename, simplename + 1,
+ strlen(simplename + 1) + 1);
+ }
+#ifdef DEBUGGING
+ if (debug & 4)
+ say("Moving %s to %s.\n", orig, bakname);
+#endif
+ if (rename(orig, bakname) < 0) {
+ if (errno != EXDEV || copy_file(orig, bakname) < 0)
+ return -1;
+ }
+ return 0;
+}
+
+/*
+ * Copy a file.
+ */
+int
+copy_file(const char *from, const char *to)
+{
+ int tofd, fromfd;
+ ssize_t i;
+
+ tofd = open(to, O_CREAT|O_TRUNC|O_WRONLY, 0666);
+ if (tofd < 0)
+ return -1;
+ fromfd = open(from, O_RDONLY, 0);
+ if (fromfd < 0)
+ pfatal("internal error, can't reopen %s", from);
+ while ((i = read(fromfd, buf, buf_len)) > 0)
+ if (write(tofd, buf, i) != i)
+ pfatal("write to %s failed", to);
+ close(fromfd);
+ close(tofd);
+ return 0;
+}
+
+/*
+ * Allocate a unique area for a string.
+ */
+char *
+savestr(const char *s)
+{
+ char *rv;
+
+ if (!s)
+ s = "Oops";
+ rv = strdup(s);
+ if (rv == NULL) {
+ if (using_plan_a)
+ out_of_mem = true;
+ else
+ fatal("out of memory\n");
+ }
+ return rv;
+}
+
+/*
+ * Vanilla terminal output (buffered).
+ */
+void
+say(const char *fmt, ...)
+{
+ va_list ap;
+
+ va_start(ap, fmt);
+ vfprintf(stderr, fmt, ap);
+ va_end(ap);
+ fflush(stderr);
+}
+
+/*
+ * Terminal output, pun intended.
+ */
+void
+fatal(const char *fmt, ...)
+{
+ va_list ap;
+
+ va_start(ap, fmt);
+ fprintf(stderr, "patch: **** ");
+ vfprintf(stderr, fmt, ap);
+ va_end(ap);
+ my_exit(2);
+}
+
+/*
+ * Say something from patch, something from the system, then silence . . .
+ */
+void
+pfatal(const char *fmt, ...)
+{
+ va_list ap;
+ int errnum = errno;
+
+ fprintf(stderr, "patch: **** ");
+ va_start(ap, fmt);
+ vfprintf(stderr, fmt, ap);
+ va_end(ap);
+ fprintf(stderr, ": %s\n", strerror(errnum));
+ my_exit(2);
+}
+
+/*
+ * Get a response from the user via /dev/tty
+ */
+void
+ask(const char *fmt, ...)
+{
+ va_list ap;
+ ssize_t nr = 0;
+ static int ttyfd = -1;
+
+ va_start(ap, fmt);
+ vfprintf(stdout, fmt, ap);
+ va_end(ap);
+ fflush(stdout);
+ if (ttyfd < 0)
+ ttyfd = open(_PATH_TTY, O_RDONLY);
+ if (ttyfd >= 0) {
+ if ((nr = read(ttyfd, buf, buf_len)) > 0 &&
+ buf[nr - 1] == '\n')
+ buf[nr - 1] = '\0';
+ }
+ if (ttyfd < 0 || nr <= 0) {
+ /* no tty or error reading, pretend user entered 'return' */
+ putchar('\n');
+ buf[0] = '\0';
+ }
+}
+
+/*
+ * How to handle certain events when not in a critical region.
+ */
+void
+set_signals(int reset)
+{
+ static sig_t hupval, intval;
+
+ if (!reset) {
+ hupval = signal(SIGHUP, SIG_IGN);
+ if (hupval != SIG_IGN)
+ hupval = my_exit;
+ intval = signal(SIGINT, SIG_IGN);
+ if (intval != SIG_IGN)
+ intval = my_exit;
+ }
+ signal(SIGHUP, hupval);
+ signal(SIGINT, intval);
+}
+
+/*
+ * How to handle certain events when in a critical region.
+ */
+void
+ignore_signals(void)
+{
+ signal(SIGHUP, SIG_IGN);
+ signal(SIGINT, SIG_IGN);
+}
+
+/*
+ * Make sure we'll have the directories to create a file. If `striplast' is
+ * true, ignore the last element of `filename'.
+ */
+
+void
+makedirs(const char *filename, bool striplast)
+{
+ char *tmpbuf;
+
+ if ((tmpbuf = strdup(filename)) == NULL)
+ fatal("out of memory\n");
+
+ if (striplast) {
+ char *s = strrchr(tmpbuf, '/');
+ if (s == NULL) {
+ free(tmpbuf);
+ return; /* nothing to be done */
+ }
+ *s = '\0';
+ }
+ if (mkpath(tmpbuf) != 0)
+ pfatal("creation of %s failed", tmpbuf);
+ free(tmpbuf);
+}
+
+/*
+ * Make filenames more reasonable.
+ */
+char *
+fetchname(const char *at, bool *exists, int strip_leading)
+{
+ char *fullname, *name, *t;
+ int sleading, tab;
+ struct stat filestat;
+
+ if (at == NULL || *at == '\0')
+ return NULL;
+ while (isspace((unsigned char)*at))
+ at++;
+#ifdef DEBUGGING
+ if (debug & 128)
+ say("fetchname %s %d\n", at, strip_leading);
+#endif
+ /* So files can be created by diffing against /dev/null. */
+ if (strnEQ(at, _PATH_DEVNULL, sizeof(_PATH_DEVNULL) - 1))
+ return NULL;
+ name = fullname = t = savestr(at);
+
+ tab = strchr(t, '\t') != NULL;
+ /* Strip off up to `strip_leading' path components and NUL terminate. */
+ for (sleading = strip_leading; *t != '\0' && ((tab && *t != '\t') ||
+ !isspace((unsigned char)*t)); t++) {
+ if (t[0] == '/' && t[1] != '/' && t[1] != '\0')
+ if (--sleading >= 0)
+ name = t + 1;
+ }
+ *t = '\0';
+
+ /*
+ * If no -p option was given (957 is the default value!), we were
+ * given a relative pathname, and the leading directories that we
+ * just stripped off all exist, put them back on.
+ */
+ if (strip_leading == 957 && name != fullname && *fullname != '/') {
+ name[-1] = '\0';
+ if (stat(fullname, &filestat) == 0 && S_ISDIR(filestat.st_mode)) {
+ name[-1] = '/';
+ name = fullname;
+ }
+ }
+ name = savestr(name);
+ free(fullname);
+
+ *exists = stat(name, &filestat) == 0;
+ return name;
+}
+
+/*
+ * Takes the name returned by fetchname and looks in RCS/SCCS directories
+ * for a checked in version.
+ */
+char *
+checked_in(char *file)
+{
+ char *filebase, *filedir, tmpbuf[MAXPATHLEN];
+ struct stat filestat;
+
+ filebase = basename(file);
+ filedir = dirname(file);
+
+#define try(f, a1, a2, a3) \
+(snprintf(tmpbuf, sizeof tmpbuf, f, a1, a2, a3), stat(tmpbuf, &filestat) == 0)
+
+ if (try("%s/RCS/%s%s", filedir, filebase, RCSSUFFIX) ||
+ try("%s/RCS/%s%s", filedir, filebase, "") ||
+ try("%s/%s%s", filedir, filebase, RCSSUFFIX) ||
+ try("%s/SCCS/%s%s", filedir, SCCSPREFIX, filebase) ||
+ try("%s/%s%s", filedir, SCCSPREFIX, filebase))
+ return file;
+
+ return NULL;
+}
+
+void
+version(void)
+{
+ printf("Patch version 2.0-12u8-NetBSD\n");
+ my_exit(EXIT_SUCCESS);
+}
+
+/*
+ * Exit with cleanup.
+ */
+void
+my_exit(int status)
+{
+ unlink(TMPINNAME);
+ if (!toutkeep)
+ unlink(TMPOUTNAME);
+ if (!trejkeep)
+ unlink(TMPREJNAME);
+ unlink(TMPPATNAME);
+ exit(status);
+}
+
+void *
+pch_realloc(void *ptr, size_t number, size_t size)
+{
+ if (number > SIZE_MAX / size) {
+ errno = EOVERFLOW;
+ return NULL;
+ }
+ return realloc(ptr, number * size);
+}
diff --git a/usr.bin/patch/util.h b/usr.bin/patch/util.h
new file mode 100644
index 0000000..d8bde6c
--- /dev/null
+++ b/usr.bin/patch/util.h
@@ -0,0 +1,51 @@
+/*
+ * $OpenBSD: util.h,v 1.15 2005/06/20 07:14:06 otto Exp $
+ * $DragonFly: src/usr.bin/patch/util.h,v 1.2 2007/09/29 23:11:10 swildner Exp $
+ * $NetBSD: util.h,v 1.13 2018/06/18 18:33:31 christos Exp $
+ */
+
+/*
+ * patch - a program to apply diffs to original files
+ *
+ * Copyright 1986, Larry Wall
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following condition is met:
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this condition and the following disclaimer.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE FOR
+ * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * -C option added in 1998, original code by Marc Espie, based on FreeBSD
+ * behaviour
+ */
+
+char *fetchname(const char *, bool *, int);
+char *checked_in(char *);
+int backup_file(const char *);
+int move_file(const char *, const char *);
+int copy_file(const char *, const char *);
+void say(const char *, ...) __printflike(1, 2);
+void fatal(const char *, ...) __printflike(1, 2) __dead;
+void pfatal(const char *, ...) __printflike(1, 2) __dead;
+void ask(const char *, ...) __printflike(1, 2);
+char *savestr(const char *);
+void set_signals(int);
+void ignore_signals(void);
+void makedirs(const char *, bool);
+void version(void) __dead;
+void my_exit(int) __dead;
+void *pch_realloc(void *, size_t, size_t);
+
+/* in mkpath.c */
+extern int mkpath(char *);
diff --git a/usr.bin/pathchk/pathchk.1 b/usr.bin/pathchk/pathchk.1
new file mode 100644
index 0000000..d7f5a43
--- /dev/null
+++ b/usr.bin/pathchk/pathchk.1
@@ -0,0 +1,123 @@
+.\" $NetBSD: pathchk.1,v 1.6 2010/11/16 23:59:02 wiz Exp $
+.\"
+.\" Copyright (c) 2001, 2002 Chuck Rouillard
+.\" All rights reserved.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\" notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must reproduce the above copyright
+.\" notice, this list of conditions and the following disclaimer in the
+.\" documentation and/or other materials provided with the distribution.
+.\" 3. The name of the author may not be used to endorse or promote
+.\" products derived from this software without specific prior written
+.\" permission.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS
+.\" OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+.\" WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+.\" ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
+.\" DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+.\" SUCH DAMAGE.
+.\"
+.\" from FreeBSD: pathchk.1,v 1.3 2002/12/12 17:26:01 ru Exp
+.\"
+.Dd November 9, 2010
+.Dt PATHCHK 1
+.Os
+.Sh NAME
+.Nm pathchk
+.Nd check pathnames
+.Sh SYNOPSIS
+.Nm
+.Op Fl p
+.Ar pathname ...
+.Sh DESCRIPTION
+The
+.Nm
+utility checks whether each of the specified
+.Ar pathname
+arguments is valid or portable.
+.Pp
+A diagnostic message is written for each argument that:
+.Bl -bullet
+.It
+Is longer than
+.Dv PATH_MAX
+bytes.
+.It
+Contains any component longer than
+.Dv NAME_MAX
+bytes.
+(The value of
+.Dv NAME_MAX
+depends on the underlying file system.)
+.It
+Contains a directory component that is not searchable.
+.El
+.Pp
+It is not considered an error if a
+.Ar pathname
+argument contains a nonexistent component as long as a component by that
+name could be created.
+.Pp
+The options are as follows:
+.Bl -tag -width indent
+.It Fl p
+Perform portability checks on the specified
+.Ar pathname
+arguments.
+Diagnostic messages will be written for each argument that:
+.Bl -bullet
+.It
+Is longer than
+.Dv _POSIX_PATH_MAX
+.Pq 255
+bytes.
+.It
+Contains a component longer than
+.Dv _POSIX_NAME_MAX
+.Pq 14
+bytes.
+.It
+Contains any character not in the portable filename character set (that is,
+alphanumeric characters,
+.Ql \&. ,
+.Ql \&-
+and
+.Ql _ ) .
+No component may start with the hyphen
+.Pq Ql \&-
+character.
+.El
+.El
+.Sh EXAMPLES
+Check whether the names of files in the current directory are portable to
+other
+.Tn POSIX
+systems:
+.Pp
+.Dl "find . -exec pathchk -p \e{\e} +"
+or the more efficient:
+.Dl "find . -print0 | xargs -0 pathchk -p"
+.Sh SEE ALSO
+.Xr getconf 1 ,
+.Xr pathconf 2 ,
+.Xr stat 2
+.Sh STANDARDS
+The
+.Nm
+utility conforms to
+.St -p1003.1-2001 .
+.Sh HISTORY
+A
+.Nm
+utility appeared in
+.Nx 2.0 .
diff --git a/usr.bin/pathchk/pathchk.c b/usr.bin/pathchk/pathchk.c
new file mode 100644
index 0000000..0f7f5c1
--- /dev/null
+++ b/usr.bin/pathchk/pathchk.c
@@ -0,0 +1,191 @@
+/* $NetBSD: pathchk.c,v 1.2 2011/09/16 15:39:28 joerg Exp $ */
+
+/*-
+ * Copyright (c) 2002 Tim J. Robbins.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * from FreeBSD: pathchk.c,v 1.4 2002/12/15 00:40:47 tjr Exp
+ */
+
+#include <sys/cdefs.h>
+#ifndef lint
+__RCSID("$NetBSD: pathchk.c,v 1.2 2011/09/16 15:39:28 joerg Exp $");
+#endif /* !lint */
+
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#include <err.h>
+#include <errno.h>
+#include <limits.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+static int check(const char *);
+static int portable(const char *);
+__dead static void usage(void);
+
+static int pflag; /* Perform portability checks */
+
+int
+main(int argc, char *argv[])
+{
+ int ch, rval;
+ const char *arg;
+
+ while ((ch = getopt(argc, argv, "p")) > 0) {
+ switch (ch) {
+ case 'p':
+ pflag = 1;
+ break;
+ default:
+ usage();
+ /*NOTREACHED*/
+ }
+ }
+ argc -= optind;
+ argv += optind;
+
+ if (argc == 0)
+ usage();
+
+ rval = 0;
+ while ((arg = *argv++) != NULL)
+ rval |= check(arg);
+
+ exit(rval);
+}
+
+static void
+usage(void)
+{
+
+ fprintf(stderr, "usage: pathchk [-p] pathname...\n");
+ exit(1);
+}
+
+static int
+check(const char *path)
+{
+ struct stat sb;
+ long complen, namemax, pathmax, svnamemax;
+ int badch, last;
+ char *end, *p, *pathd;
+
+ if ((pathd = strdup(path)) == NULL)
+ err(1, "strdup");
+
+ p = pathd;
+
+ if (!pflag) {
+ errno = 0;
+ namemax = pathconf(*p == '/' ? "/" : ".", _PC_NAME_MAX);
+ if (namemax == -1 && errno != 0)
+ namemax = NAME_MAX;
+ } else
+ namemax = _POSIX_NAME_MAX;
+
+ for (;;) {
+ p += strspn(p, "/");
+ complen = (long)strcspn(p, "/");
+ end = p + complen;
+ last = *end == '\0';
+ *end = '\0';
+
+ if (namemax != -1 && complen > namemax) {
+ warnx("%s: %s: component too long (limit %ld)", path,
+ p, namemax);
+ goto bad;
+ }
+
+ if (!pflag && stat(pathd, &sb) == -1 && errno != ENOENT) {
+ warn("%s: %.*s", path, (int)(strlen(pathd) -
+ complen - 1), pathd);
+ goto bad;
+ }
+
+ if (pflag && (badch = portable(p)) >= 0) {
+ warnx("%s: %s: component contains non-portable "
+ "character `%c'", path, p, badch);
+ goto bad;
+ }
+
+ if (last)
+ break;
+
+ if (!pflag) {
+ errno = 0;
+ svnamemax = namemax;
+ namemax = pathconf(pathd, _PC_NAME_MAX);
+ if (namemax == -1 && errno != 0)
+ namemax = svnamemax;
+ }
+
+ *end = '/';
+ p = end + 1;
+ }
+
+ if (!pflag) {
+ errno = 0;
+ pathmax = pathconf(path, _PC_PATH_MAX);
+ if (pathmax == -1 && errno != 0)
+ pathmax = PATH_MAX;
+ } else
+ pathmax = _POSIX_PATH_MAX;
+ if (pathmax != -1 && strlen(path) >= (size_t)pathmax) {
+ warnx("%s: path too long (limit %ld)", path, pathmax - 1);
+ goto bad;
+ }
+
+ free(pathd);
+ return (0);
+
+bad: free(pathd);
+ return (1);
+}
+
+/*
+ * Check whether a path component contains only portable characters. Return
+ * the first non-portable character found.
+ */
+static int
+portable(const char *path)
+{
+ static const char charset[] =
+ "abcdefghijklmnopqrstuvwxyz"
+ "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
+ "0123456789._-";
+ long s;
+
+ if (*path == '-')
+ return (*path);
+
+ s = strspn(path, charset);
+ if (path[s] != '\0')
+ return (path[s]);
+
+ return (-1);
+}
diff --git a/usr.bin/pr/egetopt.c b/usr.bin/pr/egetopt.c
new file mode 100644
index 0000000..5c43476
--- /dev/null
+++ b/usr.bin/pr/egetopt.c
@@ -0,0 +1,215 @@
+/* $NetBSD: egetopt.c,v 1.9 2011/09/06 18:26:06 joerg Exp $ */
+
+/*-
+ * Copyright (c) 1991 Keith Muller.
+ * Copyright (c) 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Keith Muller of the University of California, San Diego.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+#ifndef lint
+#if 0
+from: static char sccsid[] = "@(#)egetopt.c 8.1 (Berkeley) 6/6/93";
+#else
+__RCSID("$NetBSD: egetopt.c,v 1.9 2011/09/06 18:26:06 joerg Exp $");
+#endif
+#endif /* not lint */
+
+#include <ctype.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "extern.h"
+
+/*
+ * egetopt: get option letter from argument vector (an extended
+ * version of getopt).
+ *
+ * Non standard additions to the ostr specs are:
+ * 1) '?': immediate value following arg is optional (no white space
+ * between the arg and the value)
+ * 2) '#': +/- followed by a number (with an optional sign but
+ * no white space between the arg and the number). The - may be
+ * combined with other options, but the + cannot.
+ */
+
+int eopterr = 1; /* if error message should be printed */
+int eoptind = 1; /* index into parent argv vector */
+int eoptopt; /* character checked for validity */
+char *eoptarg; /* argument associated with option */
+
+#define BADCH (int)'?'
+static char EMSG[1] = { '\0' };
+
+int
+egetopt(int nargc, char * const *nargv, const char *ostr)
+{
+ static char *place = EMSG; /* option letter processing */
+ char *oli; /* option letter list index */
+ static int delim; /* which option delimiter */
+ char *p;
+ static char savec = '\0';
+
+ if (savec != '\0') {
+ *place = savec;
+ savec = '\0';
+ }
+
+ if (!*place) {
+ /*
+ * update scanning pointer
+ */
+ if ((eoptind >= nargc) ||
+ ((*(place = nargv[eoptind]) != '-') && (*place != '+'))) {
+ place = EMSG;
+ return (-1);
+ }
+
+ delim = (int)*place;
+ if (place[1] && *++place == '-' && !place[1]) {
+ /*
+ * found "--"
+ */
+ ++eoptind;
+ place = EMSG;
+ return (-1);
+ }
+ }
+
+ /*
+ * check option letter
+ */
+ if ((eoptopt = (int)*place++) == (int)':' || (eoptopt == (int)'?') ||
+ !(oli = strchr(ostr, eoptopt))) {
+ /*
+ * if the user didn't specify '-' as an option,
+ * assume it means -1 when by itself.
+ */
+ if ((eoptopt == (int)'-') && !*place)
+ return (-1);
+ if (strchr(ostr, '#') && (isdigit(eoptopt) ||
+ (((eoptopt == (int)'-') || (eoptopt == (int)'+')) &&
+ isdigit((unsigned char)*place)))) {
+ /*
+ * # option: +/- with a number is ok
+ */
+ for (p = place; *p != '\0'; ++p) {
+ if (!isdigit((unsigned char)*p))
+ break;
+ }
+ eoptarg = place-1;
+
+ if (*p == '\0') {
+ place = EMSG;
+ ++eoptind;
+ } else {
+ place = p;
+ savec = *p;
+ *place = '\0';
+ }
+ return (delim);
+ }
+
+ if (!*place)
+ ++eoptind;
+ if (eopterr) {
+ if (!(p = strrchr(*nargv, '/')))
+ p = *nargv;
+ else
+ ++p;
+ (void)fprintf(stderr, "%s: illegal option -- %c\n",
+ p, eoptopt);
+ }
+ return (BADCH);
+ }
+ if (delim == (int)'+') {
+ /*
+ * '+' is only allowed with numbers
+ */
+ if (!*place)
+ ++eoptind;
+ if (eopterr) {
+ if (!(p = strrchr(*nargv, '/')))
+ p = *nargv;
+ else
+ ++p;
+ (void)fprintf(stderr,
+ "%s: illegal '+' delimiter with option -- %c\n",
+ p, eoptopt);
+ }
+ return (BADCH);
+ }
+ ++oli;
+ if ((*oli != ':') && (*oli != '?')) {
+ /*
+ * don't need argument
+ */
+ eoptarg = NULL;
+ if (!*place)
+ ++eoptind;
+ return (eoptopt);
+ }
+
+ if (*place) {
+ /*
+ * no white space
+ */
+ eoptarg = place;
+ } else if (*oli == '?') {
+ /*
+ * no arg, but NOT required
+ */
+ eoptarg = NULL;
+ } else if (nargc <= ++eoptind) {
+ /*
+ * no arg, but IS required
+ */
+ place = EMSG;
+ if (eopterr) {
+ if (!(p = strrchr(*nargv, '/')))
+ p = *nargv;
+ else
+ ++p;
+ (void)fprintf(stderr,
+ "%s: option requires an argument -- %c\n", p,
+ eoptopt);
+ }
+ return (BADCH);
+ } else {
+ /*
+ * arg has white space
+ */
+ eoptarg = nargv[eoptind];
+ }
+ place = EMSG;
+ ++eoptind;
+ return (eoptopt);
+}
diff --git a/usr.bin/pr/extern.h b/usr.bin/pr/extern.h
new file mode 100644
index 0000000..6aeb61c
--- /dev/null
+++ b/usr.bin/pr/extern.h
@@ -0,0 +1,42 @@
+/* $NetBSD: extern.h,v 1.6 2011/09/06 18:26:06 joerg Exp $ */
+
+/*-
+ * Copyright (c) 1991 Keith Muller.
+ * Copyright (c) 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Keith Muller of the University of California, San Diego.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * from: @(#)extern.h 8.1 (Berkeley) 6/6/93
+ * $NetBSD: extern.h,v 1.6 2011/09/06 18:26:06 joerg Exp $
+ */
+
+extern int eoptind;
+extern char *eoptarg;
+
+int egetopt(int, char * const *, const char *);
diff --git a/usr.bin/pr/pr.1 b/usr.bin/pr/pr.1
new file mode 100644
index 0000000..f1cc1aa
--- /dev/null
+++ b/usr.bin/pr/pr.1
@@ -0,0 +1,361 @@
+.\" $NetBSD: pr.1,v 1.23 2017/07/03 21:34:21 wiz Exp $
+.\"
+.\" Copyright (c) 1991 Keith Muller.
+.\" Copyright (c) 1993
+.\" The Regents of the University of California. All rights reserved.
+.\" Copyright (c) 1994-1995, 1997, 1999-2003, 2009, 2012
+.\" The NetBSD Foundation, Inc.
+.\"
+.\" This code is derived from software contributed to Berkeley by
+.\" Keith Muller of the University of California, San Diego.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\" notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must reproduce the above copyright
+.\" notice, this list of conditions and the following disclaimer in the
+.\" documentation and/or other materials provided with the distribution.
+.\" 3. Neither the name of the University nor the names of its contributors
+.\" may be used to endorse or promote products derived from this software
+.\" without specific prior written permission.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+.\" ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+.\" SUCH DAMAGE.
+.\"
+.\" from: @(#)pr.1 8.1 (Berkeley) 6/6/93
+.\"
+.Dd July 31, 2012
+.Dt PR 1
+.Os
+.Sh NAME
+.Nm pr
+.Nd print files
+.Sh SYNOPSIS
+.Nm
+.Op Ar \&+page
+.Op Fl Ar column
+.Op Fl adFfmprt
+.Oo
+.Fl e Ns Oo Ar char Oc Ns Op Ar gap
+.Oc
+.Op Fl h Ar header
+.Oo
+.Fl i Ns Oo Ar char Oc Ns Op Ar gap
+.Oc
+.Op Fl l Ar lines
+.Oo
+.Fl n Ns Oo Ar char Oc Ns Op Ar width
+.Oc
+.Op Fl o Ar offset
+.Oo
+.Fl s Ns Op Ar char
+.Oc
+.Op Fl T Ar timefmt
+.Op Fl w Ar width
+.Op -
+.Op Ar file ...
+.Sh DESCRIPTION
+The
+.Nm
+utility is a printing and pagination filter for text files.
+When multiple input files are specified, each is read, formatted,
+and written to standard output.
+By default, the input is separated into 66-line pages, each with
+.Bl -bullet
+.It
+A 5-line header with the page number, date, time, and
+the pathname of the file.
+.It
+A 5-line trailer consisting of blank lines.
+.El
+.Pp
+If standard output is associated with a terminal,
+diagnostic messages are suppressed until the
+.Nm
+utility has completed processing.
+.Pp
+When multiple column output is specified,
+text columns are of equal width.
+By default text columns are separated by at least one
+.Aq Em blank .
+Input lines that do not fit into a text column are truncated.
+Lines are not truncated under single column output.
+.Sh OPTIONS
+In the following option descriptions, column, lines, offset, page, and
+width are positive decimal integers and gap is a nonnegative decimal integer.
+.Bl -tag -width 4n
+.It Ar \&+page
+Begin output at page number
+.Ar page
+of the formatted input.
+.It Fl Ar column
+Produce output that is
+.Ar columns
+wide (default is 1) that is written vertically
+down each column in the order in which the text
+is received from the input file.
+The options
+.Fl e
+and
+.Fl i
+are assumed.
+This option should not be used with
+.Fl m .
+When used with
+.Fl t ,
+the minimum number of lines is used to display the output.
+.It Fl a
+Modify the effect of the
+.Fl column
+option so that the columns are filled across the page in a round-robin order
+(e.g., when column is 2, the first input line heads column
+1, the second heads column 2, the third is the second line
+in column 1, etc.).
+This option requires the use of the
+.Fl column
+option.
+.It Fl d
+Produce output that is double spaced.
+An extra
+.Aq Em newline
+character is output following every
+.Aq newline
+found in the input.
+.It Fl e Ns Oo Ar char Oc Ns Op Ar gap
+Expand each input
+.Aq tab
+to the next greater column
+position specified by the formula
+.Ar n*gap+1 ,
+where
+.Em n
+is an integer > 0.
+If
+.Ar gap
+is zero or is omitted the default is 8.
+All
+.Aq Em tab
+characters in the input are expanded into the appropriate
+number of
+.Ao Em space Ac Ns s .
+If any nondigit character,
+.Ar char ,
+is specified, it is used as the input tab character.
+If the first character of
+.Ar char
+is a digit then
+.Ar char
+is treated as
+.Ar gap .
+.It Fl F
+Use a
+.Aq Em form-feed
+character for new pages,
+instead of the default behavior that uses a
+sequence of
+.Aq Em newline
+characters.
+.It Fl f
+Same as
+.Fl F .
+Additionally pause before beginning the first page
+if the standard output is associated with a terminal.
+.It Fl h Ar header
+Use the string
+.Ar header
+to replace the
+.Ar file name
+in the header line.
+.It Fl i Ns Oo Ar char Oc Ns Op Ar gap
+In output, replace multiple
+.Ao space Ac Ns s
+with
+.Ao tab Ac Ns s
+whenever two or more
+adjacent
+.Ao space Ac Ns s
+reach column positions
+.Ar gap+1 ,
+.Ar 2*gap+1 ,
+etc.
+If
+.Ar gap
+is zero or omitted, default
+.Aq Em tab
+settings at every eighth column position
+is used.
+If any nondigit character,
+.Ar char ,
+is specified, it is used as the output
+.Aq Em tab
+character.
+If the first character of
+.Ar char
+is a digit then
+.Ar char
+is treated as
+.Ar gap .
+.It Fl l Ar lines
+Override the 66 line default and reset the page length to
+.Ar lines .
+If
+.Ar lines
+is not greater than the sum of both the header and trailer
+depths (in lines), the
+.Nm
+utility suppresses output of both the header and trailer, as if the
+.Fl t
+option were in effect.
+.It Fl m
+Merge the contents of multiple files.
+One line from each file specified by a file operand is
+written side by side into text columns of equal fixed widths, in
+terms of the number of column positions.
+The number of text columns depends on the number of
+file operands successfully opened.
+The maximum number of files merged depends on page width and the
+per process open file limit.
+The options
+.Fl e
+and
+.Fl i
+are assumed.
+.It Fl n Ns Oo Ar char Oc Ns Op Ar width
+Provide
+.Ar width
+digit line numbering.
+The default for
+.Ar width ,
+if not specified, is 5.
+The number occupies the first
+.Ar width
+column positions of each text column or each line of
+.Fl m
+output.
+If
+.Ar char
+(any nondigit character) is given, it is appended to the line number to
+separate it from whatever follows.
+The default for
+.Ar char
+is a
+.Aq Em tab .
+Line numbers longer than
+.Ar width
+columns are truncated.
+.It Fl o Ar offset
+Each line of output is preceded by
+.Ar offset
+.Ao Em space Ac Ns s .
+If the
+.Fl o
+option is not specified, the default is zero.
+The space taken is in addition to the output line width.
+.It Fl p
+Pause before beginning each page if the
+standard output is associated with a terminal.
+.Nm
+will write an
+.Aq Em alert
+to standard error and wait for a
+.Aq Em carriage-return
+to be read on
+.Pa /dev/tty .
+.It Fl r
+Write no diagnostic reports on failure to open a file.
+.It Fl s Ns Op Ar char
+Separate text columns by the single character
+.Ar char
+instead of by the appropriate number of
+.Ao Em space Ac Ns s
+(default for
+.Ar char
+is the
+.Aq Em tab
+character).
+.It Fl T
+Specify an
+.Xr strftime 3
+format string to be used to format the date and time information in the page
+header.
+.It Fl t
+Print neither the five-line identifying
+header nor the five-line trailer usually supplied for each page.
+Quit printing after the last line of each file without spacing to the
+end of the page.
+.It Fl w Ar width
+Set the width of the line to
+.Ar width
+column positions for multiple text-column output only.
+If the
+.Fl w
+option is not specified and the
+.Fl s
+option is not specified, the default width is 72.
+If the
+.Fl w
+option is not specified and the
+.Fl s
+option is specified, the default width is 512.
+.It Ar file
+A pathname of a file to be printed.
+If no
+.Ar file
+operands are specified, or if a
+.Ar file
+operand is
+.Sq Fl ,
+the standard input is used.
+The standard input is used only if no
+.Ar file
+operands are specified, or if a
+.Ar file
+operand is
+.Sq Fl .
+.El
+.Pp
+The
+.Fl s
+option does not allow the option letter to be separated from its
+argument, and the options
+.Fl e ,
+.Fl i ,
+and
+.Fl n
+require that both arguments, if present, not be separated from the option
+letter.
+.Sh ERRORS
+If
+.Nm
+receives an interrupt while printing to a terminal, it
+flushes all accumulated error messages to the screen before
+terminating.
+.Pp
+The
+.Nm
+utility exits 0 on success, and 1 if an error occurs.
+.Pp
+Error messages are written to standard error during the printing
+process (if output is redirected) or after all successful
+file printing is complete (when printing to a terminal).
+.Sh SEE ALSO
+.Xr cat 1 ,
+.Xr more 1 ,
+.Xr strftime 3
+.Sh STANDARDS
+The
+.Nm
+utility is
+.St -p1003.1-2008
+compatible.
diff --git a/usr.bin/pr/pr.c b/usr.bin/pr/pr.c
new file mode 100644
index 0000000..91e8075
--- /dev/null
+++ b/usr.bin/pr/pr.c
@@ -0,0 +1,1901 @@
+/* $NetBSD: pr.c,v 1.24 2012/08/01 02:27:48 ginsbach Exp $ */
+
+/*-
+ * Copyright (c) 1991 Keith Muller.
+ * Copyright (c) 1993
+ * The Regents of the University of California. All rights reserved.
+ * Copyright (c) 2012
+ * The NetBSD Foundation, Inc.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Keith Muller of the University of California, San Diego.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+#ifndef lint
+__COPYRIGHT("@(#) Copyright (c) 1993\
+ The Regents of the University of California. All rights reserved.");
+#endif /* not lint */
+
+#ifndef lint
+#if 0
+from: static char sccsid[] = "@(#)pr.c 8.1 (Berkeley) 6/6/93";
+#else
+__RCSID("$NetBSD: pr.c,v 1.24 2012/08/01 02:27:48 ginsbach Exp $");
+#endif
+#endif /* not lint */
+
+#include <sys/types.h>
+#include <sys/time.h>
+#include <sys/stat.h>
+
+#include <ctype.h>
+#include <errno.h>
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <unistd.h>
+#include <util.h>
+
+#include "pr.h"
+#include "extern.h"
+
+/*
+ * pr: a printing and pagination filter. If multiple input files
+ * are specified, each is read, formatted, and written to standard
+ * output. By default, input is separated into 66-line pages, each
+ * with a header that includes the page number, date, time and the
+ * files pathname.
+ *
+ * Complies with posix P1003.2/D11
+ */
+
+/*
+ * parameter variables
+ */
+static int pgnm; /* starting page number */
+static int clcnt; /* number of columns */
+static int colwd; /* column data width - multiple columns */
+static int across; /* mult col flag; write across page */
+static int dspace; /* double space flag */
+static char inchar; /* expand input char */
+static int ingap; /* expand input gap */
+static int formfeed; /* use formfeed as trailer */
+static char *header; /* header name instead of file name */
+static char ochar; /* contract output char */
+static int ogap; /* contract output gap */
+static int lines; /* number of lines per page */
+static int merge; /* merge multiple files in output */
+static char nmchar; /* line numbering append char */
+static int nmwd; /* width of line number field */
+static int offst; /* number of page offset spaces */
+static int nodiag; /* do not report file open errors */
+static char schar; /* text column separation character */
+static int sflag; /* -s option for multiple columns */
+static int ttyout; /* output is a tty */
+static int nohead; /* do not write head and trailer */
+static int pgpause; /* pause before each page */
+static int pgwd; /* page width with multiple col output */
+static const char *timefrmt = TIMEFMT; /* time conversion string */
+static FILE *ttyinf; /* input terminal for page pauses */
+
+/*
+ * misc globals
+ */
+static FILE *errf; /* error message file pointer */
+static int addone; /* page length is odd with double space */
+static int errcnt; /* error count on file processing */
+static const char digs[] = "0123456789"; /* page number translation map */
+
+static void addnum(char *, int, int);
+static void flsh_errs(void);
+static int horzcol(int, char **);
+static int inln(FILE *, char *, int, int *, int, int *);
+static int inskip(FILE *, int, int);
+static void mfail(void);
+static int mulfile(int, char **);
+static FILE *nxtfile(int, char **, const char **, char *, int);
+static int onecol(int, char **);
+static int otln(char *, int, int *, int *, int);
+static void pfail(void);
+static int prhead(char *, const char *, int);
+static void prpause(int);
+static int prtail(int, int);
+static int setup(int, char **);
+__dead static void terminate(int);
+static void usage(void);
+static int vertcol(int, char **);
+
+int
+main(int argc, char *argv[])
+{
+ int ret_val;
+
+ if (signal(SIGINT, SIG_IGN) != SIG_IGN)
+ (void)signal(SIGINT, terminate);
+ ret_val = setup(argc, argv);
+ if (!ret_val) {
+ /*
+ * select the output format based on options
+ */
+ if (merge)
+ ret_val = mulfile(argc, argv);
+ else if (clcnt == 1)
+ ret_val = onecol(argc, argv);
+ else if (across)
+ ret_val = horzcol(argc, argv);
+ else
+ ret_val = vertcol(argc, argv);
+ } else
+ usage();
+ flsh_errs();
+ if (errcnt || ret_val)
+ exit(1);
+ return(0);
+}
+
+/*
+ * onecol: print files with only one column of output.
+ * Line length is unlimited.
+ */
+static int
+onecol(int argc, char *argv[])
+{
+ int cnt = -1;
+ int off;
+ int lrgln;
+ int linecnt;
+ int num;
+ int lncnt;
+ int pagecnt;
+ int ips;
+ int ops;
+ int cps;
+ char *obuf = NULL;
+ char *lbuf;
+ char *nbuf;
+ char *hbuf = NULL;
+ char *ohbuf;
+ FILE *inf = NULL;
+ const char *fname;
+ int mor;
+ int error = 1;
+
+ if (nmwd)
+ num = nmwd + 1;
+ else
+ num = 0;
+ off = num + offst;
+
+ /*
+ * allocate line buffer
+ */
+ if ((obuf = malloc((unsigned)(LBUF + off)*sizeof(char))) == NULL)
+ goto oomem;
+ /*
+ * allocate header buffer
+ */
+ if ((hbuf = malloc((unsigned)(HDBUF + offst)*sizeof(char))) == NULL)
+ goto oomem;
+
+ ohbuf = hbuf + offst;
+ nbuf = obuf + offst;
+ lbuf = nbuf + num;
+ if (num)
+ nbuf[--num] = nmchar;
+ if (offst) {
+ (void)memset(obuf, (int)' ', offst);
+ (void)memset(hbuf, (int)' ', offst);
+ }
+
+ /*
+ * loop by file
+ */
+ while ((inf = nxtfile(argc, argv, &fname, ohbuf, 0)) != NULL) {
+ if (pgnm) {
+ /*
+ * skip to specified page
+ */
+ if (inskip(inf, pgnm, lines))
+ continue;
+ pagecnt = pgnm;
+ } else
+ pagecnt = 1;
+ lncnt = 0;
+
+ /*
+ * loop by page
+ */
+ for(;;) {
+ linecnt = 0;
+ lrgln = 0;
+ ops = 0;
+ ips = 0;
+ cps = 0;
+
+ /*
+ * loop by line
+ */
+ while (linecnt < lines) {
+ /*
+ * input next line
+ */
+ if ((cnt = inln(inf,lbuf,LBUF,&cps,0,&mor)) < 0)
+ break;
+ if (!linecnt) {
+ if (pgpause)
+ prpause(pagecnt);
+
+ if (!nohead &&
+ prhead(hbuf, fname, pagecnt))
+ goto out;
+ }
+
+ /*
+ * start of new line.
+ */
+ if (!lrgln) {
+ if (num)
+ addnum(nbuf, num, ++lncnt);
+ if (otln(obuf,cnt+off, &ips, &ops, mor))
+ goto out;
+ } else if (otln(lbuf, cnt, &ips, &ops, mor))
+ goto out;
+
+ /*
+ * if line bigger than buffer, get more
+ */
+ if (mor) {
+ lrgln = 1;
+ continue;
+ }
+
+ /*
+ * whole line rcvd. reset tab proc. state
+ */
+ ++linecnt;
+ lrgln = 0;
+ ops = 0;
+ ips = 0;
+ }
+
+ /*
+ * fill to end of page
+ */
+ if (linecnt && prtail(lines-linecnt-lrgln, lrgln))
+ goto out;
+
+ /*
+ * On EOF go to next file
+ */
+ if (cnt < 0)
+ break;
+ ++pagecnt;
+ }
+ if (inf != stdin)
+ (void)fclose(inf);
+ }
+ if (eoptind < argc)
+ goto out;
+ error = 0;
+ goto out;
+oomem:
+ mfail();
+out:
+ free(obuf);
+ free(hbuf);
+ if (inf != NULL && inf != stdin)
+ (void)fclose(inf);
+ return error;
+}
+
+/*
+ * vertcol: print files with more than one column of output down a page
+ */
+static int
+vertcol(int argc, char *argv[])
+{
+ char *ptbf;
+ char **lstdat = NULL;
+ int i;
+ int j;
+ int cnt = -1;
+ int pln;
+ int *indy = NULL;
+ int cvc;
+ int *lindy = NULL;
+ int lncnt;
+ int stp;
+ int pagecnt;
+ int col = colwd + 1;
+ int mxlen = pgwd + offst + 1;
+ int mclcnt = clcnt - 1;
+ struct vcol *vc = NULL;
+ int mvc;
+ int tvc;
+ int cw = nmwd + 1;
+ int fullcol;
+ char *buf = NULL;
+ char *hbuf = NULL;
+ char *ohbuf;
+ const char *fname;
+ FILE *inf = NULL;
+ int ips = 0;
+ int cps = 0;
+ int ops = 0;
+ int mor = 0;
+ int error = 1;
+
+ /*
+ * allocate page buffer
+ */
+ if ((buf = malloc((unsigned)lines*mxlen*sizeof(char))) == NULL)
+ goto oomem;
+
+ /*
+ * allocate page header
+ */
+ if ((hbuf = malloc((unsigned)(HDBUF + offst)*sizeof(char))) == NULL)
+ goto oomem;
+ ohbuf = hbuf + offst;
+ if (offst)
+ (void)memset(hbuf, (int)' ', offst);
+
+ /*
+ * col pointers when no headers
+ */
+ mvc = lines * clcnt;
+ if ((vc = malloc((unsigned)mvc*sizeof(struct vcol))) == NULL)
+ goto oomem;
+
+ /*
+ * pointer into page where last data per line is located
+ */
+ if ((lstdat = malloc((unsigned)lines*sizeof(char *))) == NULL)
+ goto oomem;
+
+ /*
+ * fast index lookups to locate start of lines
+ */
+ if ((indy = malloc((unsigned)lines*sizeof(int))) == NULL)
+ goto oomem;
+ if ((lindy = malloc((unsigned)lines*sizeof(int))) == NULL)
+ goto oomem;
+
+ if (nmwd)
+ fullcol = col + cw;
+ else
+ fullcol = col;
+
+ /*
+ * initialize buffer lookup indexes and offset area
+ */
+ for (j = 0; j < lines; ++j) {
+ lindy[j] = j * mxlen;
+ indy[j] = lindy[j] + offst;
+ if (offst) {
+ ptbf = buf + lindy[j];
+ (void)memset(ptbf, (int)' ', offst);
+ ptbf += offst;
+ } else
+ ptbf = buf + indy[j];
+ lstdat[j] = ptbf;
+ }
+
+ /*
+ * loop by file
+ */
+ while ((inf = nxtfile(argc, argv, &fname, ohbuf, 0)) != NULL) {
+ if (pgnm) {
+ /*
+ * skip to requested page
+ */
+ if (inskip(inf, pgnm, lines))
+ continue;
+ pagecnt = pgnm;
+ } else
+ pagecnt = 1;
+ lncnt = 0;
+
+ /*
+ * loop by page
+ */
+ for(;;) {
+ /*
+ * loop by column
+ */
+ cvc = 0;
+ for (i = 0; i < clcnt; ++i) {
+ j = 0;
+ /*
+ * if last column, do not pad
+ */
+ if (i == mclcnt)
+ stp = 1;
+ else
+ stp = 0;
+ /*
+ * loop by line
+ */
+ for(;;) {
+ /*
+ * is this first column
+ */
+ if (!i) {
+ ptbf = buf + indy[j];
+ lstdat[j] = ptbf;
+ } else
+ ptbf = lstdat[j];
+ vc[cvc].pt = ptbf;
+
+ /*
+ * add number
+ */
+ if (nmwd) {
+ addnum(ptbf, nmwd, ++lncnt);
+ ptbf += nmwd;
+ *ptbf++ = nmchar;
+ }
+
+ /*
+ * input next line
+ */
+ cnt = inln(inf,ptbf,colwd,&cps,1,&mor);
+ vc[cvc++].cnt = cnt;
+ if (cnt < 0)
+ break;
+ ptbf += cnt;
+
+ /*
+ * pad all but last column on page
+ */
+ if (!stp) {
+ /*
+ * pad to end of column
+ */
+ if (sflag)
+ *ptbf++ = schar;
+ else if ((pln = col-cnt) > 0) {
+ (void)memset(ptbf,
+ (int)' ',pln);
+ ptbf += pln;
+ }
+ }
+ /*
+ * remember last char in line
+ */
+ lstdat[j] = ptbf;
+ if (++j >= lines)
+ break;
+ }
+ if (cnt < 0)
+ break;
+ }
+
+ /*
+ * when -t (no header) is specified the spec requires
+ * the min number of lines. The last page may not have
+ * balanced length columns. To fix this we must reorder
+ * the columns. This is a very slow technique so it is
+ * only used under limited conditions. Without -t, the
+ * balancing of text columns is unspecified. To NOT
+ * balance the last page, add the global variable
+ * nohead to the if statement below e.g.
+ *
+ * if ((cnt < 0) && nohead && cvc ......
+ */
+ --cvc;
+
+ /*
+ * check to see if last page needs to be reordered
+ */
+ if ((cnt < 0) && cvc && ((mvc-cvc) >= clcnt)){
+ pln = cvc/clcnt;
+ if (cvc % clcnt)
+ ++pln;
+
+ if (pgpause)
+ prpause(pagecnt);
+
+ /*
+ * print header
+ */
+ if (!nohead && prhead(hbuf, fname, pagecnt))
+ goto out;
+ for (i = 0; i < pln; ++i) {
+ ips = 0;
+ ops = 0;
+ if (offst&& otln(buf,offst,&ips,&ops,1)) {
+ error = 1;
+ goto out;
+ }
+ tvc = i;
+
+ for (j = 0; j < clcnt; ++j) {
+ /*
+ * determine column length
+ */
+ if (j == mclcnt) {
+ /*
+ * last column
+ */
+ cnt = vc[tvc].cnt;
+ if (nmwd)
+ cnt += cw;
+ } else if (sflag) {
+ /*
+ * single ch between
+ */
+ cnt = vc[tvc].cnt + 1;
+ if (nmwd)
+ cnt += cw;
+ } else
+ cnt = fullcol;
+ if (otln(vc[tvc].pt, cnt, &ips,
+ &ops, 1))
+ goto out;
+ tvc += pln;
+ if (tvc >= cvc)
+ break;
+ }
+ /*
+ * terminate line
+ */
+ if (otln(buf, 0, &ips, &ops, 0))
+ goto out;
+ }
+ /*
+ * pad to end of page
+ */
+ if (prtail((lines - pln), 0))
+ goto out;
+ /*
+ * done with output, go to next file
+ */
+ break;
+ }
+
+ /*
+ * determine how many lines to output
+ */
+ if (i > 0)
+ pln = lines;
+ else
+ pln = j;
+
+ /*
+ * print header
+ */
+ if (pln) {
+ if (pgpause)
+ prpause(pagecnt);
+
+ if (!nohead && prhead(hbuf, fname, pagecnt))
+ goto out;
+ }
+
+ /*
+ * output each line
+ */
+ for (i = 0; i < pln; ++i) {
+ ptbf = buf + lindy[i];
+ if ((j = lstdat[i] - ptbf) <= offst)
+ break;
+ if (otln(ptbf, j, &ips, &ops, 0))
+ goto out;
+ }
+
+ /*
+ * pad to end of page
+ */
+ if (pln && prtail((lines - pln), 0))
+ goto out;
+
+ /*
+ * if EOF go to next file
+ */
+ if (cnt < 0)
+ break;
+ ++pagecnt;
+ }
+ if (inf != stdin)
+ (void)fclose(inf);
+ }
+ if (eoptind < argc)
+ goto out;
+ error = 0;
+ goto out;
+oomem:
+ mfail();
+out:
+ free(buf);
+ free(hbuf);
+ free(vc);
+ free(lstdat);
+ free(lindy);
+ if (inf != NULL && inf != stdin)
+ (void)fclose(inf);
+ return error;
+}
+
+/*
+ * horzcol: print files with more than one column of output across a page
+ */
+static int
+horzcol(int argc, char *argv[])
+{
+ char *ptbf;
+ int pln;
+ int cnt = -1;
+ char *lstdat;
+ int col = colwd + 1;
+ int j;
+ int i;
+ int lncnt;
+ int pagecnt;
+ char *buf = NULL;
+ char *hbuf = NULL;
+ char *ohbuf;
+ const char *fname;
+ FILE *inf = NULL;
+ int ips = 0;
+ int cps = 0;
+ int ops = 0;
+ int mor = 0;
+ int error = 1;
+
+ if ((buf = malloc((unsigned)(pgwd+offst+1)*sizeof(char))) == NULL)
+ goto oomem;
+
+ /*
+ * page header
+ */
+ if ((hbuf = malloc((unsigned)(HDBUF + offst)*sizeof(char))) == NULL)
+ goto oomem;
+ ohbuf = hbuf + offst;
+ if (offst) {
+ (void)memset(buf, (int)' ', offst);
+ (void)memset(hbuf, (int)' ', offst);
+ }
+
+ /*
+ * loop by file
+ */
+ while ((inf = nxtfile(argc, argv, &fname, ohbuf, 0)) != NULL) {
+ if (pgnm) {
+ if (inskip(inf, pgnm, lines))
+ continue;
+ pagecnt = pgnm;
+ } else
+ pagecnt = 1;
+ lncnt = 0;
+
+ /*
+ * loop by page
+ */
+ for(;;) {
+ /*
+ * loop by line
+ */
+ for (i = 0; i < lines; ++i) {
+ ptbf = buf + offst;
+ lstdat = ptbf;
+ j = 0;
+ /*
+ * loop by col
+ */
+ for(;;) {
+ if (nmwd) {
+ /*
+ * add number to column
+ */
+ addnum(ptbf, nmwd, ++lncnt);
+ ptbf += nmwd;
+ *ptbf++ = nmchar;
+ }
+ /*
+ * input line
+ */
+ if ((cnt = inln(inf,ptbf,colwd,&cps,1,
+ &mor)) < 0)
+ break;
+ ptbf += cnt;
+ lstdat = ptbf;
+
+ /*
+ * if last line skip padding
+ */
+ if (++j >= clcnt)
+ break;
+
+ /*
+ * pad to end of column
+ */
+ if (sflag)
+ *ptbf++ = schar;
+ else if ((pln = col - cnt) > 0) {
+ (void)memset(ptbf,(int)' ',pln);
+ ptbf += pln;
+ }
+ }
+
+ /*
+ * determine line length
+ */
+ if ((j = lstdat - buf) <= offst)
+ break;
+ if (!i) {
+ if (pgpause)
+ prpause(pagecnt);
+
+ if (!nohead &&
+ prhead(hbuf, fname, pagecnt))
+ goto out;
+ }
+ /*
+ * output line
+ */
+ if (otln(buf, j, &ips, &ops, 0))
+ goto out;
+ }
+
+ /*
+ * pad to end of page
+ */
+ if (i && prtail(lines-i, 0))
+ goto out;
+
+ /*
+ * if EOF go to next file
+ */
+ if (cnt < 0)
+ break;
+ ++pagecnt;
+ }
+ if (inf != stdin)
+ (void)fclose(inf);
+ }
+ if (eoptind < argc)
+ goto out;
+ error = 0;
+ goto out;
+oomem:
+ mfail();
+out:
+ free(buf);
+ free(hbuf);
+ if (inf != NULL && inf != stdin)
+ (void)fclose(inf);
+ return error;
+}
+
+/*
+ * mulfile: print files with more than one column of output and
+ * more than one file concurrently
+ */
+static int
+mulfile(int argc, char *argv[])
+{
+ char *ptbf;
+ int j;
+ int pln;
+ int cnt;
+ char *lstdat;
+ int i;
+ FILE **fbuf = NULL;
+ int actf;
+ int lncnt;
+ int col;
+ int pagecnt;
+ int fproc;
+ char *buf = NULL;
+ char *hbuf = NULL;
+ char *ohbuf;
+ const char *fname;
+ int ips = 0;
+ int cps = 0;
+ int ops = 0;
+ int mor = 0;
+ int error = 1;
+
+ /*
+ * array of FILE *, one for each operand
+ */
+ if ((fbuf = calloc(clcnt, sizeof(FILE *))) == NULL)
+ goto oomem;
+
+ /*
+ * page header
+ */
+ if ((hbuf = malloc((unsigned)(HDBUF + offst)*sizeof(char))) == NULL)
+ goto oomem;
+ ohbuf = hbuf + offst;
+
+ /*
+ * do not know how many columns yet. The number of operands provide an
+ * upper bound on the number of columns. We use the number of files
+ * we can open successfully to set the number of columns. The operation
+ * of the merge operation (-m) in relation to unsuccesful file opens
+ * is unspecified by posix.
+ */
+ j = 0;
+ while (j < clcnt) {
+ if ((fbuf[j] = nxtfile(argc, argv, &fname, ohbuf, 1)) == NULL)
+ break;
+ if (pgnm && (inskip(fbuf[j], pgnm, lines)))
+ fbuf[j] = NULL;
+ ++j;
+ }
+
+ /*
+ * if no files, exit
+ */
+ if (!j)
+ goto out;
+
+ /*
+ * calculate page boundries based on open file count
+ */
+ clcnt = j;
+ if (nmwd) {
+ colwd = (pgwd - clcnt - nmwd)/clcnt;
+ pgwd = ((colwd + 1) * clcnt) - nmwd - 2;
+ } else {
+ colwd = (pgwd + 1 - clcnt)/clcnt;
+ pgwd = ((colwd + 1) * clcnt) - 1;
+ }
+ if (colwd < 1) {
+ (void)fprintf(errf,
+ "pr: page width too small for %d columns\n", clcnt);
+ goto out;
+ }
+ actf = clcnt;
+ col = colwd + 1;
+
+ /*
+ * line buffer
+ */
+ if ((buf = malloc((unsigned)(pgwd+offst+1)*sizeof(char))) == NULL)
+ goto out;
+ if (offst) {
+ (void)memset(buf, (int)' ', offst);
+ (void)memset(hbuf, (int)' ', offst);
+ }
+ if (pgnm)
+ pagecnt = pgnm;
+ else
+ pagecnt = 1;
+ lncnt = 0;
+
+ /*
+ * continue to loop while any file still has data
+ */
+ while (actf > 0) {
+ /*
+ * loop by line
+ */
+ for (i = 0; i < lines; ++i) {
+ ptbf = buf + offst;
+ lstdat = ptbf;
+ if (nmwd) {
+ /*
+ * add line number to line
+ */
+ addnum(ptbf, nmwd, ++lncnt);
+ ptbf += nmwd;
+ *ptbf++ = nmchar;
+ }
+ j = 0;
+ fproc = 0;
+
+ /*
+ * loop by column
+ */
+ for (j = 0; j < clcnt; ++j) {
+ if (fbuf[j] == NULL) {
+ /*
+ * empty column; EOF
+ */
+ cnt = 0;
+ } else if ((cnt = inln(fbuf[j], ptbf, colwd,
+ &cps, 1, &mor)) < 0) {
+ /*
+ * EOF hit; no data
+ */
+ if (fbuf[j] != stdin)
+ (void)fclose(fbuf[j]);
+ fbuf[j] = NULL;
+ --actf;
+ cnt = 0;
+ } else {
+ /*
+ * process file data
+ */
+ ptbf += cnt;
+ lstdat = ptbf;
+ fproc++;
+ }
+
+ /*
+ * if last ACTIVE column, done with line
+ */
+ if (fproc >= actf)
+ break;
+
+ /*
+ * pad to end of column
+ */
+ if (sflag) {
+ *ptbf++ = schar;
+ } else if ((pln = col - cnt) > 0) {
+ (void)memset(ptbf, (int)' ', pln);
+ ptbf += pln;
+ }
+ }
+
+ /*
+ * calculate data in line
+ */
+ if ((j = lstdat - buf) <= offst)
+ break;
+
+ if (!i) {
+ if (pgpause)
+ prpause(pagecnt);
+
+ if (!nohead && prhead(hbuf, fname, pagecnt))
+ goto out;
+ }
+
+ /*
+ * output line
+ */
+ if (otln(buf, j, &ips, &ops, 0))
+ goto out;
+
+ /*
+ * if no more active files, done
+ */
+ if (actf <= 0) {
+ ++i;
+ break;
+ }
+ }
+
+ /*
+ * pad to end of page
+ */
+ if (i && prtail(lines-i, 0))
+ goto out;
+ ++pagecnt;
+ }
+ if (eoptind < argc)
+ goto out;
+ error = 0;
+ goto out;
+oomem:
+ mfail();
+out:
+ if (fbuf) {
+ for (j = 0; j < clcnt; j++)
+ if (fbuf[j] && fbuf[j] != stdin)
+ (void)fclose(fbuf[j]);
+ free(fbuf);
+ }
+ free(hbuf);
+ free(buf);
+ return error;
+}
+
+/*
+ * inln(): input a line of data (unlimited length lines supported)
+ * Input is optionally expanded to spaces
+ *
+ * inf: file
+ * buf: buffer
+ * lim: buffer length
+ * cps: column positon 1st char in buffer (large line support)
+ * trnc: throw away data more than lim up to \n
+ * mor: set if more data in line (not truncated)
+ */
+static int
+inln(FILE *inf, char *buf, int lim, int *cps, int trnc, int *mor)
+{
+ int col;
+ int gap = ingap;
+ int ch = EOF;
+ char *ptbuf;
+ int chk = (int)inchar;
+
+ ptbuf = buf;
+
+ if (gap) {
+ /*
+ * expanding input option
+ */
+ while ((--lim >= 0) && ((ch = getc(inf)) != EOF)) {
+ /*
+ * is this the input "tab" char
+ */
+ if (ch == chk) {
+ /*
+ * expand to number of spaces
+ */
+ col = (ptbuf - buf) + *cps;
+ col = gap - (col % gap);
+
+ /*
+ * if more than this line, push back
+ */
+ if ((col > lim) && (ungetc(ch, inf) == EOF))
+ return(1);
+
+ /*
+ * expand to spaces
+ */
+ while ((--col >= 0) && (--lim >= 0))
+ *ptbuf++ = ' ';
+ continue;
+ }
+ if (ch == '\n')
+ break;
+ *ptbuf++ = ch;
+ }
+ } else {
+ /*
+ * no expansion
+ */
+ while ((--lim >= 0) && ((ch = getc(inf)) != EOF)) {
+ if (ch == '\n')
+ break;
+ *ptbuf++ = ch;
+ }
+ }
+ col = ptbuf - buf;
+ if (ch == EOF) {
+ *mor = 0;
+ *cps = 0;
+ if (!col)
+ return(-1);
+ return(col);
+ }
+ if (ch == '\n') {
+ /*
+ * entire line processed
+ */
+ *mor = 0;
+ *cps = 0;
+ return(col);
+ }
+
+ /*
+ * line was larger than limit
+ */
+ if (trnc) {
+ /*
+ * throw away rest of line
+ */
+ while ((ch = getc(inf)) != EOF) {
+ if (ch == '\n')
+ break;
+ }
+ *cps = 0;
+ *mor = 0;
+ } else {
+ /*
+ * save column offset if not truncated
+ */
+ *cps += col;
+ *mor = 1;
+ }
+
+ return(col);
+}
+
+/*
+ * otln(): output a line of data. (Supports unlimited length lines)
+ * output is optionally contracted to tabs
+ *
+ * buf: output buffer with data
+ * cnt: number of chars of valid data in buf
+ * svips: buffer input column position (for large lines)
+ * svops: buffer output column position (for large lines)
+ * mor: output line not complete in this buf; more data to come.
+ * 1 is more, 0 is complete, -1 is no \n's
+ */
+static int
+otln(char *buf, int cnt, int *svips, int *svops, int mor)
+{
+ int ops; /* last col output */
+ int ips; /* last col in buf examined */
+ int gap = ogap;
+ int tbps;
+ char *endbuf;
+
+ if (ogap) {
+ /*
+ * contracting on output
+ */
+ endbuf = buf + cnt;
+ ops = *svops;
+ ips = *svips;
+ while (buf < endbuf) {
+ /*
+ * count number of spaces and ochar in buffer
+ */
+ if (*buf == ' ') {
+ ++ips;
+ ++buf;
+ continue;
+ }
+
+ /*
+ * simulate ochar processing
+ */
+ if (*buf == ochar) {
+ ips += gap - (ips % gap);
+ ++buf;
+ continue;
+ }
+
+ /*
+ * got a non space char; contract out spaces
+ */
+ while (ips - ops > 1) {
+ /*
+ * use as many ochar as will fit
+ */
+ if ((tbps = ops + gap - (ops % gap)) > ips)
+ break;
+ if (putchar(ochar) == EOF) {
+ pfail();
+ return(1);
+ }
+ ops = tbps;
+ }
+
+ while (ops < ips) {
+ /*
+ * finish off with spaces
+ */
+ if (putchar(' ') == EOF) {
+ pfail();
+ return(1);
+ }
+ ++ops;
+ }
+
+ /*
+ * output non space char
+ */
+ if (putchar(*buf++) == EOF) {
+ pfail();
+ return(1);
+ }
+ ++ips;
+ ++ops;
+ }
+
+ if (mor > 0) {
+ /*
+ * if incomplete line, save position counts
+ */
+ *svops = ops;
+ *svips = ips;
+ return(0);
+ }
+
+ if (mor < 0) {
+ while (ips - ops > 1) {
+ /*
+ * use as many ochar as will fit
+ */
+ if ((tbps = ops + gap - (ops % gap)) > ips)
+ break;
+ if (putchar(ochar) == EOF) {
+ pfail();
+ return(1);
+ }
+ ops = tbps;
+ }
+ while (ops < ips) {
+ /*
+ * finish off with spaces
+ */
+ if (putchar(' ') == EOF) {
+ pfail();
+ return(1);
+ }
+ ++ops;
+ }
+ return(0);
+ }
+ } else {
+ /*
+ * output is not contracted
+ */
+ if (cnt && (fwrite(buf, sizeof(char), cnt, stdout) <= 0)) {
+ pfail();
+ return(1);
+ }
+ if (mor != 0)
+ return(0);
+ }
+
+ /*
+ * process line end and double space as required
+ */
+ if ((putchar('\n') == EOF) || (dspace && (putchar('\n') == EOF))) {
+ pfail();
+ return(1);
+ }
+ return(0);
+}
+
+/*
+ * inskip(): skip over pgcnt pages with lncnt lines per page
+ * file is closed at EOF (if not stdin).
+ *
+ * inf FILE * to read from
+ * pgcnt number of pages to skip
+ * lncnt number of lines per page
+ */
+static int
+inskip(FILE *inf, int pgcnt, int lncnt)
+{
+ int c;
+ int cnt;
+
+ while(--pgcnt > 0) {
+ cnt = lncnt;
+ while ((c = getc(inf)) != EOF) {
+ if ((c == '\n') && (--cnt == 0))
+ break;
+ }
+ if (c == EOF) {
+ if (inf != stdin)
+ (void)fclose(inf);
+ return(1);
+ }
+ }
+ return(0);
+}
+
+/*
+ * nxtfile: returns a FILE * to next file in arg list and sets the
+ * time field for this file (or current date).
+ *
+ * buf array to store proper date for the header.
+ * dt if set skips the date processing (used with -m)
+ */
+static FILE *
+nxtfile(int argc, char **argv, const char **fname, char *buf, int dt)
+{
+ FILE *inf = NULL;
+ struct timeval tv;
+ struct timezone tz;
+ struct tm *timeptr = NULL;
+ struct stat statbuf;
+ time_t curtime;
+ static int twice = -1;
+
+ ++twice;
+ if (eoptind >= argc) {
+ /*
+ * no file listed; default, use standard input
+ */
+ if (twice)
+ return(NULL);
+ clearerr(stdin);
+ inf = stdin;
+ if (header != NULL)
+ *fname = header;
+ else
+ *fname = FNAME;
+ if (nohead)
+ return(inf);
+ if (gettimeofday(&tv, &tz) < 0) {
+ ++errcnt;
+ (void)fprintf(errf, "pr: cannot get time of day, %s\n",
+ strerror(errno));
+ eoptind = argc - 1;
+ return(NULL);
+ }
+ curtime = tv.tv_sec;
+ timeptr = localtime(&curtime);
+ }
+ for (; eoptind < argc; ++eoptind) {
+ if (strcmp(argv[eoptind], "-") == 0) {
+ /*
+ * process a "-" for filename
+ */
+ clearerr(stdin);
+ inf = stdin;
+ if (header != NULL)
+ *fname = header;
+ else
+ *fname = FNAME;
+ ++eoptind;
+ if (nohead || (dt && twice))
+ return(inf);
+ if (gettimeofday(&tv, &tz) < 0) {
+ ++errcnt;
+ (void)fprintf(errf,
+ "pr: cannot get time of day, %s\n",
+ strerror(errno));
+ return(NULL);
+ }
+ curtime = tv.tv_sec;
+ timeptr = localtime(&curtime);
+ } else {
+ /*
+ * normal file processing
+ */
+ if ((inf = fopen(argv[eoptind], "r")) == NULL) {
+ ++errcnt;
+ if (nodiag)
+ continue;
+ (void)fprintf(errf, "pr: Cannot open %s, %s\n",
+ argv[eoptind], strerror(errno));
+ continue;
+ }
+ if (header != NULL)
+ *fname = header;
+ else if (dt)
+ *fname = FNAME;
+ else
+ *fname = argv[eoptind];
+ ++eoptind;
+ if (nohead || (dt && twice))
+ return(inf);
+
+ if (dt) {
+ if (gettimeofday(&tv, &tz) < 0) {
+ ++errcnt;
+ (void)fprintf(errf,
+ "pr: cannot get time of day, %s\n",
+ strerror(errno));
+ return(NULL);
+ }
+ curtime = tv.tv_sec;
+ timeptr = localtime(&curtime);
+ } else {
+ if (fstat(fileno(inf), &statbuf) < 0) {
+ ++errcnt;
+ (void)fclose(inf);
+ (void)fprintf(errf,
+ "pr: Cannot stat %s, %s\n",
+ argv[eoptind], strerror(errno));
+ return(NULL);
+ }
+ timeptr = localtime(&(statbuf.st_mtime));
+ }
+ }
+ break;
+ }
+ if (inf == NULL)
+ return(NULL);
+
+ /*
+ * set up time field used in header
+ */
+ if (strftime(buf, HDBUF, timefrmt, timeptr) <= 0) {
+ ++errcnt;
+ if (inf != stdin)
+ (void)fclose(inf);
+ (void)fputs("pr: time conversion failed\n", errf);
+ return(NULL);
+ }
+ return(inf);
+}
+
+/*
+ * addnum(): adds the line number to the column
+ * Truncates from the front or pads with spaces as required.
+ * Numbers are right justified.
+ *
+ * buf buffer to store the number
+ * wdth width of buffer to fill
+ * line line number
+ *
+ * NOTE: numbers occupy part of the column. The posix
+ * spec does not specify if -i processing should or should not
+ * occur on number padding. The spec does say it occupies
+ * part of the column. The usage of addnum currently treats
+ * numbers as part of the column so spaces may be replaced.
+ */
+void
+addnum(char *buf, int wdth, int line)
+{
+ char *pt = buf + wdth;
+
+ do {
+ *--pt = digs[line % 10];
+ line /= 10;
+ } while (line && (pt > buf));
+
+ /*
+ * pad with space as required
+ */
+ while (pt > buf)
+ *--pt = ' ';
+}
+
+/*
+ * prpause(): pause before printing each page
+ *
+ * pagcnt page number
+ */
+static void
+prpause(int pagcnt)
+{
+
+ if (ttyout) {
+ int c;
+
+ (void)putc('\a', stderr);
+ (void)fflush(stderr);
+
+ while ((c = getc(ttyinf)) != '\n' && c != EOF)
+ ;
+
+ /*
+ * pause ONLY before first page of first file
+ */
+ if (pgpause == FIRSTPAGE && pagcnt == 1)
+ pgpause = NO_PAUSE;
+ }
+}
+
+/*
+ * prhead(): prints the top of page header
+ *
+ * buf buffer with time field (and offset)
+ * cnt number of chars in buf
+ * fname fname field for header
+ * pagcnt page number
+ */
+static int
+prhead(char *buf, const char *fname, int pagcnt)
+{
+ int ips = 0;
+ int ops = 0;
+
+ if ((putchar('\n') == EOF) || (putchar('\n') == EOF)) {
+ pfail();
+ return(1);
+ }
+ /*
+ * posix is not clear if the header is subject to line length
+ * restrictions. The specification for header line format
+ * in the spec clearly does not limit length. No pr currently
+ * restricts header length. However if we need to truncate in
+ * an reasonable way, adjust the length of the printf by
+ * changing HDFMT to allow a length max as an argument printf.
+ * buf (which contains the offset spaces and time field could
+ * also be trimmed
+ *
+ * note only the offset (if any) is processed for tab expansion
+ */
+ if (offst && otln(buf, offst, &ips, &ops, -1))
+ return(1);
+ (void)printf(HDFMT,buf+offst, fname, pagcnt);
+ return(0);
+}
+
+/*
+ * prtail(): pad page with empty lines (if required) and print page trailer
+ * if requested
+ *
+ * cnt number of lines of padding needed
+ * incomp was a '\n' missing from last line output
+ */
+static int
+prtail(int cnt, int incomp)
+{
+ if (nohead) {
+ /*
+ * only pad with no headers when incomplete last line
+ */
+ if (!incomp)
+ return(0);
+ if ((dspace && (putchar('\n') == EOF)) ||
+ (putchar('\n') == EOF)) {
+ pfail();
+ return(1);
+ }
+ return(0);
+ }
+
+ /*
+ * if double space output two \n
+ */
+ if (dspace)
+ cnt *= 2;
+
+ /*
+ * if an odd number of lines per page, add an extra \n
+ */
+ if (addone)
+ ++cnt;
+
+ /*
+ * pad page
+ */
+ if (formfeed) {
+ if ((incomp && (putchar('\n') == EOF)) ||
+ (putchar('\f') == EOF)) {
+ pfail();
+ return(1);
+ }
+ return(0);
+ }
+ cnt += TAILLEN;
+ while (--cnt >= 0) {
+ if (putchar('\n') == EOF) {
+ pfail();
+ return(1);
+ }
+ }
+ return(0);
+}
+
+/*
+ * terminate(): when a SIGINT is recvd
+ */
+static void
+terminate(int which_sig)
+{
+ flsh_errs();
+ (void)raise_default_signal(which_sig);
+ exit(1);
+}
+
+
+/*
+ * flsh_errs(): output saved up diagnostic messages after all normal
+ * processing has completed
+ */
+static void
+flsh_errs(void)
+{
+ char buf[BUFSIZ];
+
+ (void)fflush(stdout);
+ (void)fflush(errf);
+ if (errf == stderr)
+ return;
+ rewind(errf);
+ while (fgets(buf, BUFSIZ, errf) != NULL)
+ (void)fputs(buf, stderr);
+}
+
+static void
+mfail(void)
+{
+ (void)fputs("pr: memory allocation failed\n", errf);
+}
+
+static void
+pfail(void)
+{
+ (void)fprintf(errf, "pr: write failure, %s\n", strerror(errno));
+}
+
+static void
+usage(void)
+{
+ (void)fputs(
+ "usage: pr [+page] [-col] [-adFfmprt] [-e[ch][gap]] [-h header]\n",
+ errf);
+ (void)fputs(
+ " [-i[ch][gap]] [-l line] [-n[ch][width]] [-o offset]\n",
+ errf);
+ (void)fputs(
+ " [-s[ch]] [-T timefmt] [-w width] [-] [file ...]\n",
+ errf);
+}
+
+/*
+ * setup: Validate command args, initialize and perform sanity
+ * checks on options
+ */
+static int
+setup(int argc, char **argv)
+{
+ int c;
+ int eflag = 0;
+ int iflag = 0;
+ int wflag = 0;
+ int cflag = 0;
+
+ ttyinf = stdin;
+
+ if (isatty(fileno(stdout))) {
+ /*
+ * defer diagnostics until processing is done
+ */
+ if ((errf = tmpfile()) == NULL) {
+ (void)fputs("Cannot defer diagnostic messages\n",stderr);
+ return(1);
+ }
+ ttyout = 1;
+ } else
+ errf = stderr;
+ while ((c = egetopt(argc, argv, "#adFfmrte?h:i?l:n?o:ps?T:w:")) != -1) {
+ switch (c) {
+ case '+':
+ if ((pgnm = atoi(eoptarg)) < 1) {
+ (void)fputs("pr: +page number must be 1 or more\n",
+ errf);
+ return(1);
+ }
+ break;
+ case '-':
+ if ((clcnt = atoi(eoptarg)) < 1) {
+ (void)fputs("pr: -columns must be 1 or more\n",errf);
+ return(1);
+ }
+ if (clcnt > 1)
+ ++cflag;
+ break;
+ case 'a':
+ ++across;
+ break;
+ case 'd':
+ ++dspace;
+ break;
+ case 'e':
+ ++eflag;
+ if ((eoptarg != NULL) &&
+ !isdigit((unsigned char)*eoptarg))
+ inchar = *eoptarg++;
+ else
+ inchar = INCHAR;
+ if ((eoptarg != NULL) &&
+ isdigit((unsigned char)*eoptarg)) {
+ if ((ingap = atoi(eoptarg)) < 0) {
+ (void)fputs(
+ "pr: -e gap must be 0 or more\n", errf);
+ return(1);
+ }
+ if (ingap == 0)
+ ingap = INGAP;
+ } else if ((eoptarg != NULL) && (*eoptarg != '\0')) {
+ (void)fprintf(errf,
+ "pr: invalid value for -e %s\n", eoptarg);
+ return(1);
+ } else
+ ingap = INGAP;
+ break;
+ case 'f':
+ pgpause |= FIRSTPAGE;
+ /*FALLTHROUGH*/
+ case 'F':
+ ++formfeed;
+ break;
+ case 'h':
+ header = eoptarg;
+ break;
+ case 'i':
+ ++iflag;
+ if ((eoptarg != NULL) &&
+ !isdigit((unsigned char)*eoptarg))
+ ochar = *eoptarg++;
+ else
+ ochar = OCHAR;
+ if ((eoptarg != NULL) &&
+ isdigit((unsigned char)*eoptarg)) {
+ if ((ogap = atoi(eoptarg)) < 0) {
+ (void)fputs(
+ "pr: -i gap must be 0 or more\n", errf);
+ return(1);
+ }
+ if (ogap == 0)
+ ogap = OGAP;
+ } else if ((eoptarg != NULL) && (*eoptarg != '\0')) {
+ (void)fprintf(errf,
+ "pr: invalid value for -i %s\n", eoptarg);
+ return(1);
+ } else
+ ogap = OGAP;
+ break;
+ case 'l':
+ if (!isdigit((unsigned char)*eoptarg) ||
+ ((lines=atoi(eoptarg)) < 1)) {
+ (void)fputs(
+ "pr: Number of lines must be 1 or more\n",errf);
+ return(1);
+ }
+ break;
+ case 'm':
+ ++merge;
+ break;
+ case 'n':
+ if ((eoptarg != NULL) &&
+ !isdigit((unsigned char)*eoptarg))
+ nmchar = *eoptarg++;
+ else
+ nmchar = NMCHAR;
+ if ((eoptarg != NULL) &&
+ isdigit((unsigned char)*eoptarg)) {
+ if ((nmwd = atoi(eoptarg)) < 1) {
+ (void)fputs(
+ "pr: -n width must be 1 or more\n",errf);
+ return(1);
+ }
+ } else if ((eoptarg != NULL) && (*eoptarg != '\0')) {
+ (void)fprintf(errf,
+ "pr: invalid value for -n %s\n", eoptarg);
+ return(1);
+ } else
+ nmwd = NMWD;
+ break;
+ case 'o':
+ if (!isdigit((unsigned char)*eoptarg) ||
+ ((offst = atoi(eoptarg))< 1)){
+ (void)fputs("pr: -o offset must be 1 or more\n",
+ errf);
+ return(1);
+ }
+ break;
+ case 'p':
+ pgpause |= EACHPAGE;
+ break;
+ case 'r':
+ ++nodiag;
+ break;
+ case 's':
+ ++sflag;
+ if (eoptarg == NULL)
+ schar = SCHAR;
+ else
+ schar = *eoptarg++;
+ if (eoptarg && *eoptarg != '\0') {
+ (void)fprintf(errf,
+ "pr: invalid value for -s %s\n", eoptarg);
+ return(1);
+ }
+ break;
+ case 'T':
+ timefrmt = eoptarg;
+ break;
+ case 't':
+ ++nohead;
+ break;
+ case 'w':
+ ++wflag;
+ if (!isdigit((unsigned char)*eoptarg) ||
+ ((pgwd = atoi(eoptarg)) < 1)){
+ (void)fputs(
+ "pr: -w width must be 1 or more \n",errf);
+ return(1);
+ }
+ break;
+ case '?':
+ default:
+ return(1);
+ }
+ }
+
+ /*
+ * default and sanity checks
+ */
+ if (!clcnt) {
+ if (merge) {
+ if ((clcnt = argc - eoptind) <= 1) {
+ clcnt = CLCNT;
+ merge = 0;
+ }
+ } else
+ clcnt = CLCNT;
+ }
+ if (across) {
+ if (clcnt == 1) {
+ (void)fputs("pr: -a flag requires multiple columns\n",
+ errf);
+ return(1);
+ }
+ if (merge) {
+ (void)fputs("pr: -m cannot be used with -a\n", errf);
+ return(1);
+ }
+ }
+ if (!wflag) {
+ if (sflag)
+ pgwd = SPGWD;
+ else
+ pgwd = PGWD;
+ }
+ if (cflag || merge) {
+ if (!eflag) {
+ inchar = INCHAR;
+ ingap = INGAP;
+ }
+ if (!iflag) {
+ ochar = OCHAR;
+ ogap = OGAP;
+ }
+ }
+ if (cflag) {
+ if (merge) {
+ (void)fputs(
+ "pr: -m cannot be used with multiple columns\n", errf);
+ return(1);
+ }
+ if (nmwd) {
+ colwd = (pgwd + 1 - (clcnt * (nmwd + 2)))/clcnt;
+ pgwd = ((colwd + nmwd + 2) * clcnt) - 1;
+ } else {
+ colwd = (pgwd + 1 - clcnt)/clcnt;
+ pgwd = ((colwd + 1) * clcnt) - 1;
+ }
+ if (colwd < 1) {
+ (void)fprintf(errf,
+ "pr: page width is too small for %d columns\n",clcnt);
+ return(1);
+ }
+ }
+ if (!lines)
+ lines = LINES;
+
+ /*
+ * make sure long enough for headers. if not disable
+ */
+ if (lines <= HEADLEN + TAILLEN)
+ ++nohead;
+ else if (!nohead)
+ lines -= HEADLEN + TAILLEN;
+
+ /*
+ * adjust for double space on odd length pages
+ */
+ if (dspace) {
+ if (lines == 1)
+ dspace = 0;
+ else {
+ if (lines & 1)
+ ++addone;
+ lines /= 2;
+ }
+ }
+
+ /*
+ * open /dev/tty if we are to pause before each page
+ * but only if stdout is a terminal and stdin is not a terminal
+ */
+ if (ttyout && pgpause && !isatty(fileno(stdin))) {
+ if ((ttyinf = fopen("/dev/tty", "r")) == NULL) {
+ (void)fprintf(errf, "pr: cannot open terminal\n");
+ return(1);
+ }
+ }
+
+ return(0);
+}
diff --git a/usr.bin/pr/pr.h b/usr.bin/pr/pr.h
new file mode 100644
index 0000000..0afd374
--- /dev/null
+++ b/usr.bin/pr/pr.h
@@ -0,0 +1,79 @@
+/* $NetBSD: pr.h,v 1.5 2012/07/24 02:13:04 ginsbach Exp $ */
+
+/*-
+ * Copyright (c) 1991 Keith Muller.
+ * Copyright (c) 1993
+ * The Regents of the University of California. All rights reserved.
+ * Copyright (c) 2012
+ * The NetBSD Foundation, Inc.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Keith Muller of the University of California, San Diego.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * from: @(#)pr.h 8.1 (Berkeley) 6/6/93
+ * $NetBSD: pr.h,v 1.5 2012/07/24 02:13:04 ginsbach Exp $
+ */
+
+/*
+ * parameter defaults
+ */
+#define CLCNT 1
+#define INCHAR '\t'
+#define INGAP 8
+#define OCHAR '\t'
+#define OGAP 8
+#define LINES 66
+#define NMWD 5
+#define NMCHAR '\t'
+#define SCHAR '\t'
+#define PGWD 72
+#define SPGWD 512
+
+/*
+ * misc default values
+ */
+#define HDFMT "%s %s Page %d\n\n\n"
+#define HEADLEN 5
+#define TAILLEN 5
+#define TIMEFMT "%b %e %H:%M %Y"
+#define FNAME ""
+#define LBUF 8192
+#define HDBUF 512
+
+/* when to pause before (for -f and -p options) */
+#define NO_PAUSE 0
+#define FIRSTPAGE 1
+#define ENSUINGPAGES 2
+#define EACHPAGE (FIRSTPAGE | ENSUINGPAGES)
+
+/*
+ * structure for vertical columns. Used to balance cols on last page
+ */
+struct vcol {
+ char *pt; /* ptr to col */
+ int cnt; /* char count */
+};
diff --git a/usr.bin/printf/printf.1 b/usr.bin/printf/printf.1
new file mode 100644
index 0000000..588614d
--- /dev/null
+++ b/usr.bin/printf/printf.1
@@ -0,0 +1,438 @@
+.\" $NetBSD: printf.1,v 1.31 2018/08/31 17:27:35 kre Exp $
+.\"
+.\" Copyright (c) 1989, 1990, 1993
+.\" The Regents of the University of California. All rights reserved.
+.\"
+.\" This code is derived from software contributed to Berkeley by
+.\" the Institute of Electrical and Electronics Engineers, Inc.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\" notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must reproduce the above copyright
+.\" notice, this list of conditions and the following disclaimer in the
+.\" documentation and/or other materials provided with the distribution.
+.\" 3. Neither the name of the University nor the names of its contributors
+.\" may be used to endorse or promote products derived from this software
+.\" without specific prior written permission.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+.\" ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+.\" SUCH DAMAGE.
+.\"
+.\" from: @(#)printf.1 8.1 (Berkeley) 6/6/93
+.\"
+.Dd August 31, 2018
+.Dt PRINTF 1
+.Os
+.Sh NAME
+.Nm printf
+.Nd formatted output
+.Sh SYNOPSIS
+.Nm
+.Ar format
+.Op Ar arguments ...
+.Sh DESCRIPTION
+.Nm
+formats and prints its arguments, after the first, under control
+of the
+.Ar format .
+The
+.Ar format
+is a character string which contains three types of objects: plain characters,
+which are simply copied to standard output, character escape sequences which
+are converted and copied to the standard output, and format specifications,
+each of which causes printing of the next successive
+.Ar argument .
+.Pp
+The
+.Ar arguments
+after the first are treated as strings if the corresponding format is
+either
+.Cm b ,
+.Cm B ,
+.Cm c ,
+or
+.Cm s ;
+otherwise it is evaluated as a C constant, with the following extensions:
+.Pp
+.Bl -bullet -offset indent -compact
+.It
+A leading plus or minus sign is allowed.
+.It
+If the leading character is a single or double quote, the value is the ASCII
+code of the next character.
+.El
+.Pp
+The format string is reused as often as necessary to satisfy the
+.Ar arguments .
+Any extra format specifications are evaluated with zero or the null
+string.
+.Pp
+Character escape sequences are in backslash notation as defined in
+.St -ansiC .
+The characters and their meanings are as follows:
+.Bl -tag -width Ds -offset indent
+.It Cm \ee
+Write an
+.Aq escape
+character.
+.It Cm \ea
+Write a
+.Aq bell
+character.
+.It Cm \eb
+Write a
+.Aq backspace
+character.
+.It Cm \ef
+Write a
+.Aq form-feed
+character.
+.It Cm \en
+Write a
+.Aq new-line
+character.
+.It Cm \er
+Write a
+.Aq carriage return
+character.
+.It Cm \et
+Write a
+.Aq tab
+character.
+.It Cm \ev
+Write a
+.Aq vertical tab
+character.
+.It Cm \e\'
+Write a
+.Aq single quote
+character.
+.It Cm \e"
+Write a
+.Aq double quote
+character.
+.It Cm \e\e
+Write a backslash character.
+.It Cm \e Ns Ar num
+Write an 8\-bit character whose ASCII
+value is the 1\-, 2\-, or 3\-digit octal number
+.Ar num .
+.It Cm \ex Ns Ar xx
+Write an 8\-bit character whose ASCII
+value is the 1\- or 2\-digit hexadecimal number
+.Ar xx .
+.El
+.Pp
+Each format specification is introduced by the percent character
+.Pq Dq \&% .
+The remainder of the format specification includes,
+in the following order:
+.Bl -tag -width Ds
+.It Zero or more of the following flags :
+.Bl -tag -width Ds
+.It Cm #
+A
+.Sq #
+character specifying that the value should be printed in an
+.Dq alternative form .
+For
+.Cm b ,
+.Cm c ,
+.Cm d ,
+and
+.Cm s
+formats, this option has no effect.
+For the
+.Cm o
+format the precision of the number is increased to force the first
+character of the output string to a zero.
+For the
+.Cm x
+.Pq Cm X
+format, a non-zero result has the string
+.Li 0x
+.Pq Li 0X
+prepended to it.
+For
+.Cm e ,
+.Cm E ,
+.Cm f ,
+.Cm F ,
+.Cm g ,
+and
+.Cm G
+formats, the result will always contain a decimal point, even if no
+digits follow the point (normally, a decimal point only appears in the
+results of those formats if a digit follows the decimal point).
+For
+.Cm g
+and
+.Cm G
+formats, trailing zeros are not removed from the result as they
+would otherwise be.
+.\" I turned this off - decided it isn't a valid use of '#'
+.\" For the
+.\" .Cm B
+.\" format, backslash-escape sequences are expanded first;
+.It Cm \&\-
+A minus sign
+.Sq \-
+which specifies
+.Em left adjustment
+of the output in the indicated field;
+.It Cm \&+
+A
+.Sq \&+
+character specifying that there should always be
+a sign placed before the number when using signed formats.
+.It Sq \&\ \&
+A space specifying that a blank should be left before a positive number
+for a signed format.
+A
+.Sq \&+
+overrides a space if both are used;
+.It Cm \&0
+A zero `0' character indicating that zero-padding should be used
+rather than blank-padding.
+A
+.Sq \-
+overrides a
+.Sq \&0
+if both are used;
+.El
+.It Field Width :
+An optional digit string specifying a
+.Em field width ;
+if the output string has fewer characters than the field width it will
+be blank-padded on the left (or right, if the left-adjustment indicator
+has been given) to make up the field width (note that a leading zero
+is a flag, but an embedded zero is part of a field width);
+.It Precision :
+An optional period,
+.Sq Cm \&. ,
+followed by an optional digit string giving a
+.Em precision
+which specifies the number of digits to appear after the decimal point,
+for
+.Cm e
+and
+.Cm f
+formats, or the maximum number of characters to be printed
+from a string
+.Sm off
+.Pf ( Cm b ,
+.Sm on
+.Cm B ,
+and
+.Cm s
+formats); if the digit string is missing, the precision is treated
+as zero;
+.It Format :
+A character which indicates the type of format to use (one of
+.Cm diouxXfFeEgGaAbBcs ) .
+.El
+.Pp
+A field width or precision may be
+.Sq Cm \&*
+instead of a digit string.
+In this case an
+.Ar argument
+supplies the field width or precision.
+.Pp
+The format characters and their meanings are:
+.Bl -tag -width Fl
+.It Cm diouXx
+The
+.Ar argument ,
+which must represent an integer constant,
+with an optional leading plus or minus sign,
+is printed as a signed decimal (d or i),
+unsigned octal (o), unsigned decimal (u),
+or unsigned hexadecimal (X or x).
+.It Cm fF
+The
+.Ar argument
+is printed in the style
+.Sm off
+.Pf [\-]ddd Cm \&. No ddd
+.Sm on
+where the number of d's
+after the decimal point is equal to the precision specification for
+the argument.
+If the precision is missing, 6 digits are given; if the precision
+is explicitly 0, no digits and no decimal point are printed.
+If the number is Infinity, or Not a Number (NaN), then
+.Dq inf
+.Pq \&or Dq nan
+is printed for
+.Cm f
+format, and
+.Dq INF
+.Pq \&or Dq NAN
+for
+.Cm F
+format.
+.It Cm eE
+The
+.Ar argument
+is printed in the style
+.Sm off
+.Pf [\-]d Cm \&. No ddd Cm e No \*(Pmdd
+.Sm on
+where there
+is one digit before the decimal point and the number after is equal to
+the precision specification for the argument; when the precision is
+missing, 6 digits are produced.
+An upper-case E is used for an
+.Sq E
+format, and upper-case for Infinity and NaN as for
+.Sq F
+format.
+.It Cm gG
+The
+.Ar argument
+is printed in style
+.Cm f
+.Pq Cm F
+or in style
+.Cm e
+.Pq Cm E
+whichever gives full precision in minimum space.
+.It Cm aA
+The
+.Ar argument
+is treated as a floating point number,
+for which the underlying hexadecimal representation is
+printed.
+See
+.Xr printf 3
+for the details.
+.It Cm b
+Characters from the string
+.Ar argument
+are printed with backslash-escape sequences expanded.
+.Pp
+The following additional backslash-escape sequences are supported:
+.Bl -tag -width Ds
+.It Cm \ec
+Causes
+.Nm
+to ignore any remaining characters in the string operand containing it,
+any remaining string operands, and any additional characters in
+the format operand.
+.It Cm \e0 Ns Ar num
+Write an 8\-bit character whose ASCII value is the 1\-, 2\-, or
+3\-digit octal number
+.Ar num .
+.It Cm \e^ Ns Ar c
+Write the control character
+.Ar c .
+Generates characters `\e000' through `\e037`, and `\e177' (from `\e^?').
+.It Cm \eM\- Ns Ar c
+Write the character
+.Ar c
+with the 8th bit set.
+Generates characters `\e241' through `\e376`.
+.It Cm \eM^ Ns Ar c
+Write the control character
+.Ar c
+with the 8th bit set.
+Generates characters `\e200' through `\e237`, and `\e377' (from `\eM^?').
+.El
+.It Cm B
+Characters from the string
+.Ar argument
+are printed with unprintable characters backslash-escaped using the
+.Sm off
+.Pf ` Cm \e Ar c No ',
+.Pf ` Cm \e^ Ar c No ',
+.Pf ` Cm \eM\- Ar c No '
+or
+.Pf ` Cm \eM^ Ar c No ',
+.Sm on
+formats described above.
+.It Cm c
+The first character of
+.Ar argument
+is printed.
+.It Cm s
+Characters from the string
+.Ar argument
+are printed until the end is reached or until the number of characters
+indicated by the precision specification is reached; if the
+precision is omitted, all characters in the string are printed.
+.It Cm \&%
+Print a `%'; no argument is used.
+.El
+.Pp
+In no case does a non-existent or small field width cause truncation of
+a field; padding takes place only if the specified field width exceeds
+the actual width.
+.Pp
+If the first character of
+.Ar format
+is a dash,
+.Ar format
+must be preceded by a word consisting of two dashes (--) to prevent it
+from being interpreted as an option string.
+.Sh EXIT STATUS
+.Ex -std
+.Sh SEE ALSO
+.Xr echo 1 ,
+.Xr printf 3 ,
+.Xr vis 3 ,
+.Xr printf 9
+.Sh STANDARDS
+The
+.Nm
+utility conforms to
+.St -p1003.1-2001 .
+.Pp
+Support for the floating point formats and `*' as a field width and precision
+are optional in POSIX.
+.Pp
+The behaviour of the %B format and the \e', \e", \exxx, \ee and
+\e[M][\-|^]c escape sequences are undefined in POSIX.
+.Sh BUGS
+Since the floating point numbers are translated from ASCII to
+floating-point and then back again, floating-point precision may be lost.
+.Pp
+Hexadecimal character constants are restricted to, and should be specified
+as, two character constants.
+This is contrary to the ISO C standard but
+does guarantee detection of the end of the constant.
+.Sh NOTES
+All formats which treat the
+.Ar argument
+as a number first convert the
+.Ar argument
+from its external representation as a character string
+to an internal numeric representation, and then apply the
+format to the internal numeric representation, producing
+another external character string representation.
+One might expect the
+.Cm \&%c
+format to do likewise, but in fact it does not.
+.Pp
+To convert a string representation of a decimal, octal, or hexadecimal
+number into the corresponding character, two nested
+.Nm
+invocations may be used, in which the inner invocation
+converts the input to an octal string, and the outer
+invocation uses the octal string as part of a format.
+For example, the following command outputs the character whose code
+is 0x0A, which is a newline in ASCII:
+.Pp
+.Dl printf \&"$(printf \&"\e\e%o" \&"0x0A")"
diff --git a/usr.bin/printf/printf.c b/usr.bin/printf/printf.c
new file mode 100644
index 0000000..4d88e7d
--- /dev/null
+++ b/usr.bin/printf/printf.c
@@ -0,0 +1,709 @@
+/* $NetBSD: printf.c,v 1.48 2019/01/27 12:03:09 kre Exp $ */
+
+/*
+ * Copyright (c) 1989, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+#ifndef lint
+#if !defined(BUILTIN) && !defined(SHELL)
+__COPYRIGHT("@(#) Copyright (c) 1989, 1993\
+ The Regents of the University of California. All rights reserved.");
+#endif
+#endif
+
+#ifndef lint
+#if 0
+static char sccsid[] = "@(#)printf.c 8.2 (Berkeley) 3/22/95";
+#else
+__RCSID("$NetBSD: printf.c,v 1.48 2019/01/27 12:03:09 kre Exp $");
+#endif
+#endif /* not lint */
+
+#include <sys/types.h>
+
+#include <ctype.h>
+#include <err.h>
+#include <errno.h>
+#include <inttypes.h>
+#include <limits.h>
+#include <locale.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#ifdef __GNUC__
+#define ESCAPE '\e'
+#else
+#define ESCAPE 033
+#endif
+
+static void conv_escape_str(char *, void (*)(int), int);
+static char *conv_escape(char *, char *, int);
+static char *conv_expand(const char *);
+static char getchr(void);
+static double getdouble(void);
+static int getwidth(void);
+static intmax_t getintmax(void);
+static char *getstr(void);
+static char *mklong(const char *, char);
+static void check_conversion(const char *, const char *);
+static void usage(void);
+
+static void b_count(int);
+static void b_output(int);
+static size_t b_length;
+static char *b_fmt;
+
+static int rval;
+static char **gargv;
+
+#ifdef BUILTIN /* csh builtin */
+#define main progprintf
+#endif
+
+#ifdef SHELL /* sh (aka ash) builtin */
+#define main printfcmd
+#include "../../bin/sh/bltin/bltin.h"
+#endif /* SHELL */
+
+#define PF(f, func) { \
+ if (fieldwidth != -1) { \
+ if (precision != -1) \
+ error = printf(f, fieldwidth, precision, func); \
+ else \
+ error = printf(f, fieldwidth, func); \
+ } else if (precision != -1) \
+ error = printf(f, precision, func); \
+ else \
+ error = printf(f, func); \
+}
+
+#define APF(cpp, f, func) { \
+ if (fieldwidth != -1) { \
+ if (precision != -1) \
+ error = asprintf(cpp, f, fieldwidth, precision, func); \
+ else \
+ error = asprintf(cpp, f, fieldwidth, func); \
+ } else if (precision != -1) \
+ error = asprintf(cpp, f, precision, func); \
+ else \
+ error = asprintf(cpp, f, func); \
+}
+
+#ifdef main
+int main(int, char *[]);
+#endif
+
+int
+main(int argc, char *argv[])
+{
+ char *fmt, *start;
+ int fieldwidth, precision;
+ char nextch;
+ char *format;
+ char ch;
+ int error, o;
+
+#if !defined(SHELL) && !defined(BUILTIN)
+ (void)setlocale (LC_ALL, "");
+#endif
+
+ rval = 0; /* clear for builtin versions (avoid holdover) */
+
+ while ((o = getopt(argc, argv, "")) != -1) {
+ switch (o) {
+ case '?':
+ default:
+ usage();
+ return 1;
+ }
+ }
+ argc -= optind;
+ argv += optind;
+
+ if (argc < 1) {
+ usage();
+ return 1;
+ }
+
+ format = *argv;
+ gargv = ++argv;
+
+#define SKIP1 "#-+ 0'"
+#define SKIP2 "0123456789"
+ do {
+ /*
+ * Basic algorithm is to scan the format string for conversion
+ * specifications -- once one is found, find out if the field
+ * width or precision is a '*'; if it is, gather up value.
+ * Note, format strings are reused as necessary to use up the
+ * provided arguments, arguments of zero/null string are
+ * provided to use up the format string.
+ */
+
+ /* find next format specification */
+ for (fmt = format; (ch = *fmt++) != '\0';) {
+ if (ch == '\\') {
+ char c_ch;
+ fmt = conv_escape(fmt, &c_ch, 0);
+ putchar(c_ch);
+ continue;
+ }
+ if (ch != '%' || (*fmt == '%' && ++fmt)) {
+ (void)putchar(ch);
+ continue;
+ }
+
+ /*
+ * Ok - we've found a format specification,
+ * Save its address for a later printf().
+ */
+ start = fmt - 1;
+
+ /* skip to field width */
+ fmt += strspn(fmt, SKIP1);
+ if (*fmt == '*') {
+ fmt++;
+ fieldwidth = getwidth();
+ } else {
+ fieldwidth = -1;
+
+ /* skip to possible '.' for precision */
+ fmt += strspn(fmt, SKIP2);
+ }
+
+ if (*fmt == '.') {
+ /* get following precision */
+ fmt++;
+ if (*fmt == '*') {
+ fmt++;
+ precision = getwidth();
+ } else {
+ precision = -1;
+ fmt += strspn(fmt, SKIP2);
+ }
+ } else
+ precision = -1;
+
+ ch = *fmt;
+ if (!ch) {
+ warnx("%s: missing format character", start);
+ return 1;
+ }
+
+ /*
+ * null terminate format string to we can use it
+ * as an argument to printf.
+ */
+ nextch = fmt[1];
+ fmt[1] = 0;
+
+ switch (ch) {
+
+ case 'B': {
+ const char *p = conv_expand(getstr());
+
+ if (p == NULL)
+ goto out;
+ *fmt = 's';
+ PF(start, p);
+ if (error < 0)
+ goto out;
+ break;
+ }
+ case 'b': {
+ /*
+ * There has to be a better way to do this,
+ * but the string we generate might have
+ * embedded nulls
+ */
+ static char *a, *t;
+ char *cp = getstr();
+
+ /* Free on entry in case shell longjumped out */
+ if (a != NULL)
+ free(a);
+ a = NULL;
+ if (t != NULL)
+ free(t);
+ t = NULL;
+
+ /* Count number of bytes we want to output */
+ b_length = 0;
+ conv_escape_str(cp, b_count, 0);
+ t = malloc(b_length + 1);
+ if (t == NULL)
+ goto out;
+ (void)memset(t, 'x', b_length);
+ t[b_length] = 0;
+
+ /* Get printf to calculate the lengths */
+ *fmt = 's';
+ APF(&a, start, t);
+ if (error == -1)
+ goto out;
+ b_fmt = a;
+
+ /* Output leading spaces and data bytes */
+ conv_escape_str(cp, b_output, 1);
+
+ /* Add any trailing spaces */
+ printf("%s", b_fmt);
+ break;
+ }
+ case 'c': {
+ char p = getchr();
+
+ PF(start, p);
+ if (error < 0)
+ goto out;
+ break;
+ }
+ case 's': {
+ char *p = getstr();
+
+ PF(start, p);
+ if (error < 0)
+ goto out;
+ break;
+ }
+ case 'd':
+ case 'i': {
+ intmax_t p = getintmax();
+ char *f = mklong(start, ch);
+
+ PF(f, p);
+ if (error < 0)
+ goto out;
+ break;
+ }
+ case 'o':
+ case 'u':
+ case 'x':
+ case 'X': {
+ uintmax_t p = (uintmax_t)getintmax();
+ char *f = mklong(start, ch);
+
+ PF(f, p);
+ if (error < 0)
+ goto out;
+ break;
+ }
+ case 'a':
+ case 'A':
+ case 'e':
+ case 'E':
+ case 'f':
+ case 'F':
+ case 'g':
+ case 'G': {
+ double p = getdouble();
+
+ PF(start, p);
+ if (error < 0)
+ goto out;
+ break;
+ }
+ case '%':
+ /* Don't ask, but this is useful ... */
+ if (fieldwidth == 'N' && precision == 'B')
+ return 0;
+ /* FALLTHROUGH */
+ default:
+ warnx("%s: invalid directive", start);
+ return 1;
+ }
+ *fmt++ = ch;
+ *fmt = nextch;
+ /* escape if a \c was encountered */
+ if (rval & 0x100)
+ return rval & ~0x100;
+ }
+ } while (gargv != argv && *gargv);
+
+ return rval & ~0x100;
+ out:
+ warn("print failed");
+ return 1;
+}
+
+/* helper functions for conv_escape_str */
+
+static void
+/*ARGSUSED*/
+b_count(int ch)
+{
+ b_length++;
+}
+
+/* Output one converted character for every 'x' in the 'format' */
+
+static void
+b_output(int ch)
+{
+ for (;;) {
+ switch (*b_fmt++) {
+ case 0:
+ b_fmt--;
+ return;
+ case ' ':
+ putchar(' ');
+ break;
+ default:
+ putchar(ch);
+ return;
+ }
+ }
+}
+
+
+/*
+ * Print SysV echo(1) style escape string
+ * Halts processing string if a \c escape is encountered.
+ */
+static void
+conv_escape_str(char *str, void (*do_putchar)(int), int quiet)
+{
+ int value;
+ int ch;
+ char c;
+
+ while ((ch = *str++) != '\0') {
+ if (ch != '\\') {
+ do_putchar(ch);
+ continue;
+ }
+
+ ch = *str++;
+ if (ch == 'c') {
+ /* \c as in SYSV echo - abort all processing.... */
+ rval |= 0x100;
+ break;
+ }
+
+ /*
+ * %b string octal constants are not like those in C.
+ * They start with a \0, and are followed by 0, 1, 2,
+ * or 3 octal digits.
+ */
+ if (ch == '0') {
+ int octnum = 0, i;
+ for (i = 0; i < 3; i++) {
+ if (!isdigit((unsigned char)*str) || *str > '7')
+ break;
+ octnum = (octnum << 3) | (*str++ - '0');
+ }
+ do_putchar(octnum);
+ continue;
+ }
+
+ /* \[M][^|-]C as defined by vis(3) */
+ if (ch == 'M' && *str == '-') {
+ do_putchar(0200 | str[1]);
+ str += 2;
+ continue;
+ }
+ if (ch == 'M' && *str == '^') {
+ str++;
+ value = 0200;
+ ch = '^';
+ } else
+ value = 0;
+ if (ch == '^') {
+ ch = *str++;
+ if (ch == '?')
+ value |= 0177;
+ else
+ value |= ch & 037;
+ do_putchar(value);
+ continue;
+ }
+
+ /* Finally test for sequences valid in the format string */
+ str = conv_escape(str - 1, &c, quiet);
+ do_putchar(c);
+ }
+}
+
+/*
+ * Print "standard" escape characters
+ */
+static char *
+conv_escape(char *str, char *conv_ch, int quiet)
+{
+ char value;
+ char ch;
+ char num_buf[4], *num_end;
+
+ ch = *str++;
+
+ switch (ch) {
+ case '\0':
+ if (!quiet)
+ warnx("incomplete escape sequence");
+ rval = 1;
+ value = '\\';
+ --str;
+ break;
+
+ case '0': case '1': case '2': case '3':
+ case '4': case '5': case '6': case '7':
+ num_buf[0] = ch;
+ ch = str[0];
+ num_buf[1] = ch;
+ num_buf[2] = (char)(ch != '\0' ? str[1] : '\0');
+ num_buf[3] = '\0';
+ value = (char)strtoul(num_buf, &num_end, 8);
+ str += num_end - (num_buf + 1);
+ break;
+
+ case 'x':
+ /*
+ * Hexadecimal character constants are not required to be
+ * supported (by SuS v1) because there is no consistent
+ * way to detect the end of the constant.
+ * Supporting 2 byte constants is a compromise.
+ */
+ ch = str[0];
+ num_buf[0] = ch;
+ num_buf[1] = (char)(ch != '\0' ? str[1] : '\0');
+ num_buf[2] = '\0';
+ value = (char)strtoul(num_buf, &num_end, 16);
+ str += num_end - num_buf;
+ break;
+
+ case '\\': value = '\\'; break; /* backslash */
+ case '\'': value = '\''; break; /* single quote */
+ case '"': value = '"'; break; /* double quote */
+ case 'a': value = '\a'; break; /* alert */
+ case 'b': value = '\b'; break; /* backspace */
+ case 'e': value = ESCAPE; break; /* escape */
+ case 'E': value = ESCAPE; break; /* escape */
+ case 'f': value = '\f'; break; /* form-feed */
+ case 'n': value = '\n'; break; /* newline */
+ case 'r': value = '\r'; break; /* carriage-return */
+ case 't': value = '\t'; break; /* tab */
+ case 'v': value = '\v'; break; /* vertical-tab */
+
+ default:
+ if (!quiet)
+ warnx("unknown escape sequence `\\%c'", ch);
+ rval = 1;
+ value = ch;
+ break;
+ }
+
+ *conv_ch = value;
+ return str;
+}
+
+/* expand a string so that everything is printable */
+
+static char *
+conv_expand(const char *str)
+{
+ static char *conv_str;
+ char *cp;
+ char ch;
+
+ if (conv_str)
+ free(conv_str);
+ /* get a buffer that is definitely large enough.... */
+ conv_str = malloc(4 * strlen(str) + 1);
+ if (!conv_str)
+ return NULL;
+ cp = conv_str;
+
+ while ((ch = *(const char *)str++) != '\0') {
+ switch (ch) {
+ /* Use C escapes for expected control characters */
+ case '\\': ch = '\\'; break; /* backslash */
+ case '\'': ch = '\''; break; /* single quote */
+ case '"': ch = '"'; break; /* double quote */
+ case '\a': ch = 'a'; break; /* alert */
+ case '\b': ch = 'b'; break; /* backspace */
+ case ESCAPE: ch = 'e'; break; /* escape */
+ case '\f': ch = 'f'; break; /* form-feed */
+ case '\n': ch = 'n'; break; /* newline */
+ case '\r': ch = 'r'; break; /* carriage-return */
+ case '\t': ch = 't'; break; /* tab */
+ case '\v': ch = 'v'; break; /* vertical-tab */
+ default:
+ /* Copy anything printable */
+ if (isprint((unsigned char)ch)) {
+ *cp++ = ch;
+ continue;
+ }
+ /* Use vis(3) encodings for the rest */
+ *cp++ = '\\';
+ if (ch & 0200) {
+ *cp++ = 'M';
+ ch &= (char)~0200;
+ }
+ if (ch == 0177) {
+ *cp++ = '^';
+ *cp++ = '?';
+ continue;
+ }
+ if (ch < 040) {
+ *cp++ = '^';
+ *cp++ = ch | 0100;
+ continue;
+ }
+ *cp++ = '-';
+ *cp++ = ch;
+ continue;
+ }
+ *cp++ = '\\';
+ *cp++ = ch;
+ }
+
+ *cp = 0;
+ return conv_str;
+}
+
+static char *
+mklong(const char *str, char ch)
+{
+ static char copy[64];
+ size_t len;
+
+ len = strlen(str) + 2;
+ if (len > sizeof copy) {
+ warnx("format %s too complex", str);
+ len = 4;
+ }
+ (void)memmove(copy, str, len - 3);
+ copy[len - 3] = 'j';
+ copy[len - 2] = ch;
+ copy[len - 1] = '\0';
+ return copy;
+}
+
+static char
+getchr(void)
+{
+ if (!*gargv)
+ return 0;
+ return **gargv++;
+}
+
+static char *
+getstr(void)
+{
+ static char empty[] = "";
+ if (!*gargv)
+ return empty;
+ return *gargv++;
+}
+
+static int
+getwidth(void)
+{
+ unsigned long val;
+ char *s, *ep;
+
+ s = *gargv;
+ if (s == NULL)
+ return 0;
+ gargv++;
+
+ errno = 0;
+ val = strtoul(s, &ep, 0);
+ check_conversion(s, ep);
+
+ /* Arbitrarily 'restrict' field widths to 1Mbyte */
+ if (val > 1 << 20) {
+ warnx("%s: invalid field width", s);
+ return 0;
+ }
+
+ return (int)val;
+}
+
+static intmax_t
+getintmax(void)
+{
+ intmax_t val;
+ char *cp, *ep;
+
+ cp = *gargv;
+ if (cp == NULL)
+ return 0;
+ gargv++;
+
+ if (*cp == '\"' || *cp == '\'')
+ return *(cp + 1);
+
+ errno = 0;
+ val = strtoimax(cp, &ep, 0);
+ check_conversion(cp, ep);
+ return val;
+}
+
+static double
+getdouble(void)
+{
+ double val;
+ char *ep;
+
+ if (!*gargv)
+ return 0.0;
+
+ if (**gargv == '\"' || **gargv == '\'')
+ return (double) *((*gargv++)+1);
+
+ errno = 0;
+ val = strtod(*gargv, &ep);
+ check_conversion(*gargv++, ep);
+ return val;
+}
+
+static void
+check_conversion(const char *s, const char *ep)
+{
+ if (*ep) {
+ if (ep == s)
+ warnx("%s: expected numeric value", s);
+ else
+ warnx("%s: not completely converted", s);
+ rval = 1;
+ } else if (errno == ERANGE) {
+ warnx("%s: %s", s, strerror(ERANGE));
+ rval = 1;
+ }
+}
+
+static void
+usage(void)
+{
+ (void)fprintf(stderr, "Usage: %s format [arg ...]\n", getprogname());
+}
diff --git a/usr.bin/renice/renice.8 b/usr.bin/renice/renice.8
new file mode 100644
index 0000000..5dedc3e
--- /dev/null
+++ b/usr.bin/renice/renice.8
@@ -0,0 +1,151 @@
+.\" $NetBSD: renice.8,v 1.15 2012/12/06 07:52:12 wiz Exp $
+.\"
+.\" Copyright (c) 1983, 1991, 1993
+.\" The Regents of the University of California. All rights reserved.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\" notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must reproduce the above copyright
+.\" notice, this list of conditions and the following disclaimer in the
+.\" documentation and/or other materials provided with the distribution.
+.\" 3. Neither the name of the University nor the names of its contributors
+.\" may be used to endorse or promote products derived from this software
+.\" without specific prior written permission.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+.\" ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+.\" SUCH DAMAGE.
+.\"
+.\" from: @(#)renice.8 8.1 (Berkeley) 6/9/93
+.\"
+.Dd December 6, 2012
+.Dt RENICE 8
+.Os
+.Sh NAME
+.Nm renice
+.Nd alter priority of running processes
+.Sh SYNOPSIS
+.Nm
+.Ar priority
+.Oo
+.Op Fl p
+.Ar pid ...
+.Oc
+.Oo
+.Fl g
+.Ar pgrp ...
+.Oc
+.Oo
+.Fl u
+.Ar user ...
+.Oc
+.Nm
+.Fl n
+.Ar increment
+.Oo
+.Op Fl p
+.Ar pid ...
+.Oc
+.Oo
+.Fl g
+.Ar pgrp ...
+.Oc
+.Oo
+.Fl u
+.Ar user ...
+.Oc
+.Sh DESCRIPTION
+.Nm
+alters the
+scheduling priority of one or more running processes.
+The following
+.Ar who
+parameters are interpreted as process ID's, process group
+ID's, or user names.
+.Nm Ns 'ing
+a process group causes all processes in the process group
+to have their scheduling priority altered.
+.Nm Ns 'ing
+a user causes all processes owned by the user to have
+their scheduling priority altered.
+By default, the processes to be affected are specified by
+their process ID's.
+.Pp
+Options supported by
+.Nm :
+.Bl -tag -width Ds
+.It Fl g
+Force
+.Ar who
+parameters to be interpreted as process group ID's.
+.It Fl n
+Instead of changing the specified processes to the given priority,
+interpret the following argument as an increment to be applied to
+the current priority of each process.
+.It Fl u
+Force the
+.Ar who
+parameters to be interpreted as user names.
+.It Fl p
+Resets the
+.Ar who
+interpretation to be (the default) process ID's.
+.El
+.Pp
+For example,
+.Bd -literal -offset indent
+renice +1 987 -u daemon root -p 32
+.Ed
+.Pp
+would change the priority of process ID's 987 and 32, and
+all processes owned by users daemon and root.
+.Pp
+Users other than the super-user may only alter the priority of
+processes they own,
+and can only monotonically increase their ``nice value''
+within the range 0 to
+.Dv PRIO_MAX
+(20).
+(This prevents overriding administrative fiats.)
+The super-user
+may alter the priority of any process
+and set the priority to any value in the range
+.Dv PRIO_MIN
+(\-20)
+to
+.Dv PRIO_MAX .
+.Pp
+Useful priorities are:
+0, the ``base'' scheduling priority;
+20, the affected processes will run only when nothing at the base priority
+wants to;
+anything negative, the processes will receive a scheduling preference.
+.Sh FILES
+.Bl -tag -width /etc/passwd -compact
+.It Pa /etc/passwd
+to map user names to user ID's
+.El
+.Sh SEE ALSO
+.Xr nice 1 ,
+.Xr prenice 1 ,
+.Xr getpriority 2 ,
+.Xr setpriority 2
+.Sh HISTORY
+The
+.Nm
+command appeared in
+.Bx 4.0 .
+.Sh BUGS
+Non super-users can not increase scheduling priorities of their own processes,
+even if they were the ones that decreased the priorities in the first place.
diff --git a/usr.bin/renice/renice.c b/usr.bin/renice/renice.c
new file mode 100644
index 0000000..977f081
--- /dev/null
+++ b/usr.bin/renice/renice.c
@@ -0,0 +1,181 @@
+/* $NetBSD: renice.c,v 1.18 2008/07/21 14:19:25 lukem Exp $ */
+
+/*
+ * Copyright (c) 1983, 1989, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+#ifndef lint
+__COPYRIGHT("@(#) Copyright (c) 1983, 1989, 1993\
+ The Regents of the University of California. All rights reserved.");
+#endif /* not lint */
+
+#ifndef lint
+/*static char sccsid[] = "from: @(#)renice.c 8.1 (Berkeley) 6/9/93";*/
+__RCSID("$NetBSD: renice.c,v 1.18 2008/07/21 14:19:25 lukem Exp $");
+#endif /* not lint */
+
+#include <sys/resource.h>
+
+#include <err.h>
+#include <errno.h>
+#include <limits.h>
+#include <pwd.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sysexits.h>
+
+static int getnum(const char *, const char *, int *);
+static int donice(int, id_t, int, int);
+static void usage(void) __dead;
+
+/*
+ * Change the priority (nice) of processes
+ * or groups of processes which are already
+ * running.
+ */
+int
+main(int argc, char **argv)
+{
+ int which = PRIO_PROCESS;
+ int prio, errs = 0, incr = 0;
+ id_t who = 0;
+
+ argc--, argv++;
+ if (argc < 2)
+ usage();
+ if (strcmp(*argv, "-n") == 0) {
+ incr = 1;
+ argc--, argv++;
+ if (argc == 0)
+ usage();
+ }
+ if (getnum("priority", *argv, &prio))
+ return 1;
+ argc--, argv++;
+ for (; argc > 0; argc--, argv++) {
+ if (strcmp(*argv, "-g") == 0) {
+ which = PRIO_PGRP;
+ continue;
+ }
+ if (strcmp(*argv, "-u") == 0) {
+ which = PRIO_USER;
+ continue;
+ }
+ if (strcmp(*argv, "-p") == 0) {
+ which = PRIO_PROCESS;
+ continue;
+ }
+ if (which == PRIO_USER) {
+ struct passwd *pwd = getpwnam(*argv);
+
+ if (pwd == NULL) {
+ warnx("%s: unknown user", *argv);
+ errs++;
+ continue;
+ }
+ who = (id_t)pwd->pw_uid;
+ } else {
+ int twho;
+ if (getnum("pid", *argv, &twho)) {
+ errs++;
+ continue;
+ }
+ if (twho < 0) {
+ warnx("%s: bad value", *argv);
+ errs++;
+ continue;
+ }
+ who = (id_t)twho;
+ }
+ errs += donice(which, who, prio, incr);
+ }
+ return errs == 0 ? EXIT_SUCCESS : EXIT_FAILURE;
+}
+
+static int
+getnum(const char *com, const char *str, int *val)
+{
+ long v;
+ char *ep;
+
+ errno = 0;
+ v = strtol(str, &ep, 0);
+
+ if (*ep) {
+ warnx("Bad %s argument: %s", com, str);
+ return 1;
+ }
+ if ((v == LONG_MIN || v == LONG_MAX) && errno == ERANGE) {
+ warn("Invalid %s argument: %s", com, str);
+ return 1;
+ }
+
+ *val = (int)v;
+ return 0;
+}
+
+static int
+donice(int which, id_t who, int prio, int incr)
+{
+ int oldprio;
+
+ errno = 0;
+ if ((oldprio = getpriority(which, who)) == -1 && errno != 0) {
+ warn("%d: getpriority", who);
+ return 1;
+ }
+
+ if (incr)
+ prio = oldprio + prio;
+
+ if (prio > PRIO_MAX)
+ prio = PRIO_MAX;
+ if (prio < PRIO_MIN)
+ prio = PRIO_MIN;
+
+ if (setpriority(which, who, prio) == -1) {
+ warn("%d: setpriority", who);
+ return 1;
+ }
+ (void)printf("%d: old priority %d, new priority %d\n",
+ who, oldprio, prio);
+ return 0;
+}
+
+static void
+usage(void)
+{
+
+ (void)fprintf(stderr, "Usage: %s [<priority> | -n <incr>] ",
+ getprogname());
+ (void)fprintf(stderr, "[[-p] <pids>...] [-g <pgrp>...] ");
+ (void)fprintf(stderr, "[-u <user>...]\n");
+ exit(1);
+}
diff --git a/usr.bin/sed/POSIX b/usr.bin/sed/POSIX
new file mode 100644
index 0000000..cbacd89
--- /dev/null
+++ b/usr.bin/sed/POSIX
@@ -0,0 +1,205 @@
+# $NetBSD: POSIX,v 1.5 2014/06/06 00:13:13 christos Exp $
+# @(#)POSIX 8.1 (Berkeley) 6/6/93
+# $FreeBSD: head/usr.bin/sed/POSIX 168417 2007-04-06 08:43:30Z yar $
+
+Comments on the IEEE P1003.2 Draft 12
+ Part 2: Shell and Utilities
+ Section 4.55: sed - Stream editor
+
+Diomidis Spinellis <dds@doc.ic.ac.uk>
+Keith Bostic <bostic@cs.berkeley.edu>
+
+In the following paragraphs, "wrong" usually means "inconsistent with
+historic practice", as most of the following comments refer to
+undocumented inconsistencies between the historical versions of sed and
+the POSIX 1003.2 standard. All the comments are notes taken while
+implementing a POSIX-compatible version of sed, and should not be
+interpreted as official opinions or criticism towards the POSIX committee.
+All uses of "POSIX" refer to section 4.55, Draft 12 of POSIX 1003.2.
+
+ 1. 32V and BSD derived implementations of sed strip the text
+ arguments of the a, c and i commands of their initial blanks,
+ i.e.
+
+ #!/bin/sed -f
+ a\
+ foo\
+ \ indent\
+ bar
+
+ produces:
+
+ foo
+ indent
+ bar
+
+ POSIX does not specify this behavior as the System V versions of
+ sed do not do this stripping. The argument against stripping is
+ that it is difficult to write sed scripts that have leading blanks
+ if they are stripped. The argument for stripping is that it is
+ difficult to write readable sed scripts unless indentation is allowed
+ and ignored, and leading whitespace is obtainable by entering a
+ backslash in front of it. This implementation follows the BSD
+ historic practice.
+
+ 2. Historical versions of sed required that the w flag be the last
+ flag to an s command as it takes an additional argument. This
+ is obvious, but not specified in POSIX.
+
+ 3. Historical versions of sed required that whitespace follow a w
+ flag to an s command. This is not specified in POSIX. This
+ implementation permits whitespace but does not require it.
+
+ 4. Historical versions of sed permitted any number of whitespace
+ characters to follow the w command. This is not specified in
+ POSIX. This implementation permits whitespace but does not
+ require it.
+
+ 5. The rule for the l command differs from historic practice. Table
+ 2-15 includes the various ANSI C escape sequences, including \\
+ for backslash. Some historical versions of sed displayed two
+ digit octal numbers, too, not three as specified by POSIX. POSIX
+ is a cleanup, and is followed by this implementation.
+
+ 6. The POSIX specification for ! does not specify that for a single
+ command the command must not contain an address specification
+ whereas the command list can contain address specifications. The
+ specification for ! implies that "3!/hello/p" works, and it never
+ has, historically. Note,
+
+ 3!{
+ /hello/p
+ }
+
+ does work.
+
+ 7. POSIX does not specify what happens with consecutive ! commands
+ (e.g. /foo/!!!p). Historic implementations allow any number of
+ !'s without changing the behaviour. (It seems logical that each
+ one might reverse the behaviour.) This implementation follows
+ historic practice.
+
+ 8. Historic versions of sed permitted commands to be separated
+ by semi-colons, e.g. 'sed -ne '1p;2p;3q' printed the first
+ three lines of a file. This is not specified by POSIX.
+ Note, the ; command separator is not allowed for the commands
+ a, c, i, w, r, :, b, t, # and at the end of a w flag in the s
+ command. This implementation follows historic practice and
+ implements the ; separator.
+
+ 9. Historic versions of sed terminated the script if EOF was reached
+ during the execution of the 'n' command, i.e.:
+
+ sed -e '
+ n
+ i\
+ hello
+ ' </dev/null
+
+ did not produce any output. POSIX does not specify this behavior.
+ This implementation follows historic practice.
+
+10. Deleted.
+
+11. Historical implementations do not output the change text of a c
+ command in the case of an address range whose first line number
+ is greater than the second (e.g. 3,1). POSIX requires that the
+ text be output. Since the historic behavior doesn't seem to have
+ any particular purpose, this implementation follows the POSIX
+ behavior.
+
+12. POSIX does not specify whether address ranges are checked and
+ reset if a command is not executed due to a jump. The following
+ program will behave in different ways depending on whether the
+ 'c' command is triggered at the third line, i.e. will the text
+ be output even though line 3 of the input will never logically
+ encounter that command.
+
+ 2,4b
+ 1,3c\
+ text
+
+ Historic implementations did not output the text in the above
+ example. Therefore it was believed that a range whose second
+ address was never matched extended to the end of the input.
+ However, the current practice adopted by this implementation,
+ as well as by those from GNU and SUN, is as follows: The text
+ from the 'c' command still isn't output because the second address
+ isn't actually matched; but the range is reset after all if its
+ second address is a line number. In the above example, only the
+ first line of the input will be deleted.
+
+13. Historical implementations allow an output suppressing #n at the
+ beginning of -e arguments as well as in a script file. POSIX
+ does not specify this. This implementation follows historical
+ practice.
+
+14. POSIX does not explicitly specify how sed behaves if no script is
+ specified. Since the sed Synopsis permits this form of the command,
+ and the language in the Description section states that the input
+ is output, it seems reasonable that it behave like the cat(1)
+ command. Historic sed implementations behave differently for "ls |
+ sed", where they produce no output, and "ls | sed -e#", where they
+ behave like cat. This implementation behaves like cat in both cases.
+
+15. The POSIX requirement to open all w files at the beginning makes
+ sed behave nonintuitively when the w commands are preceded by
+ addresses or are within conditional blocks. This implementation
+ follows historic practice and POSIX, by default, and provides the
+ -a option which opens the files only when they are needed.
+
+16. POSIX does not specify how escape sequences other than \n and \D
+ (where D is the delimiter character) are to be treated. This is
+ reasonable, however, it also doesn't state that the backslash is
+ to be discarded from the output regardless. A strict reading of
+ POSIX would be that "echo xyz | sed s/./\a" would display "\ayz".
+ As historic sed implementations always discarded the backslash,
+ this implementation does as well.
+
+17. POSIX specifies that an address can be "empty". This implies
+ that constructs like ",d" or "1,d" and ",5d" are allowed. This
+ is not true for historic implementations or this implementation
+ of sed.
+
+18. The b t and : commands are documented in POSIX to ignore leading
+ white space, but no mention is made of trailing white space.
+ Historic implementations of sed assigned different locations to
+ the labels "x" and "x ". This is not useful, and leads to subtle
+ programming errors, but it is historic practice and changing it
+ could theoretically break working scripts. This implementation
+ follows historic practice.
+
+19. Although POSIX specifies that reading from files that do not exist
+ from within the script must not terminate the script, it does not
+ specify what happens if a write command fails. Historic practice
+ is to fail immediately if the file cannot be opened or written.
+ This implementation follows historic practice.
+
+20. Historic practice is that the \n construct can be used for either
+ string1 or string2 of the y command. This is not specified by
+ POSIX. This implementation follows historic practice.
+
+21. Deleted.
+
+22. Historic implementations of sed ignore the RE delimiter characters
+ within character classes. This is not specified in POSIX. This
+ implementation follows historic practice.
+
+23. Historic implementations handle empty RE's in a special way: the
+ empty RE is interpreted as if it were the last RE encountered,
+ whether in an address or elsewhere. POSIX does not document this
+ behavior. For example the command:
+
+ sed -e /abc/s//XXX/
+
+ substitutes XXX for the pattern abc. The semantics of "the last
+ RE" can be defined in two different ways:
+
+ 1. The last RE encountered when compiling (lexical/static scope).
+ 2. The last RE encountered while running (dynamic scope).
+
+ While many historical implementations fail on programs depending
+ on scope differences, the SunOS version exhibited dynamic scope
+ behaviour. This implementation does dynamic scoping, as this seems
+ the most useful and in order to remain consistent with historical
+ practice.
diff --git a/usr.bin/sed/TEST/hanoi.sed b/usr.bin/sed/TEST/hanoi.sed
new file mode 100644
index 0000000..e58a967
--- /dev/null
+++ b/usr.bin/sed/TEST/hanoi.sed
@@ -0,0 +1,103 @@
+# Towers of Hanoi in sed.
+#
+# from: @(#)hanoi.sed 8.1 (Berkeley) 6/6/93
+# $NetBSD: hanoi.sed,v 1.3 1997/01/09 20:21:35 tls Exp $
+#
+#
+# Ex:
+# Run "sed -f hanoi.sed", and enter:
+#
+# :abcd: : :<CR><CR>
+#
+# note -- TWO carriage returns, a peculiarity of sed), this will output the
+# sequence of states involved in moving 4 rings, the largest called "a" and
+# the smallest called "d", from the first to the second of three towers, so
+# that the rings on any tower at any time are in descending order of size.
+# You can start with a different arrangement and a different number of rings,
+# say :ce:b:ax: and it will give the shortest procedure for moving them all
+# to the middle tower. The rules are: the names of the rings must all be
+# lower-case letters, they must be input within 3 fields (representing the
+# towers) and delimited by 4 colons, such that the letters within each field
+# are in alphabetical order (i.e. rings are in descending order of size).
+#
+# For the benefit of anyone who wants to figure out the script, an "internal"
+# line of the form
+# b:0abx:1a2b3 :2 :3x2
+# has the following meaning: the material after the three markers :1, :2,
+# and :3 represents the three towers; in this case the current set-up is
+# ":ab : :x :". The numbers after a, b and x in these fields indicate
+# that the next time it gets a chance, it will move a to tower 2, move b
+# to tower 3, and move x to tower 2. The string after :0 just keeps track
+# of the alphabetical order of the names of the rings. The b at the
+# beginning means that it is now dealing with ring b (either about to move
+# it, or re-evaluating where it should next be moved to).
+#
+# Although this version is "limited" to 26 rings because of the size of the
+# alphabet, one could write a script using the same idea in which the rings
+# were represented by arbitrary [strings][within][brackets], and in place of
+# the built-in line of the script giving the order of the letters of the
+# alphabet, it would accept from the user a line giving the ordering to be
+# assumed, e.g. [ucbvax][decvax][hplabs][foo][bar].
+#
+# George Bergman
+# Math, UC Berkeley 94720 USA
+
+# cleaning, diagnostics
+s/ *//g
+/^$/d
+/[^a-z:]/{a\
+Illegal characters: use only a-z and ":". Try again.
+d
+}
+/^:[a-z]*:[a-z]*:[a-z]*:$/!{a\
+Incorrect format: use\
+\ : string1 : string2 : string3 :<CR><CR>\
+Try again.
+d
+}
+/\([a-z]\).*\1/{a\
+Repeated letters not allowed. Try again.
+d
+}
+# initial formatting
+h
+s/[a-z]/ /g
+G
+s/^:\( *\):\( *\):\( *\):\n:\([a-z]*\):\([a-z]*\):\([a-z]*\):$/:1\4\2\3:2\5\1\3:3\6\1\2:0/
+s/[a-z]/&2/g
+s/^/abcdefghijklmnopqrstuvwxyz/
+:a
+s/^\(.\).*\1.*/&\1/
+s/.//
+/^[^:]/ba
+s/\([^0]*\)\(:0.*\)/\2\1:/
+s/^[^0]*0\(.\)/\1&/
+:b
+# outputting current state without markers
+h
+s/.*:1/:/
+s/[123]//gp
+g
+:c
+# establishing destinations
+/^\(.\).*\1:1/td
+/^\(.\).*:1[^:]*\11/s/^\(.\)\(.*\1\([a-z]\).*\)\3./\3\2\31/
+/^\(.\).*:1[^:]*\12/s/^\(.\)\(.*\1\([a-z]\).*\)\3./\3\2\33/
+/^\(.\).*:1[^:]*\13/s/^\(.\)\(.*\1\([a-z]\).*\)\3./\3\2\32/
+/^\(.\).*:2[^:]*\11/s/^\(.\)\(.*\1\([a-z]\).*\)\3./\3\2\33/
+/^\(.\).*:2[^:]*\12/s/^\(.\)\(.*\1\([a-z]\).*\)\3./\3\2\32/
+/^\(.\).*:2[^:]*\13/s/^\(.\)\(.*\1\([a-z]\).*\)\3./\3\2\31/
+/^\(.\).*:3[^:]*\11/s/^\(.\)\(.*\1\([a-z]\).*\)\3./\3\2\32/
+/^\(.\).*:3[^:]*\12/s/^\(.\)\(.*\1\([a-z]\).*\)\3./\3\2\31/
+/^\(.\).*:3[^:]*\13/s/^\(.\)\(.*\1\([a-z]\).*\)\3./\3\2\33/
+bc
+# iterate back to find smallest out-of-place ring
+:d
+s/^\(.\)\(:0[^:]*\([^:]\)\1.*:\([123]\)[^:]*\1\)\4/\3\2\4/
+td
+# move said ring (right, resp. left)
+s/^\(.\)\(.*\)\1\([23]\)\(.*:\3[^ ]*\) /\1\2 \4\1\3/
+s/^\(.\)\(.*:\([12]\)[^ ]*\) \(.*\)\1\3/\1\2\1\3\4 /
+tb
+s/.*/Done! Try another, or end with ^D./p
+d
diff --git a/usr.bin/sed/TEST/math.sed b/usr.bin/sed/TEST/math.sed
new file mode 100644
index 0000000..82968b5
--- /dev/null
+++ b/usr.bin/sed/TEST/math.sed
@@ -0,0 +1,164 @@
+#
+# from: @(#)math.sed 8.1 (Berkeley) 6/6/93
+# $NetBSD: math.sed,v 1.3 1997/01/09 20:21:36 tls Exp $
+#
+# Addition and multiplication in sed.
+# ++ for a limited time only do (expr) too!!!
+#
+# Kevin S Braunsdorf, PUCC UNIX Group, ksb@cc.purdue.edu.
+#
+# Ex:
+# echo "4+7*3" | sed -f %f
+
+# make sure the expression is well formed
+s/[ ]//g
+/[+*\/-]$/{
+ a\
+ poorly formed expression, operator on the end
+ q
+}
+/^[+*\/]/{
+ a\
+ poorly formed expression, leading operator
+ q
+}
+
+# fill hold space with done token
+x
+s/^.*/done/
+x
+
+# main loop, process operators (*, + and () )
+: loop
+/^\+/{
+ s///
+ b loop
+}
+/^\(.*\)(\([^)]*\))\(.*\)$/{
+ H
+ s//\2/
+ x
+ s/^\(.*\)\n\(.*\)(\([^()]*\))\(.*\)$/()\2@\4@\1/
+ x
+ b loop
+}
+/^[0-9]*\*/b mul
+/^\([0-9]*\)\+\([0-9+*]*\*[0-9]*\)$/{
+ s//\2+\1/
+ b loop
+}
+/^[0-9]*\+/{
+ s/$/=/
+ b add
+}
+x
+/^done$/{
+ x
+ p
+ d
+}
+/^()/{
+ s///
+ x
+ G
+ s/\(.*\)\n\([^@]*\)@\([^@]*\)@\(.*\)/\2\1\3/
+ x
+ s/[^@]*@[^@]*@\(.*\)/\1/
+ x
+ b loop
+}
+i\
+help, stack problem
+p
+x
+p
+q
+
+# turn mul into add until 1*x -> x
+: mul
+/^0*1\*/{
+ s///
+ b loop
+}
+/^\([0-9]*\)0\*/{
+ s/^\([0-9]*\)0\*\([0-9]*\)/\1*\20/
+ b mul
+}
+s/^\([0-9]*\)1\*/\10*/
+s/^\([0-9]*\)2\*/\11*/
+s/^\([0-9]*\)3\*/\12*/
+s/^\([0-9]*\)4\*/\13*/
+s/^\([0-9]*\)5\*/\14*/
+s/^\([0-9]*\)6\*/\15*/
+s/^\([0-9]*\)7\*/\16*/
+s/^\([0-9]*\)8\*/\17*/
+s/^\([0-9]*\)9\*/\18*/
+s/\*\([0-9*]*\)/*\1+\1/
+b mul
+
+# get rid of a plus term until 0+x -> x
+: add
+/^\+\([0-9+*]*\)=/{
+ s//\1/
+ b loop
+}
+/^\([0-9*]*\)\+=/{
+ s//\1/
+ b loop
+}
+/^\([0-9]*\)\+\([0-9*+]*\)\+=/{
+ s//\2+\1/
+ b loop
+}
+/^\([0-9]*\)0\+\([0-9]*\)\([0-9]\)=/{
+ s//\1+\2=\3/
+ b add
+}
+/^\([0-9]*\)\([0-9]\)\+\([0-9]*\)0=/{
+ s//\1+\3=\2/
+ b add
+}
+/^\([0-9]*\)0\+\([0-9*+]*\)\+\([0-9]*\)\([0-9]\)=/{
+ s//\1+\2+\3=\4/
+ b add
+}
+/^\([0-9]*\)\([0-9]\)\+\([0-9*+]*\)\+\([0-9]*\)0=/{
+ s//\1+\3+\4=\2/
+ b add
+}
+s/^\([0-9]*\)1\+/\10+/
+s/^\([0-9]*\)2\+/\11+/
+s/^\([0-9]*\)3\+/\12+/
+s/^\([0-9]*\)4\+/\13+/
+s/^\([0-9]*\)5\+/\14+/
+s/^\([0-9]*\)6\+/\15+/
+s/^\([0-9]*\)7\+/\16+/
+s/^\([0-9]*\)8\+/\17+/
+s/^\([0-9]*\)9\+/\18+/
+
+s/9=\([0-9]*\)$/_=\1/
+s/8=\([0-9]*\)$/9=\1/
+s/7=\([0-9]*\)$/8=\1/
+s/6=\([0-9]*\)$/7=\1/
+s/5=\([0-9]*\)$/6=\1/
+s/4=\([0-9]*\)$/5=\1/
+s/3=\([0-9]*\)$/4=\1/
+s/2=\([0-9]*\)$/3=\1/
+s/1=\([0-9]*\)$/2=\1/
+/_/{
+ s//_0/
+ : inc
+ s/9_/_0/
+ s/8_/9/
+ s/7_/8/
+ s/6_/7/
+ s/5_/6/
+ s/4_/5/
+ s/3_/4/
+ s/2_/3/
+ s/1_/2/
+ s/0_/1/
+ s/\+_/+1/
+ /_/b inc
+}
+b add
diff --git a/usr.bin/sed/TEST/sed.test b/usr.bin/sed/TEST/sed.test
new file mode 100644
index 0000000..ff4093a
--- /dev/null
+++ b/usr.bin/sed/TEST/sed.test
@@ -0,0 +1,554 @@
+#!/bin/sh -
+# $NetBSD: sed.test,v 1.5 2011/11/12 03:15:05 christos Exp $
+#
+# Copyright (c) 1992 Diomidis Spinellis.
+# Copyright (c) 1992, 1993
+# The Regents of the University of California. All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1. Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+# 3. All advertising materials mentioning features or use of this software
+# must display the following acknowledgement:
+# This product includes software developed by the University of
+# California, Berkeley and its contributors.
+# 4. Neither the name of the University nor the names of its contributors
+# may be used to endorse or promote products derived from this software
+# without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+# ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+# SUCH DAMAGE.
+#
+# from: @(#)sed.test 8.1 (Berkeley) 6/6/93
+# $NetBSD: sed.test,v 1.5 2011/11/12 03:15:05 christos Exp $
+#
+
+# sed Regression Tests
+#
+# The following files are created:
+# lines[1-4], script1, script2
+# Two directories *.out contain the test results
+
+main()
+{
+ BASE=/usr/bin/sed
+ BASELOG=sed.out
+ TEST=$(cd $(dirname $0)/.. && make -V .OBJDIR)/sed
+ TESTLOG=nsed.out
+ DICT=/usr/share/dict/words
+
+ test_error | more
+
+ awk 'END { for (i = 1; i < 15; i++) print "l1_" i}' </dev/null >lines1
+ awk 'END { for (i = 1; i < 10; i++) print "l2_" i}' </dev/null >lines2
+
+ exec 4>&1 5>&2
+
+ # Set these flags to get messages about known problems
+ BSD=1
+ GNU=0
+ SUN=0
+ tests $BASE $BASELOG
+
+ BSD=0
+ GNU=0
+ SUN=0
+ tests $TEST $TESTLOG
+ exec 1>&4 2>&5
+ diff -c $BASELOG $TESTLOG | more
+}
+
+tests()
+{
+ SED=$1
+ DIR=$2
+ rm -rf $DIR
+ mkdir $DIR
+ MARK=100
+
+ test_args
+ test_addr
+ echo Testing commands
+ test_group
+ test_acid
+ test_branch
+ test_pattern
+ test_print
+ test_subst
+}
+
+mark()
+{
+ MARK=$(expr $MARK + 1)
+ exec 1>&4 2>&5
+ exec >"$DIR/${MARK}_$1"
+ echo "Test $1:$MARK"
+ # Uncomment this line to match tests with sed error messages
+ echo "Test $1:$MARK" >&5
+}
+
+test_args()
+{
+ mark '1.1'
+ echo Testing argument parsing
+ echo First type
+ if [ $SUN -eq 1 ] ; then
+ echo SunOS sed prints only with -n
+ else
+ $SED 's/^/e1_/p' lines1
+ fi
+ mark '1.2' ; $SED -n 's/^/e1_/p' lines1
+ mark '1.3'
+ if [ $SUN -eq 1 ] ; then
+ echo SunOS sed prints only with -n
+ else
+ $SED 's/^/e1_/p' <lines1
+ fi
+ mark '1.4' ; $SED -n 's/^/e1_/p' <lines1
+ echo Second type
+ mark '1.4.1'
+ if [ $SUN -eq 1 ] ; then
+ echo SunOS sed fails this
+ fi
+ $SED -e '' <lines1
+ echo 's/^/s1_/p' >script1
+ echo 's/^/s2_/p' >script2
+ mark '1.5'
+ if [ $SUN -eq 1 ] ; then
+ echo SunOS sed prints only with -n
+ else
+ $SED -f script1 lines1
+ fi
+ mark '1.6'
+ if [ $SUN -eq 1 ] ; then
+ echo SunOS sed prints only with -n
+ else
+ $SED -f script1 <lines1
+ fi
+ mark '1.7'
+ if [ $SUN -eq 1 ] ; then
+ echo SunOS sed prints only with -n
+ else
+ $SED -e 's/^/e1_/p' lines1
+ fi
+ mark '1.8'
+ if [ $SUN -eq 1 ] ; then
+ echo SunOS sed prints only with -n
+ else
+ $SED -e 's/^/e1_/p' <lines1
+ fi
+ mark '1.9' ; $SED -n -f script1 lines1
+ mark '1.10' ; $SED -n -f script1 <lines1
+ mark '1.11' ; $SED -n -e 's/^/e1_/p' lines1
+ mark '1.12'
+ if [ $SUN -eq 1 ] ; then
+ echo SunOS sed prints only with -n
+ else
+ $SED -n -e 's/^/e1_/p' <lines1
+ fi
+ mark '1.13'
+ if [ $SUN -eq 1 ] ; then
+ echo SunOS sed prints only with -n
+ else
+ $SED -e 's/^/e1_/p' -e 's/^/e2_/p' lines1
+ fi
+ mark '1.14'
+ if [ $SUN -eq 1 ] ; then
+ echo SunOS sed prints only with -n
+ else
+ $SED -f script1 -f script2 lines1
+ fi
+ mark '1.15'
+ if [ $GNU -eq 1 -o $SUN -eq 1 ] ; then
+ echo GNU and SunOS sed fail this following older POSIX draft
+ else
+ $SED -e 's/^/e1_/p' -f script1 lines1
+ fi
+ mark '1.16'
+ if [ $SUN -eq 1 ] ; then
+ echo SunOS sed prints only with -n
+ else
+ $SED -e 's/^/e1_/p' lines1 lines1
+ fi
+ # POSIX D11.2:11251
+ mark '1.17' ; $SED p <lines1 lines1
+cat >script1 <<EOF
+#n
+# A comment
+
+p
+EOF
+ mark '1.18' ; $SED -f script1 <lines1 lines1
+}
+
+test_addr()
+{
+ echo Testing address ranges
+ mark '2.1' ; $SED -n -e '4p' lines1
+ mark '2.2' ; $SED -n -e '20p' lines1 lines2
+ mark '2.3' ; $SED -n -e '$p' lines1
+ mark '2.4' ; $SED -n -e '$p' lines1 lines2
+ mark '2.5' ; $SED -n -e '$a\
+hello' /dev/null
+ mark '2.6' ; $SED -n -e '$p' lines1 /dev/null lines2
+ # Should not print anything
+ mark '2.7' ; $SED -n -e '20p' lines1
+ mark '2.8' ; $SED -n -e '0p' lines1
+ mark '2.9' ; $SED -n '/l1_7/p' lines1
+ mark '2.10' ; $SED -n ' /l1_7/ p' lines1
+ mark '2.11'
+ if [ $BSD -eq 1 ] ; then
+ echo BSD sed fails this test
+ fi
+ if [ $GNU -eq 1 ] ; then
+ echo GNU sed fails this
+ fi
+ $SED -n '\_l1\_7_p' lines1
+ mark '2.12' ; $SED -n '1,4p' lines1
+ mark '2.13' ; $SED -n '1,$p' lines1 lines2
+ mark '2.14' ; $SED -n '1,/l2_9/p' lines1 lines2
+ mark '2.15' ; $SED -n '/4/,$p' lines1 lines2
+ mark '2.16' ; $SED -n '/4/,20p' lines1 lines2
+ mark '2.17' ; $SED -n '/4/,/10/p' lines1 lines2
+ mark '2.18' ; $SED -n '/l2_3/,/l1_8/p' lines1 lines2
+ mark '2.19'
+ if [ $GNU -eq 1 ] ; then
+ echo GNU sed fails this
+ fi
+ $SED -n '12,3p' lines1 lines2
+ mark '2.20'
+ if [ $GNU -eq 1 ] ; then
+ echo GNU sed fails this
+ fi
+ $SED -n '/l1_7/,3p' lines1 lines2
+}
+
+test_group()
+{
+ echo Brace and other grouping
+ mark '3.1' ; $SED -e '
+4,12 {
+ s/^/^/
+ s/$/$/
+ s/_/T/
+}' lines1
+ mark '3.2' ; $SED -e '
+4,12 {
+ s/^/^/
+ /6/,/10/ {
+ s/$/$/
+ /8/ s/_/T/
+ }
+}' lines1
+ mark '3.3' ; $SED -e '
+4,12 !{
+ s/^/^/
+ /6/,/10/ !{
+ s/$/$/
+ /8/ !s/_/T/
+ }
+}' lines1
+ mark '3.4' ; $SED -e '4,12!s/^/^/' lines1
+}
+
+test_acid()
+{
+ echo Testing a c d and i commands
+ mark '4.1' ; $SED -n -e '
+s/^/before_i/p
+20i\
+inserted
+s/^/after_i/p
+' lines1 lines2
+ mark '4.2' ; $SED -n -e '
+5,12s/^/5-12/
+s/^/before_a/p
+/5-12/a\
+appended
+s/^/after_a/p
+' lines1 lines2
+ mark '4.3'
+ if [ $GNU -eq 1 ] ; then
+ echo GNU sed fails this
+ fi
+ $SED -n -e '
+s/^/^/p
+/l1_/a\
+appended
+8,10N
+s/$/$/p
+' lines1 lines2
+ mark '4.4' ; $SED -n -e '
+c\
+hello
+' lines1
+ mark '4.5' ; $SED -n -e '
+8c\
+hello
+' lines1
+ mark '4.6' ; $SED -n -e '
+3,14c\
+hello
+' lines1
+# SunOS and GNU sed behave differently. We follow POSIX
+# mark '4.7' ; $SED -n -e '
+#8,3c\
+#hello
+#' lines1
+ mark '4.8' ; $SED d <lines1
+}
+
+test_branch()
+{
+ echo Testing labels and branching
+ mark '5.1' ; $SED -n -e '
+b label4
+:label3
+s/^/label3_/p
+b end
+:label4
+2,12b label1
+b label2
+:label1
+s/^/label1_/p
+b
+:label2
+s/^/label2_/p
+b label3
+:end
+' lines1
+ mark '5.2'
+ if [ $BSD -eq 1 ] ; then
+ echo BSD sed fails this test
+ fi
+ $SED -n -e '
+s/l1_/l2_/
+t ok
+b
+:ok
+s/^/tested /p
+' lines1 lines2
+# SunOS sed behaves differently here. Clarification needed.
+# mark '5.3' ; $SED -n -e '
+#5,8b inside
+#1,5 {
+# s/^/^/p
+# :inside
+# s/$/$/p
+#}
+#' lines1
+# Check that t clears the substitution done flag
+ mark '5.4' ; $SED -n -e '
+1,8s/^/^/
+t l1
+:l1
+t l2
+s/$/$/p
+b
+:l2
+s/^/ERROR/
+' lines1
+# Check that reading a line clears the substitution done flag
+ mark '5.5'
+ if [ $BSD -eq 1 ] ; then
+ echo BSD sed fails this test
+ fi
+ $SED -n -e '
+t l2
+1,8s/^/^/p
+2,7N
+b
+:l2
+s/^/ERROR/p
+' lines1
+ mark '5.6' ; $SED 5q lines1
+ mark '5.7' ; $SED -e '
+5i\
+hello
+5q' lines1
+# Branch across block boundary
+ mark '5.8' ; $SED -e '
+{
+:b
+}
+s/l/m/
+tb' lines1
+}
+
+test_pattern()
+{
+echo Pattern space commands
+# Check that the pattern space is deleted
+ mark '6.1' ; $SED -n -e '
+c\
+changed
+p
+' lines1
+ mark '6.2' ; $SED -n -e '
+4d
+p
+' lines1
+# SunOS sed refused to print here
+# mark '6.3' ; $SED -e '
+#N
+#N
+#N
+#D
+#P
+#4p
+#' lines1
+ mark '6.4' ; $SED -e '
+2h
+3H
+4g
+5G
+6x
+6p
+6x
+6p
+' lines1
+ mark '6.5' ; $SED -e '4n' lines1
+ mark '6.6' ; $SED -n -e '4n' lines1
+}
+
+test_print()
+{
+ echo Testing print and file routines
+ awk 'END {for (i = 1; i < 256; i++) printf("%c", i);print "\n"}' \
+ </dev/null >lines3
+ # GNU and SunOS sed behave differently here
+ mark '7.1'
+ if [ $BSD -eq 1 ] ; then
+ echo 'BSD sed drops core on this one; TEST SKIPPED'
+ else
+ $SED -n l lines3
+ fi
+ mark '7.2' ; $SED -e '/l2_/=' lines1 lines2
+ rm -f lines4
+ mark '7.3' ; $SED -e '3,12w lines4' lines1
+ echo w results
+ cat lines4
+ mark '7.4' ; $SED -e '4r lines2' lines1
+ mark '7.5' ; $SED -e '5r /dev/dds' lines1
+ mark '7.6' ; $SED -e '6r /dev/null' lines1
+ mark '7.7'
+ if [ $BSD -eq 1 -o $GNU -eq 1 -o $SUN -eq 1 ] ; then
+ echo BSD, GNU and SunOS cannot pass this one
+ else
+ sed '200q' $DICT | sed 's$.*$s/^/&/w tmpdir/&$' >script1
+ rm -rf tmpdir
+ mkdir tmpdir
+ $SED -f script1 lines1
+ cat tmpdir/*
+ rm -rf tmpdir
+ fi
+ mark '7.8'
+ if [ $BSD -eq 1 ] ; then
+ echo BSD sed cannot pass 7.7
+ else
+ echo line1 > lines3
+ echo "" >> lines3
+ $SED -n -e '$p' lines3 /dev/null
+ fi
+
+}
+
+test_subst()
+{
+ echo Testing substitution commands
+ mark '8.1' ; $SED -e 's/./X/g' lines1
+ mark '8.2' ; $SED -e 's,.,X,g' lines1
+# GNU and SunOS sed thinks we are escaping . as wildcard, not as separator
+# mark '8.3' ; $SED -e 's.\..X.g' lines1
+# POSIX does not say that this should work
+# mark '8.4' ; $SED -e 's/[/]/Q/' lines1
+ mark '8.4' ; $SED -e 's/[\/]/Q/' lines1
+ mark '8.5' ; $SED -e 's_\__X_' lines1
+ mark '8.6' ; $SED -e 's/./(&)/g' lines1
+ mark '8.7' ; $SED -e 's/./(\&)/g' lines1
+ mark '8.8' ; $SED -e 's/\(.\)\(.\)\(.\)/x\3x\2x\1/g' lines1
+ mark '8.9' ; $SED -e 's/_/u0\
+u1\
+u2/g' lines1
+ mark '8.10'
+ if [ $BSD -eq 1 -o $GNU -eq 1 ] ; then
+ echo 'BSD/GNU sed do not understand digit flags on s commands'
+ fi
+ $SED -e 's/./X/4' lines1
+ rm -f lines4
+ mark '8.11' ; $SED -e 's/1/X/w lines4' lines1
+ echo s wfile results
+ cat lines4
+ mark '8.12' ; $SED -e 's/[123]/X/g' lines1
+ mark '8.13' ; $SED -e 'y/0123456789/9876543210/' lines1
+ mark '8.14' ;
+ if [ $BSD -eq 1 -o $GNU -eq 1 -o $SUN -eq 1 ] ; then
+ echo BSD/GNU/SUN sed fail this test
+ else
+ $SED -e 'y10\123456789198765432\101' lines1
+ fi
+ mark '8.15' ; $SED -e '1N;2y/\n/X/' lines1
+ mark '8.16'
+ if [ $BSD -eq 1 ] ; then
+ echo 'BSD sed does not handle branch defined REs'
+ else
+ echo 'eeefff' | $SED -e 'p' -e 's/e/X/p' -e ':x' \
+ -e 's//Y/p' -e '/f/bx'
+ fi
+}
+
+test_error()
+{
+ exec 3<&0 4>&1 5>&2
+ exec 0</dev/null
+ exec 2>&1
+ set -x
+ $TEST -x && exit 1
+ $TEST -f && exit 1
+ $TEST -e && exit 1
+ $TEST -f /dev/dds && exit 1
+ $TEST p /dev/dds && exit 1
+ $TEST -f /bin/sh && exit 1
+ $TEST '{' && exit 1
+ $TEST '{' && exit 1
+ $TEST '/hello/' && exit 1
+ $TEST '1,/hello/' && exit 1
+ $TEST -e '-5p' && exit 1
+ $TEST '/jj' && exit 1
+ $TEST 'a hello' && exit 1
+ $TEST 'a \ hello' && exit 1
+ $TEST 'b foo' && exit 1
+ $TEST 'd hello' && exit 1
+ $TEST 's/aa' && exit 1
+ $TEST 's/aa/' && exit 1
+ $TEST 's/a/b' && exit 1
+ $TEST 's/a/b/c/d' && exit 1
+ $TEST 's/a/b/ 1 2' && exit 1
+ $TEST 's/a/b/ 1 g' && exit 1
+ $TEST 's/a/b/w' && exit 1
+ $TEST 'y/aa' && exit 1
+ $TEST 'y/aa/b/' && exit 1
+ $TEST 'y/aa/' && exit 1
+ $TEST 'y/a/b' && exit 1
+ $TEST 'y/a/b/c/d' && exit 1
+ $TEST '!' && exit 1
+ $TEST supercalifrangolisticexprialidociussupercalifrangolisticexcius
+ set +x
+ exec 0<&3 1>&4 2>&5
+}
+
+main
diff --git a/usr.bin/sed/compile.c b/usr.bin/sed/compile.c
new file mode 100644
index 0000000..300a77f
--- /dev/null
+++ b/usr.bin/sed/compile.c
@@ -0,0 +1,946 @@
+/* $NetBSD: compile.c,v 1.47 2016/04/05 00:13:03 christos Exp $ */
+
+/*-
+ * Copyright (c) 1992 Diomidis Spinellis.
+ * Copyright (c) 1992, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Diomidis Spinellis of Imperial College, University of London.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#if HAVE_NBTOOL_CONFIG_H
+#include "nbtool_config.h"
+#endif
+
+#include <sys/cdefs.h>
+__RCSID("$NetBSD: compile.c,v 1.47 2016/04/05 00:13:03 christos Exp $");
+#ifdef __FBSDID
+__FBSDID("$FreeBSD: head/usr.bin/sed/compile.c 259132 2013-12-09 18:57:20Z eadler $");
+#endif
+
+#if 0
+static const char sccsid[] = "@(#)compile.c 8.1 (Berkeley) 6/6/93";
+#endif
+
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#include <ctype.h>
+#include <err.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <limits.h>
+#include <regex.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <wchar.h>
+
+#include "defs.h"
+#include "extern.h"
+
+#define LHSZ 128
+#define LHMASK (LHSZ - 1)
+static struct labhash {
+ struct labhash *lh_next;
+ u_int lh_hash;
+ struct s_command *lh_cmd;
+ int lh_ref;
+} *labels[LHSZ];
+
+static char *compile_addr(char *, struct s_addr *);
+static char *compile_ccl(char **, char *);
+static char *compile_delimited(char *, char *, int);
+static char *compile_flags(char *, struct s_subst *);
+static regex_t *compile_re(char *, int);
+static char *compile_subst(char *, struct s_subst *);
+static char *compile_text(void);
+static char *compile_tr(char *, struct s_tr **);
+static struct s_command
+ **compile_stream(struct s_command **);
+static char *duptoeol(char *, const char *);
+static void enterlabel(struct s_command *);
+static struct s_command
+ *findlabel(char *);
+static void fixuplabel(struct s_command *, struct s_command *);
+static void uselabel(void);
+
+/*
+ * Command specification. This is used to drive the command parser.
+ */
+struct s_format {
+ char code; /* Command code */
+ int naddr; /* Number of address args */
+ enum e_args args; /* Argument type */
+};
+
+static struct s_format cmd_fmts[] = {
+ {'{', 2, GROUP},
+ {'}', 0, ENDGROUP},
+ {'a', 1, TEXT},
+ {'b', 2, BRANCH},
+ {'c', 2, TEXT},
+ {'d', 2, EMPTY},
+ {'D', 2, EMPTY},
+ {'g', 2, EMPTY},
+ {'G', 2, EMPTY},
+ {'h', 2, EMPTY},
+ {'H', 2, EMPTY},
+ {'i', 1, TEXT},
+ {'l', 2, EMPTY},
+ {'n', 2, EMPTY},
+ {'N', 2, EMPTY},
+ {'p', 2, EMPTY},
+ {'P', 2, EMPTY},
+ {'q', 1, EMPTY},
+ {'r', 1, RFILE},
+ {'s', 2, SUBST},
+ {'t', 2, BRANCH},
+ {'w', 2, WFILE},
+ {'x', 2, EMPTY},
+ {'y', 2, TR},
+ {'!', 2, NONSEL},
+ {':', 0, LABEL},
+ {'#', 0, COMMENT},
+ {'=', 1, EMPTY},
+ {'\0', 0, COMMENT},
+};
+
+/* The compiled program. */
+struct s_command *prog;
+
+/*
+ * Compile the program into prog.
+ * Initialise appends.
+ */
+void
+compile(void)
+{
+ *compile_stream(&prog) = NULL;
+ fixuplabel(prog, NULL);
+ uselabel();
+ if (appendnum > 0)
+ appends = xmalloc(sizeof(struct s_appends) * appendnum);
+ match = xmalloc((maxnsub + 1) * sizeof(regmatch_t));
+}
+
+#define EATSPACE() do { \
+ if (p) \
+ while (*p && isspace((unsigned char)*p)) \
+ p++; \
+ } while (0)
+
+static struct s_command **
+compile_stream(struct s_command **link)
+{
+ char *p;
+ static char lbuf[_POSIX2_LINE_MAX + 1]; /* To save stack */
+ struct s_command *cmd, *cmd2, *stack;
+ struct s_format *fp;
+ char re[_POSIX2_LINE_MAX + 1];
+ int naddr; /* Number of addresses */
+
+ stack = 0;
+ for (;;) {
+ if ((p = cu_fgets(lbuf, sizeof(lbuf), NULL)) == NULL) {
+ if (stack != 0)
+ errx(1, "%lu: %s: unexpected EOF (pending }'s)",
+ linenum, fname);
+ return (link);
+ }
+
+semicolon: EATSPACE();
+ if (p) {
+ if (*p == '#' || *p == '\0')
+ continue;
+ else if (*p == ';') {
+ p++;
+ goto semicolon;
+ }
+ }
+ *link = cmd = xmalloc(sizeof(struct s_command));
+ link = &cmd->next;
+ cmd->startline = cmd->nonsel = 0;
+ /* First parse the addresses */
+ naddr = 0;
+
+/* Valid characters to start an address */
+#define addrchar(c) (strchr("0123456789/\\$", (c)))
+ if (addrchar(*p)) {
+ naddr++;
+ cmd->a1 = xmalloc(sizeof(struct s_addr));
+ p = compile_addr(p, cmd->a1);
+ EATSPACE(); /* EXTENSION */
+ if (*p == ',') {
+ p++;
+ EATSPACE(); /* EXTENSION */
+ naddr++;
+ cmd->a2 = xmalloc(sizeof(struct s_addr));
+ p = compile_addr(p, cmd->a2);
+ EATSPACE();
+ } else
+ cmd->a2 = 0;
+ } else
+ cmd->a1 = cmd->a2 = 0;
+
+nonsel: /* Now parse the command */
+ if (!*p)
+ errx(1, "%lu: %s: command expected", linenum, fname);
+ cmd->code = *p;
+ for (fp = cmd_fmts; fp->code; fp++)
+ if (fp->code == *p)
+ break;
+ if (!fp->code)
+ errx(1, "%lu: %s: invalid command code %c", linenum, fname, *p);
+ if (naddr > fp->naddr)
+ errx(1,
+ "%lu: %s: command %c expects up to %d address(es), found %d",
+ linenum, fname, *p, fp->naddr, naddr);
+ switch (fp->args) {
+ case NONSEL: /* ! */
+ p++;
+ EATSPACE();
+ cmd->nonsel = ! cmd->nonsel;
+ goto nonsel;
+ case GROUP: /* { */
+ p++;
+ EATSPACE();
+ cmd->next = stack;
+ stack = cmd;
+ link = &cmd->u.c;
+ if (*p)
+ goto semicolon;
+ break;
+ case ENDGROUP:
+ /*
+ * Short-circuit command processing, since end of
+ * group is really just a noop.
+ */
+ cmd->nonsel = 1;
+ if (stack == 0)
+ errx(1, "%lu: %s: unexpected }", linenum, fname);
+ cmd2 = stack;
+ stack = cmd2->next;
+ cmd2->next = cmd;
+ /*FALLTHROUGH*/
+ case EMPTY: /* d D g G h H l n N p P q x = \0 */
+ p++;
+ EATSPACE();
+ switch (*p) {
+ case ';':
+ p++;
+ link = &cmd->next;
+ goto semicolon;
+ case '}':
+ goto semicolon;
+ case '\0':
+ break;
+ default:
+ errx(1, "%lu: %s: extra characters at the end of %c command",
+ linenum, fname, cmd->code);
+ }
+ break;
+ case TEXT: /* a c i */
+ p++;
+ EATSPACE();
+ if (*p != '\\')
+ errx(1,
+"%lu: %s: command %c expects \\ followed by text", linenum, fname, cmd->code);
+ p++;
+ EATSPACE();
+ if (*p)
+ errx(1,
+ "%lu: %s: extra characters after \\ at the end of %c command",
+ linenum, fname, cmd->code);
+ cmd->t = compile_text();
+ break;
+ case COMMENT: /* \0 # */
+ break;
+ case WFILE: /* w */
+ p++;
+ EATSPACE();
+ if (*p == '\0')
+ errx(1, "%lu: %s: filename expected", linenum, fname);
+ cmd->t = duptoeol(p, "w command");
+ if (aflag)
+ cmd->u.fd = -1;
+ else if ((cmd->u.fd = open(p,
+ O_WRONLY|O_APPEND|O_CREAT|O_TRUNC,
+ DEFFILEMODE)) == -1)
+ err(1, "%s", p);
+ break;
+ case RFILE: /* r */
+ p++;
+ EATSPACE();
+ if (*p == '\0')
+ errx(1, "%lu: %s: filename expected", linenum, fname);
+ else
+ cmd->t = duptoeol(p, "read command");
+ break;
+ case BRANCH: /* b t */
+ p++;
+ EATSPACE();
+ if (*p == '\0')
+ cmd->t = NULL;
+ else
+ cmd->t = duptoeol(p, "branch");
+ break;
+ case LABEL: /* : */
+ p++;
+ EATSPACE();
+ cmd->t = duptoeol(p, "label");
+ if (strlen(p) == 0)
+ errx(1, "%lu: %s: empty label", linenum, fname);
+ enterlabel(cmd);
+ break;
+ case SUBST: /* s */
+ p++;
+ if (*p == '\0' || *p == '\\')
+ errx(1,
+"%lu: %s: substitute pattern can not be delimited by newline or backslash",
+ linenum, fname);
+ cmd->u.s = xcalloc(1, sizeof(struct s_subst));
+ p = compile_delimited(p, re, 0);
+ if (p == NULL)
+ errx(1,
+ "%lu: %s: unterminated substitute pattern", linenum, fname);
+
+ /* Compile RE with no case sensitivity temporarily */
+ if (*re == '\0')
+ cmd->u.s->re = NULL;
+ else
+ cmd->u.s->re = compile_re(re, 0);
+ --p;
+ p = compile_subst(p, cmd->u.s);
+ p = compile_flags(p, cmd->u.s);
+
+ /* Recompile RE with case sensitivity from "I" flag if any */
+ if (*re == '\0')
+ cmd->u.s->re = NULL;
+ else
+ cmd->u.s->re = compile_re(re, cmd->u.s->icase);
+ EATSPACE();
+ if (*p == ';') {
+ p++;
+ link = &cmd->next;
+ goto semicolon;
+ }
+ break;
+ case TR: /* y */
+ p++;
+ p = compile_tr(p, &cmd->u.y);
+ EATSPACE();
+ switch (*p) {
+ case ';':
+ p++;
+ link = &cmd->next;
+ goto semicolon;
+ case '}':
+ goto semicolon;
+ case '\0':
+ break;
+ default:
+ errx(1,
+"%lu: %s: extra text at the end of a transform command", linenum, fname);
+ }
+ if (*p)
+ break;
+ }
+ }
+}
+
+/*
+ * Get a delimited string. P points to the delimeter of the string; d points
+ * to a buffer area. Newline and delimiter escapes are processed; other
+ * escapes are ignored.
+ *
+ * Returns a pointer to the first character after the final delimiter or NULL
+ * in the case of a non-terminated string. The character array d is filled
+ * with the processed string.
+ */
+static char *
+compile_delimited(char *p, char *d, int is_tr)
+{
+ char c;
+
+ c = *p++;
+ if (c == '\0')
+ return (NULL);
+ else if (c == '\\')
+ errx(1, "%lu: %s: \\ can not be used as a string delimiter",
+ linenum, fname);
+ else if (c == '\n')
+ errx(1, "%lu: %s: newline can not be used as a string delimiter",
+ linenum, fname);
+ while (*p) {
+ if (*p == '[' && *p != c) {
+ if ((d = compile_ccl(&p, d)) == NULL)
+ errx(1, "%lu: %s: unbalanced brackets ([])", linenum, fname);
+ continue;
+ } else if (*p == '\\' && p[1] == '[') {
+ *d++ = *p++;
+ } else if (*p == '\\' && p[1] == c)
+ p++;
+ else if (*p == '\\' && p[1] == 'n') {
+ *d++ = '\n';
+ p += 2;
+ continue;
+ } else if (*p == '\\' && p[1] == '\\') {
+ if (is_tr)
+ p++;
+ else
+ *d++ = *p++;
+ } else if (*p == c) {
+ *d = '\0';
+ return (p + 1);
+ }
+ *d++ = *p++;
+ }
+ return (NULL);
+}
+
+
+/* compile_ccl: expand a POSIX character class */
+static char *
+compile_ccl(char **sp, char *t)
+{
+ int c, d;
+ char *s = *sp;
+
+ *t++ = *s++;
+ if (*s == '^')
+ *t++ = *s++;
+ if (*s == ']')
+ *t++ = *s++;
+ for (; *s && (*t = *s) != ']'; s++, t++)
+ if (*s == '[' && ((d = *(s+1)) == '.' || d == ':' || d == '=')) {
+ *++t = *++s, t++, s++;
+ for (c = *s; (*t = *s) != ']' || c != d; s++, t++)
+ if ((c = *s) == '\0')
+ return NULL;
+ }
+ return (*s == ']') ? *sp = ++s, ++t : NULL;
+}
+
+/*
+ * Compiles the regular expression in RE and returns a pointer to the compiled
+ * regular expression.
+ * Cflags are passed to regcomp.
+ */
+static regex_t *
+compile_re(char *re, int case_insensitive)
+{
+ regex_t *rep;
+ int eval, flags;
+
+
+ flags = rflags;
+ if (case_insensitive)
+ flags |= REG_ICASE;
+ rep = xmalloc(sizeof(regex_t));
+ if ((eval = regcomp(rep, re, flags)) != 0)
+ errx(1, "%lu: %s: RE error: %s",
+ linenum, fname, strregerror(eval, rep));
+ if (maxnsub < rep->re_nsub)
+ maxnsub = rep->re_nsub;
+ return (rep);
+}
+
+/*
+ * Compile the substitution string of a regular expression and set res to
+ * point to a saved copy of it. Nsub is the number of parenthesized regular
+ * expressions.
+ */
+static char *
+compile_subst(char *p, struct s_subst *s)
+{
+ static char lbuf[_POSIX2_LINE_MAX + 1];
+ size_t asize, size;
+ u_char ref;
+ char c, *text, *op, *sp;
+ int more = 1, sawesc = 0;
+
+ c = *p++; /* Terminator character */
+ if (c == '\0')
+ return (NULL);
+
+ s->maxbref = 0;
+ s->linenum = linenum;
+ asize = 2 * _POSIX2_LINE_MAX + 1;
+ text = xmalloc(asize);
+ size = 0;
+ do {
+ op = sp = text + size;
+ for (; *p; p++) {
+ if (*p == '\\' || sawesc) {
+ /*
+ * If this is a continuation from the last
+ * buffer, we won't have a character to
+ * skip over.
+ */
+ if (sawesc)
+ sawesc = 0;
+ else
+ p++;
+
+ if (*p == '\0') {
+ /*
+ * This escaped character is continued
+ * in the next part of the line. Note
+ * this fact, then cause the loop to
+ * exit w/ normal EOL case and reenter
+ * above with the new buffer.
+ */
+ sawesc = 1;
+ p--;
+ continue;
+ } else if (strchr("123456789", *p) != NULL) {
+ *sp++ = '\\';
+ ref = (u_char)(*p - '0');
+ if (s->re != NULL &&
+ ref > s->re->re_nsub)
+ errx(1, "%lu: %s: \\%c not defined in the RE",
+ linenum, fname, *p);
+ if (s->maxbref < ref)
+ s->maxbref = ref;
+ } else if (*p == '&' || *p == '\\')
+ *sp++ = '\\';
+ } else if (*p == c) {
+ if (*++p == '\0' && more) {
+ if (cu_fgets(lbuf, sizeof(lbuf), &more))
+ p = lbuf;
+ }
+ *sp++ = '\0';
+ size += (size_t)(sp - op);
+ s->new = xrealloc(text, size);
+ return (p);
+ } else if (*p == '\n') {
+ errx(1,
+"%lu: %s: unescaped newline inside substitute pattern", linenum, fname);
+ /* NOTREACHED */
+ }
+ *sp++ = *p;
+ }
+ size += (size_t)(sp - op);
+ if (asize - size < _POSIX2_LINE_MAX + 1) {
+ asize *= 2;
+ text = xrealloc(text, asize);
+ }
+ } while (cu_fgets(p = lbuf, sizeof(lbuf), &more));
+ errx(1, "%lu: %s: unterminated substitute in regular expression",
+ linenum, fname);
+ /* NOTREACHED */
+}
+
+/*
+ * Compile the flags of the s command
+ */
+static char *
+compile_flags(char *p, struct s_subst *s)
+{
+ int gn; /* True if we have seen g or n */
+ unsigned long nval;
+ char wfile[_POSIX2_LINE_MAX + 1], *q;
+
+ s->n = 1; /* Default */
+ s->p = 0;
+ s->wfile = NULL;
+ s->wfd = -1;
+ s->icase = 0;
+ for (gn = 0;;) {
+ EATSPACE(); /* EXTENSION */
+ switch (*p) {
+ case 'g':
+ if (gn)
+ errx(1,
+"%lu: %s: more than one number or 'g' in substitute flags", linenum, fname);
+ gn = 1;
+ s->n = 0;
+ break;
+ case '\0':
+ case '\n':
+ case ';':
+ return (p);
+ case 'p':
+ s->p = 1;
+ break;
+ case 'i':
+ case 'I':
+ s->icase = 1;
+ break;
+ case '1': case '2': case '3':
+ case '4': case '5': case '6':
+ case '7': case '8': case '9':
+ if (gn)
+ errx(1,
+"%lu: %s: more than one number or 'g' in substitute flags", linenum, fname);
+ gn = 1;
+ errno = 0;
+ nval = strtoul(p, &p, 10);
+ if (errno == ERANGE || nval > INT_MAX)
+ errx(1,
+"%lu: %s: overflow in the 'N' substitute flag", linenum, fname);
+ s->n = (int)nval;
+ p--;
+ break;
+ case 'w':
+ p++;
+#ifdef HISTORIC_PRACTICE
+ if (*p != ' ') {
+ warnx("%lu: %s: space missing before w wfile", linenum, fname);
+ return (p);
+ }
+#endif
+ EATSPACE();
+ q = wfile;
+ while (*p) {
+ if (*p == '\n')
+ break;
+ *q++ = *p++;
+ }
+ *q = '\0';
+ if (q == wfile)
+ errx(1, "%lu: %s: no wfile specified", linenum, fname);
+ s->wfile = strdup(wfile);
+ if (!aflag && (s->wfd = open(wfile,
+ O_WRONLY|O_APPEND|O_CREAT|O_TRUNC,
+ DEFFILEMODE)) == -1)
+ err(1, "%s", wfile);
+ return (p);
+ default:
+ errx(1, "%lu: %s: bad flag in substitute command: '%c'",
+ linenum, fname, *p);
+ break;
+ }
+ p++;
+ }
+}
+
+/*
+ * Compile a translation set of strings into a lookup table.
+ */
+static char *
+compile_tr(char *p, struct s_tr **py)
+{
+ struct s_tr *y;
+ size_t i;
+ const char *op, *np;
+ char old[_POSIX2_LINE_MAX + 1];
+ char new[_POSIX2_LINE_MAX + 1];
+ size_t oclen, oldlen, nclen, newlen;
+ mbstate_t mbs1, mbs2;
+
+ *py = y = xmalloc(sizeof(*y));
+ y->multis = NULL;
+ y->nmultis = 0;
+
+ if (*p == '\0' || *p == '\\')
+ errx(1,
+ "%lu: %s: transform pattern can not be delimited by newline or backslash",
+ linenum, fname);
+ p = compile_delimited(p, old, 1);
+ if (p == NULL)
+ errx(1, "%lu: %s: unterminated transform source string",
+ linenum, fname);
+ p = compile_delimited(p - 1, new, 1);
+ if (p == NULL)
+ errx(1, "%lu: %s: unterminated transform target string",
+ linenum, fname);
+ EATSPACE();
+ op = old;
+ oldlen = mbsrtowcs(NULL, &op, 0, NULL);
+ if (oldlen == (size_t)-1)
+ err(1, NULL);
+ np = new;
+ newlen = mbsrtowcs(NULL, &np, 0, NULL);
+ if (newlen == (size_t)-1)
+ err(1, NULL);
+ if (newlen != oldlen)
+ errx(1, "%lu: %s: transform strings are not the same length",
+ linenum, fname);
+ if (MB_CUR_MAX == 1) {
+ /*
+ * The single-byte encoding case is easy: generate a
+ * lookup table.
+ */
+ for (i = 0; i <= UCHAR_MAX; i++)
+ y->bytetab[i] = (u_char)i;
+ for (; *op; op++, np++)
+ y->bytetab[(u_char)*op] = (u_char)*np;
+ } else {
+ /*
+ * Multi-byte encoding case: generate a lookup table as
+ * above, but only for single-byte characters. The first
+ * bytes of multi-byte characters have their lookup table
+ * entries set to 0, which causes do_tr() to search through
+ * an auxiliary vector of multi-byte mappings.
+ */
+ memset(&mbs1, 0, sizeof(mbs1));
+ memset(&mbs2, 0, sizeof(mbs2));
+ for (i = 0; i <= UCHAR_MAX; i++)
+ y->bytetab[i] = (u_char)((btowc((int)i) != WEOF) ? i : 0);
+ while (*op != '\0') {
+ oclen = mbrlen(op, MB_LEN_MAX, &mbs1);
+ if (oclen == (size_t)-1 || oclen == (size_t)-2)
+ errc(1, EILSEQ, NULL);
+ nclen = mbrlen(np, MB_LEN_MAX, &mbs2);
+ if (nclen == (size_t)-1 || nclen == (size_t)-2)
+ errc(1, EILSEQ, NULL);
+ if (oclen == 1 && nclen == 1)
+ y->bytetab[(u_char)*op] = (u_char)*np;
+ else {
+ y->bytetab[(u_char)*op] = 0;
+ y->multis = xrealloc(y->multis,
+ (y->nmultis + 1) * sizeof(*y->multis));
+ i = y->nmultis++;
+ y->multis[i].fromlen = oclen;
+ memcpy(y->multis[i].from, op, oclen);
+ y->multis[i].tolen = nclen;
+ memcpy(y->multis[i].to, np, nclen);
+ }
+ op += oclen;
+ np += nclen;
+ }
+ }
+ return (p);
+}
+
+/*
+ * Compile the text following an a or i command.
+ */
+static char *
+compile_text(void)
+{
+ size_t asize, size;
+ int esc_nl;
+ char *text, *p, *op, *s;
+ char lbuf[_POSIX2_LINE_MAX + 1];
+
+ asize = 2 * _POSIX2_LINE_MAX + 1;
+ text = xmalloc(asize);
+ size = 0;
+ while (cu_fgets(lbuf, sizeof(lbuf), NULL)) {
+ op = s = text + size;
+ p = lbuf;
+ for (esc_nl = 0; *p != '\0'; p++) {
+ if (*p == '\\' && p[1] != '\0' && *++p == '\n')
+ esc_nl = 1;
+ *s++ = *p;
+ }
+ size += (size_t)(s - op);
+ if (!esc_nl) {
+ *s = '\0';
+ break;
+ }
+ if (asize - size < _POSIX2_LINE_MAX + 1) {
+ asize *= 2;
+ text = xrealloc(text, asize);
+ }
+ }
+ text[size] = '\0';
+ p = xrealloc(text, size + 1);
+ return (p);
+}
+
+/*
+ * Get an address and return a pointer to the first character after
+ * it. Fill the structure pointed to according to the address.
+ */
+static char *
+compile_addr(char *p, struct s_addr *a)
+{
+ char *end, re[_POSIX2_LINE_MAX + 1];
+ int icase;
+
+ icase = 0;
+
+ a->type = 0;
+ switch (*p) {
+ case '\\': /* Context address */
+ ++p;
+ /* FALLTHROUGH */
+ case '/': /* Context address */
+ p = compile_delimited(p, re, 0);
+ if (p == NULL)
+ errx(1, "%lu: %s: unterminated regular expression", linenum, fname);
+ /* Check for case insensitive regexp flag */
+ if (*p == 'I') {
+ icase = 1;
+ p++;
+ }
+ if (*re == '\0')
+ a->u.r = NULL;
+ else
+ a->u.r = compile_re(re, icase);
+ a->type = AT_RE;
+ return (p);
+
+ case '$': /* Last line */
+ a->type = AT_LAST;
+ return (p + 1);
+
+ case '+': /* Relative line number */
+ a->type = AT_RELLINE;
+ p++;
+ /* FALLTHROUGH */
+ /* Line number */
+ case '0': case '1': case '2': case '3': case '4':
+ case '5': case '6': case '7': case '8': case '9':
+ if (a->type == 0)
+ a->type = AT_LINE;
+ a->u.l = strtoul(p, &end, 10);
+ return (end);
+ default:
+ errx(1, "%lu: %s: expected context address", linenum, fname);
+ return (NULL);
+ }
+}
+
+/*
+ * duptoeol --
+ * Return a copy of all the characters up to \n or \0.
+ */
+static char *
+duptoeol(char *s, const char *ctype)
+{
+ size_t len;
+ int ws;
+ char *p, *start;
+
+ ws = 0;
+ for (start = s; *s != '\0' && *s != '\n'; ++s)
+ ws = isspace((unsigned char)*s);
+ *s = '\0';
+ if (ws)
+ warnx("%lu: %s: whitespace after %s", linenum, fname, ctype);
+ len = (size_t)(s - start + 1);
+ p = xmalloc(len);
+ return (memmove(p, start, len));
+}
+
+/*
+ * Convert goto label names to addresses, and count a and r commands, in
+ * the given subset of the script. Free the memory used by labels in b
+ * and t commands (but not by :).
+ *
+ * TODO: Remove } nodes
+ */
+static void
+fixuplabel(struct s_command *cp, struct s_command *end)
+{
+
+ for (; cp != end; cp = cp->next)
+ switch (cp->code) {
+ case 'a':
+ case 'r':
+ appendnum++;
+ break;
+ case 'b':
+ case 't':
+ /* Resolve branch target. */
+ if (cp->t == NULL) {
+ cp->u.c = NULL;
+ break;
+ }
+ if ((cp->u.c = findlabel(cp->t)) == NULL)
+ errx(1, "%lu: %s: undefined label '%s'", linenum, fname, cp->t);
+ free(cp->t);
+ break;
+ case '{':
+ /* Do interior commands. */
+ fixuplabel(cp->u.c, cp->next);
+ break;
+ }
+}
+
+/*
+ * Associate the given command label for later lookup.
+ */
+static void
+enterlabel(struct s_command *cp)
+{
+ struct labhash **lhp, *lh;
+ u_char *p;
+ u_int h, c;
+
+ for (h = 0, p = (u_char *)cp->t; (c = *p) != 0; p++)
+ h = (h << 5) + h + c;
+ lhp = &labels[h & LHMASK];
+ for (lh = *lhp; lh != NULL; lh = lh->lh_next)
+ if (lh->lh_hash == h && strcmp(cp->t, lh->lh_cmd->t) == 0)
+ errx(1, "%lu: %s: duplicate label '%s'", linenum, fname, cp->t);
+ lh = xmalloc(sizeof *lh);
+ lh->lh_next = *lhp;
+ lh->lh_hash = h;
+ lh->lh_cmd = cp;
+ lh->lh_ref = 0;
+ *lhp = lh;
+}
+
+/*
+ * Find the label contained in the command l in the command linked
+ * list cp. L is excluded from the search. Return NULL if not found.
+ */
+static struct s_command *
+findlabel(char *name)
+{
+ struct labhash *lh;
+ u_char *p;
+ u_int h, c;
+
+ for (h = 0, p = (u_char *)name; (c = *p) != 0; p++)
+ h = (h << 5) + h + c;
+ for (lh = labels[h & LHMASK]; lh != NULL; lh = lh->lh_next) {
+ if (lh->lh_hash == h && strcmp(name, lh->lh_cmd->t) == 0) {
+ lh->lh_ref = 1;
+ return (lh->lh_cmd);
+ }
+ }
+ return (NULL);
+}
+
+/*
+ * Warn about any unused labels. As a side effect, release the label hash
+ * table space.
+ */
+static void
+uselabel(void)
+{
+ struct labhash *lh, *next;
+ int i;
+
+ for (i = 0; i < LHSZ; i++) {
+ for (lh = labels[i]; lh != NULL; lh = next) {
+ next = lh->lh_next;
+ if (!lh->lh_ref)
+ warnx("%lu: %s: unused label '%s'",
+ linenum, fname, lh->lh_cmd->t);
+ free(lh);
+ }
+ }
+}
diff --git a/usr.bin/sed/defs.h b/usr.bin/sed/defs.h
new file mode 100644
index 0000000..b564115
--- /dev/null
+++ b/usr.bin/sed/defs.h
@@ -0,0 +1,150 @@
+/* $NetBSD: defs.h,v 1.12 2014/06/06 21:56:39 wiz Exp $ */
+
+/*-
+ * Copyright (c) 1992 Diomidis Spinellis.
+ * Copyright (c) 1992, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Diomidis Spinellis of Imperial College, University of London.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * @(#)defs.h 8.1 (Berkeley) 6/6/93
+ * $FreeBSD: head/usr.bin/sed/defs.h 192732 2009-05-25 06:45:33Z brian $
+ */
+
+/*
+ * Types of address specifications
+ */
+enum e_atype {
+ AT_RE = 1, /* Line that match RE */
+ AT_LINE, /* Specific line */
+ AT_RELLINE, /* Relative line */
+ AT_LAST /* Last line */
+};
+
+/*
+ * Format of an address
+ */
+struct s_addr {
+ enum e_atype type; /* Address type */
+ union {
+ u_long l; /* Line number */
+ regex_t *r; /* Regular expression */
+ } u;
+};
+
+/*
+ * Substitution command
+ */
+struct s_subst {
+ int n; /* Occurrence to subst. */
+ int p; /* True if p flag */
+ int icase; /* True if I flag */
+ char *wfile; /* NULL if no wfile */
+ int wfd; /* Cached file descriptor */
+ regex_t *re; /* Regular expression */
+ unsigned int maxbref; /* Largest backreference. */
+ u_long linenum; /* Line number. */
+ char *new; /* Replacement text */
+};
+
+/*
+ * Translate command.
+ */
+struct s_tr {
+ unsigned char bytetab[256];
+ struct trmulti {
+ size_t fromlen;
+ char from[MB_LEN_MAX];
+ size_t tolen;
+ char to[MB_LEN_MAX];
+ } *multis;
+ size_t nmultis;
+};
+
+/*
+ * An internally compiled command.
+ * Initialy, label references are stored in t, on a second pass they
+ * are updated to pointers.
+ */
+struct s_command {
+ struct s_command *next; /* Pointer to next command */
+ struct s_addr *a1, *a2; /* Start and end address */
+ u_long startline; /* Start line number or zero */
+ char *t; /* Text for : a c i r w */
+ union {
+ struct s_command *c; /* Command(s) for b t { */
+ struct s_subst *s; /* Substitute command */
+ struct s_tr *y; /* Replace command array */
+ int fd; /* File descriptor for w */
+ } u;
+ char code; /* Command code */
+ u_int nonsel:1; /* True if ! */
+};
+
+/*
+ * Types of command arguments recognised by the parser
+ */
+enum e_args {
+ EMPTY, /* d D g G h H l n N p P q x = \0 */
+ TEXT, /* a c i */
+ NONSEL, /* ! */
+ GROUP, /* { */
+ ENDGROUP, /* } */
+ COMMENT, /* # */
+ BRANCH, /* b t */
+ LABEL, /* : */
+ RFILE, /* r */
+ WFILE, /* w */
+ SUBST, /* s */
+ TR /* y */
+};
+
+/*
+ * Structure containing things to append before a line is read
+ */
+struct s_appends {
+ enum {AP_STRING, AP_FILE} type;
+ char *s;
+ size_t len;
+};
+
+enum e_spflag {
+ APPEND, /* Append to the contents. */
+ REPLACE /* Replace the contents. */
+};
+
+/*
+ * Structure for a space (process, hold, otherwise).
+ */
+typedef struct {
+ char *space; /* Current space pointer. */
+ size_t len; /* Current length. */
+ int deleted; /* If deleted. */
+ char *back; /* Backing memory. */
+ size_t blen; /* Backing memory length. */
+} SPACE;
diff --git a/usr.bin/sed/extern.h b/usr.bin/sed/extern.h
new file mode 100644
index 0000000..0d28591
--- /dev/null
+++ b/usr.bin/sed/extern.h
@@ -0,0 +1,61 @@
+/* $NetBSD: extern.h,v 1.20 2015/03/12 12:40:41 christos Exp $ */
+
+/*-
+ * Copyright (c) 1992 Diomidis Spinellis.
+ * Copyright (c) 1992, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Diomidis Spinellis of Imperial College, University of London.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * @(#)extern.h 8.1 (Berkeley) 6/6/93
+ * $FreeBSD: head/usr.bin/sed/extern.h 170608 2007-06-12 12:05:24Z yar $
+ */
+
+extern struct s_command *prog;
+extern struct s_appends *appends;
+extern regmatch_t *match;
+extern size_t maxnsub;
+extern u_long linenum;
+extern size_t appendnum;
+extern int aflag, eflag, nflag;
+extern const char *fname, *outfname;
+extern FILE *infile, *outfile;
+extern int rflags; /* regex flags to use */
+
+void cfclose(struct s_command *, struct s_command *);
+void compile(void);
+void cspace(SPACE *, const char *, size_t, enum e_spflag);
+char *cu_fgets(char *, int, int *);
+int mf_fgets(SPACE *, enum e_spflag);
+int lastline(void);
+void process(void);
+void resetstate(void);
+char *strregerror(int, regex_t *);
+void *xmalloc(size_t);
+void *xrealloc(void *, size_t);
+void *xcalloc(size_t, size_t);
diff --git a/usr.bin/sed/main.c b/usr.bin/sed/main.c
new file mode 100644
index 0000000..cfe9d35
--- /dev/null
+++ b/usr.bin/sed/main.c
@@ -0,0 +1,517 @@
+/* $NetBSD: main.c,v 1.34 2015/03/12 12:40:41 christos Exp $ */
+
+/*-
+ * Copyright (c) 2013 Johann 'Myrkraverk' Oskarsson.
+ * Copyright (c) 1992 Diomidis Spinellis.
+ * Copyright (c) 1992, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Diomidis Spinellis of Imperial College, University of London.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#if HAVE_NBTOOL_CONFIG_H
+#include "nbtool_config.h"
+#endif
+
+#include <sys/cdefs.h>
+__RCSID("$NetBSD: main.c,v 1.34 2015/03/12 12:40:41 christos Exp $");
+#ifdef __FBSDID
+__FBSDID("$FreeBSD: head/usr.bin/sed/main.c 252231 2013-06-26 04:14:19Z pfg $");
+#endif
+
+#ifndef lint
+__COPYRIGHT("@(#) Copyright (c) 1992, 1993\
+ The Regents of the University of California. All rights reserved.");
+#endif
+
+#if 0
+static const char sccsid[] = "@(#)main.c 8.2 (Berkeley) 1/3/94";
+#endif
+
+#include <sys/types.h>
+#include <sys/mman.h>
+#include <sys/param.h>
+#include <sys/stat.h>
+
+#include <err.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <libgen.h>
+#include <limits.h>
+#include <locale.h>
+#include <regex.h>
+#include <stddef.h>
+#define _WITH_GETLINE
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "defs.h"
+#include "extern.h"
+
+/*
+ * Linked list of units (strings and files) to be compiled
+ */
+struct s_compunit {
+ struct s_compunit *next;
+ enum e_cut {CU_FILE, CU_STRING} type;
+ char *s; /* Pointer to string or fname */
+};
+
+/*
+ * Linked list pointer to compilation units and pointer to current
+ * next pointer.
+ */
+static struct s_compunit *script, **cu_nextp = &script;
+
+/*
+ * Linked list of files to be processed
+ */
+struct s_flist {
+ char *fname;
+ struct s_flist *next;
+};
+
+/*
+ * Linked list pointer to files and pointer to current
+ * next pointer.
+ */
+static struct s_flist *files, **fl_nextp = &files;
+
+FILE *infile; /* Current input file */
+FILE *outfile; /* Current output file */
+
+int aflag, eflag, nflag;
+int rflags = 0;
+static int rval; /* Exit status */
+
+static int ispan; /* Whether inplace editing spans across files */
+
+/*
+ * Current file and line number; line numbers restart across compilation
+ * units, but span across input files. The latter is optional if editing
+ * in place.
+ */
+const char *fname; /* File name. */
+const char *outfname; /* Output file name */
+static char oldfname[PATH_MAX]; /* Old file name (for in-place editing) */
+static char tmpfname[PATH_MAX]; /* Temporary file name (for in-place editing) */
+static const char *inplace; /* Inplace edit file extension. */
+u_long linenum;
+
+static void add_compunit(enum e_cut, char *);
+static void add_file(char *);
+static void usage(void) __dead;
+
+int
+main(int argc, char *argv[])
+{
+ int c, fflag;
+ char *temp_arg;
+
+ setprogname(argv[0]);
+ (void) setlocale(LC_ALL, "");
+
+ fflag = 0;
+ inplace = NULL;
+
+ while ((c = getopt(argc, argv, "EI::ae:f:i::lnru")) != -1)
+ switch (c) {
+ case 'r': /* Gnu sed compat */
+ case 'E':
+ rflags = REG_EXTENDED;
+ break;
+ case 'I':
+ inplace = optarg ? optarg : __UNCONST("");
+ ispan = 1; /* span across input files */
+ break;
+ case 'a':
+ aflag = 1;
+ break;
+ case 'e':
+ eflag = 1;
+ temp_arg = xmalloc(strlen(optarg) + 2);
+ strcpy(temp_arg, optarg);
+ strcat(temp_arg, "\n");
+ add_compunit(CU_STRING, temp_arg);
+ break;
+ case 'f':
+ fflag = 1;
+ add_compunit(CU_FILE, optarg);
+ break;
+ case 'i':
+ inplace = optarg ? optarg : __UNCONST("");
+ ispan = 0; /* don't span across input files */
+ break;
+ case 'l':
+#ifdef _IOLBF
+ c = setvbuf(stdout, NULL, _IOLBF, 0);
+#else
+ c = setlinebuf(stdout);
+#endif
+ if (c)
+ warn("setting line buffered output failed");
+ break;
+ case 'n':
+ nflag = 1;
+ break;
+ case 'u':
+#ifdef _IONBF
+ c = setvbuf(stdout, NULL, _IONBF, 0);
+#else
+ c = -1;
+ errno = EOPNOTSUPP;
+#endif
+ if (c)
+ warn("setting unbuffered output failed");
+ break;
+ default:
+ case '?':
+ usage();
+ }
+ argc -= optind;
+ argv += optind;
+
+ /* First usage case; script is the first arg */
+ if (!eflag && !fflag && *argv) {
+ add_compunit(CU_STRING, *argv);
+ argv++;
+ }
+
+ compile();
+
+ /* Continue with first and start second usage */
+ if (*argv)
+ for (; *argv; argv++)
+ add_file(*argv);
+ else
+ add_file(NULL);
+ process();
+ cfclose(prog, NULL);
+ if (fclose(stdout))
+ err(1, "stdout");
+ exit(rval);
+}
+
+static void
+usage(void)
+{
+ (void)fprintf(stderr,
+ "Usage: %s [-aElnru] command [file ...]\n"
+ "\t%s [-aElnru] [-e command] [-f command_file] [-I[extension]]\n"
+ "\t [-i[extension]] [file ...]\n", getprogname(), getprogname());
+ exit(1);
+}
+
+/*
+ * Like fgets, but go through the chain of compilation units chaining them
+ * together. Empty strings and files are ignored.
+ */
+char *
+cu_fgets(char *buf, int n, int *more)
+{
+ static enum {ST_EOF, ST_FILE, ST_STRING} state = ST_EOF;
+ static FILE *f; /* Current open file */
+ static char *s; /* Current pointer inside string */
+ static char string_ident[30];
+ char *p;
+
+again:
+ switch (state) {
+ case ST_EOF:
+ if (script == NULL) {
+ if (more != NULL)
+ *more = 0;
+ return (NULL);
+ }
+ linenum = 0;
+ switch (script->type) {
+ case CU_FILE:
+ if ((f = fopen(script->s, "r")) == NULL)
+ err(1, "%s", script->s);
+ fname = script->s;
+ state = ST_FILE;
+ goto again;
+ case CU_STRING:
+ if (((size_t)snprintf(string_ident,
+ sizeof(string_ident), "\"%s\"", script->s)) >=
+ sizeof(string_ident) - 1)
+ (void)strcpy(string_ident +
+ sizeof(string_ident) - 6, " ...\"");
+ fname = string_ident;
+ s = script->s;
+ state = ST_STRING;
+ goto again;
+ }
+ case ST_FILE:
+ if ((p = fgets(buf, n, f)) != NULL) {
+ linenum++;
+ if (linenum == 1 && buf[0] == '#' && buf[1] == 'n')
+ nflag = 1;
+ if (more != NULL)
+ *more = !feof(f);
+ return (p);
+ }
+ script = script->next;
+ (void)fclose(f);
+ state = ST_EOF;
+ goto again;
+ case ST_STRING:
+ if (linenum == 0 && s[0] == '#' && s[1] == 'n')
+ nflag = 1;
+ p = buf;
+ for (;;) {
+ if (n-- <= 1) {
+ *p = '\0';
+ linenum++;
+ if (more != NULL)
+ *more = 1;
+ return (buf);
+ }
+ switch (*s) {
+ case '\0':
+ state = ST_EOF;
+ if (s == script->s) {
+ script = script->next;
+ goto again;
+ } else {
+ script = script->next;
+ *p = '\0';
+ linenum++;
+ if (more != NULL)
+ *more = 0;
+ return (buf);
+ }
+ case '\n':
+ *p++ = '\n';
+ *p = '\0';
+ s++;
+ linenum++;
+ if (more != NULL)
+ *more = 0;
+ return (buf);
+ default:
+ *p++ = *s++;
+ }
+ }
+ }
+ /* NOTREACHED */
+ return (NULL);
+}
+
+/*
+ * Like fgets, but go through the list of files chaining them together.
+ * Set len to the length of the line.
+ */
+int
+mf_fgets(SPACE *sp, enum e_spflag spflag)
+{
+ struct stat sb;
+ size_t len;
+ static char *p = NULL;
+ static size_t plen = 0;
+ int c;
+ static int firstfile;
+
+ if (infile == NULL) {
+ /* stdin? */
+ if (files->fname == NULL) {
+ if (inplace != NULL)
+ errx(1, "-I or -i may not be used with stdin");
+ infile = stdin;
+ fname = "stdin";
+ outfile = stdout;
+ outfname = "stdout";
+ }
+ firstfile = 1;
+ }
+
+ for (;;) {
+ if (infile != NULL && (c = getc(infile)) != EOF) {
+ (void)ungetc(c, infile);
+ break;
+ }
+ /* If we are here then either eof or no files are open yet */
+ if (infile == stdin) {
+ sp->len = 0;
+ return (0);
+ }
+ if (infile != NULL) {
+ fclose(infile);
+ if (*oldfname != '\0') {
+ /* if there was a backup file, remove it */
+ unlink(oldfname);
+ /*
+ * Backup the original. Note that hard links
+ * are not supported on all filesystems.
+ */
+ if ((link(fname, oldfname) != 0) &&
+ (rename(fname, oldfname) != 0)) {
+ warn("rename()");
+ if (*tmpfname)
+ unlink(tmpfname);
+ exit(1);
+ }
+ *oldfname = '\0';
+ }
+ if (*tmpfname != '\0') {
+ if (outfile != NULL && outfile != stdout)
+ if (fclose(outfile) != 0) {
+ warn("fclose()");
+ unlink(tmpfname);
+ exit(1);
+ }
+ outfile = NULL;
+ if (rename(tmpfname, fname) != 0) {
+ /* this should not happen really! */
+ warn("rename()");
+ unlink(tmpfname);
+ exit(1);
+ }
+ *tmpfname = '\0';
+ }
+ outfname = NULL;
+ }
+ if (firstfile == 0)
+ files = files->next;
+ else
+ firstfile = 0;
+ if (files == NULL) {
+ sp->len = 0;
+ return (0);
+ }
+ fname = files->fname;
+ if (inplace != NULL) {
+ if (lstat(fname, &sb) != 0)
+ err(1, "%s", fname);
+ if (!(sb.st_mode & S_IFREG))
+ errx(1, "%s: %s %s", fname,
+ "in-place editing only",
+ "works for regular files");
+ if (*inplace != '\0') {
+ strlcpy(oldfname, fname,
+ sizeof(oldfname));
+ len = strlcat(oldfname, inplace,
+ sizeof(oldfname));
+ if (len > sizeof(oldfname))
+ errx(1, "%s: name too long", fname);
+ }
+ char d_name[PATH_MAX], f_name[PATH_MAX];
+ (void)strlcpy(d_name, fname, sizeof(d_name));
+ (void)strlcpy(f_name, fname, sizeof(f_name));
+ len = (size_t)snprintf(tmpfname, sizeof(tmpfname),
+ "%s/.!%ld!%s", dirname(d_name), (long)getpid(),
+ basename(f_name));
+ if (len >= sizeof(tmpfname))
+ errx(1, "%s: name too long", fname);
+ unlink(tmpfname);
+ if (outfile != NULL && outfile != stdout)
+ fclose(outfile);
+ if ((outfile = fopen(tmpfname, "w")) == NULL)
+ err(1, "%s", fname);
+ fchown(fileno(outfile), sb.st_uid, sb.st_gid);
+ fchmod(fileno(outfile), sb.st_mode & ALLPERMS);
+ outfname = tmpfname;
+ if (!ispan) {
+ linenum = 0;
+ resetstate();
+ }
+ } else {
+ outfile = stdout;
+ outfname = "stdout";
+ }
+ if ((infile = fopen(fname, "r")) == NULL) {
+ warn("%s", fname);
+ rval = 1;
+ continue;
+ }
+ }
+ /*
+ * We are here only when infile is open and we still have something
+ * to read from it.
+ *
+ * Use getline() so that we can handle essentially infinite input
+ * data. The p and plen are static so each invocation gives
+ * getline() the same buffer which is expanded as needed.
+ */
+ ssize_t slen = getline(&p, &plen, infile);
+ if (slen == -1)
+ err(1, "%s", fname);
+ if (slen != 0 && p[slen - 1] == '\n')
+ slen--;
+ cspace(sp, p, (size_t)slen, spflag);
+
+ linenum++;
+
+ return (1);
+}
+
+/*
+ * Add a compilation unit to the linked list
+ */
+static void
+add_compunit(enum e_cut type, char *s)
+{
+ struct s_compunit *cu;
+
+ cu = xmalloc(sizeof(struct s_compunit));
+ cu->type = type;
+ cu->s = s;
+ cu->next = NULL;
+ *cu_nextp = cu;
+ cu_nextp = &cu->next;
+}
+
+/*
+ * Add a file to the linked list
+ */
+static void
+add_file(char *s)
+{
+ struct s_flist *fp;
+
+ fp = xmalloc(sizeof(struct s_flist));
+ fp->next = NULL;
+ *fl_nextp = fp;
+ fp->fname = s;
+ fl_nextp = &fp->next;
+}
+
+int
+lastline(void)
+{
+ int ch;
+
+ if (files->next != NULL && (inplace == NULL || ispan))
+ return (0);
+ if ((ch = getc(infile)) == EOF)
+ return (1);
+ ungetc(ch, infile);
+ return (0);
+}
diff --git a/usr.bin/sed/misc.c b/usr.bin/sed/misc.c
new file mode 100644
index 0000000..bd0ecdf
--- /dev/null
+++ b/usr.bin/sed/misc.c
@@ -0,0 +1,119 @@
+/* $NetBSD: misc.c,v 1.15 2014/06/26 02:14:32 christos Exp $ */
+
+/*-
+ * Copyright (c) 1992 Diomidis Spinellis.
+ * Copyright (c) 1992, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Diomidis Spinellis of Imperial College, University of London.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#if HAVE_NBTOOL_CONFIG_H
+#include "nbtool_config.h"
+#endif
+
+#include <sys/cdefs.h>
+__RCSID("$NetBSD: misc.c,v 1.15 2014/06/26 02:14:32 christos Exp $");
+#ifdef __FBSDID
+__FBSDID("$FreeBSD: head/usr.bin/sed/misc.c 200462 2009-12-13 03:14:06Z delphij $");
+#endif
+
+#if 0
+static const char sccsid[] = "@(#)misc.c 8.1 (Berkeley) 6/6/93";
+#endif
+
+#include <sys/types.h>
+
+#include <err.h>
+#include <limits.h>
+#include <regex.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "defs.h"
+#include "extern.h"
+
+/*
+ * malloc with result test
+ */
+void *
+xmalloc(size_t size)
+{
+ void *p;
+
+ if ((p = malloc(size)) == NULL)
+ err(1, "malloc(%zu)", size);
+ return p;
+}
+
+/*
+ * realloc with result test
+ */
+void *
+xrealloc(void *p, size_t size)
+{
+ if (p == NULL) /* Compatibility hack. */
+ return (xmalloc(size));
+
+ if ((p = realloc(p, size)) == NULL)
+ err(1, "realloc(%zu)", size);
+ return p;
+}
+
+/*
+ * realloc with result test
+ */
+void *
+xcalloc(size_t c, size_t n)
+{
+ void *p;
+
+ if ((p = calloc(c, n)) == NULL)
+ err(1, "calloc(%zu, %zu)", c, n);
+ return p;
+}
+/*
+ * Return a string for a regular expression error passed. This is overkill,
+ * because of the silly semantics of regerror (we can never know the size of
+ * the buffer).
+ */
+char *
+strregerror(int errcode, regex_t *preg)
+{
+ char buf[1];
+ static char *oe;
+ size_t s;
+
+ if (oe != NULL)
+ free(oe);
+ s = regerror(errcode, preg, buf, 0);
+ oe = xmalloc(s);
+ (void)regerror(errcode, preg, oe, s);
+ return (oe);
+}
diff --git a/usr.bin/sed/process.c b/usr.bin/sed/process.c
new file mode 100644
index 0000000..896df5a
--- /dev/null
+++ b/usr.bin/sed/process.c
@@ -0,0 +1,792 @@
+/* $NetBSD: process.c,v 1.52 2015/03/12 12:40:41 christos Exp $ */
+
+/*-
+ * Copyright (c) 1992 Diomidis Spinellis.
+ * Copyright (c) 1992, 1993, 1994
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Diomidis Spinellis of Imperial College, University of London.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#if HAVE_NBTOOL_CONFIG_H
+#include "nbtool_config.h"
+#endif
+
+#include <sys/cdefs.h>
+__RCSID("$NetBSD: process.c,v 1.52 2015/03/12 12:40:41 christos Exp $");
+#ifdef __FBSDID
+__FBSDID("$FreeBSD: head/usr.bin/sed/process.c 192732 2009-05-25 06:45:33Z brian $");
+#endif
+
+#if 0
+static const char sccsid[] = "@(#)process.c 8.6 (Berkeley) 4/20/94";
+#endif
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/ioctl.h>
+#include <sys/uio.h>
+
+#include <ctype.h>
+#include <err.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <limits.h>
+#include <regex.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <wchar.h>
+#include <wctype.h>
+
+#include "defs.h"
+#include "extern.h"
+
+static SPACE HS, PS, SS, YS;
+#define pd PS.deleted
+#define ps PS.space
+#define psl PS.len
+#define hs HS.space
+#define hsl HS.len
+
+static __inline int applies(struct s_command *);
+static void do_tr(struct s_tr *);
+static void flush_appends(void);
+static void lputs(char *, size_t);
+static __inline int regexec_e(regex_t *, const char *, int, int, size_t);
+static void regsub(SPACE *, char *, char *);
+static int substitute(struct s_command *);
+
+struct s_appends *appends; /* Array of pointers to strings to append. */
+static size_t appendx; /* Index into appends array. */
+size_t appendnum; /* Size of appends array. */
+
+static int lastaddr; /* Set by applies if last address of a range. */
+static int sdone; /* If any substitutes since last line input. */
+ /* Iov structure for 'w' commands. */
+static regex_t *defpreg;
+size_t maxnsub;
+regmatch_t *match;
+
+#define OUT() do {fwrite(ps, 1, psl, outfile); fputc('\n', outfile);} while (0)
+
+void
+process(void)
+{
+ struct s_command *cp;
+ SPACE tspace;
+ size_t oldpsl = 0;
+ char *p;
+
+ p = NULL;
+
+ for (linenum = 0; mf_fgets(&PS, REPLACE);) {
+ pd = 0;
+top:
+ cp = prog;
+redirect:
+ while (cp != NULL) {
+ if (!applies(cp)) {
+ cp = cp->next;
+ continue;
+ }
+ switch (cp->code) {
+ case '{':
+ cp = cp->u.c;
+ goto redirect;
+ case 'a':
+ if (appendx >= appendnum)
+ appends = xrealloc(appends,
+ sizeof(struct s_appends) *
+ (appendnum *= 2));
+ appends[appendx].type = AP_STRING;
+ appends[appendx].s = cp->t;
+ appends[appendx].len = strlen(cp->t);
+ appendx++;
+ break;
+ case 'b':
+ cp = cp->u.c;
+ goto redirect;
+ case 'c':
+ pd = 1;
+ psl = 0;
+ if (cp->a2 == NULL || lastaddr || lastline())
+ (void)fprintf(outfile, "%s", cp->t);
+ goto new;
+ case 'd':
+ pd = 1;
+ goto new;
+ case 'D':
+ if (pd)
+ goto new;
+ if (psl == 0 ||
+ (p = memchr(ps, '\n', psl - 1)) == NULL) {
+ pd = 1;
+ goto new;
+ } else {
+ psl -= (size_t)((p + 1) - ps);
+ memmove(ps, p + 1, psl);
+ goto top;
+ }
+ case 'g':
+ cspace(&PS, hs, hsl, REPLACE);
+ break;
+ case 'G':
+ cspace(&PS, "\n", 1, APPEND);
+ cspace(&PS, hs, hsl, APPEND);
+ break;
+ case 'h':
+ cspace(&HS, ps, psl, REPLACE);
+ break;
+ case 'H':
+ cspace(&HS, "\n", 1, APPEND);
+ cspace(&HS, ps, psl, APPEND);
+ break;
+ case 'i':
+ (void)fprintf(outfile, "%s", cp->t);
+ break;
+ case 'l':
+ lputs(ps, psl);
+ break;
+ case 'n':
+ if (!nflag && !pd)
+ OUT();
+ flush_appends();
+ if (!mf_fgets(&PS, REPLACE))
+ exit(0);
+ pd = 0;
+ break;
+ case 'N':
+ flush_appends();
+ cspace(&PS, "\n", 1, APPEND);
+ if (!mf_fgets(&PS, APPEND))
+ exit(0);
+ break;
+ case 'p':
+ if (pd)
+ break;
+ OUT();
+ break;
+ case 'P':
+ if (pd)
+ break;
+ if ((p = memchr(ps, '\n', psl - 1)) != NULL) {
+ oldpsl = psl;
+ psl = (size_t)(p - ps);
+ }
+ OUT();
+ if (p != NULL)
+ psl = oldpsl;
+ break;
+ case 'q':
+ if (!nflag && !pd)
+ OUT();
+ flush_appends();
+ exit(0);
+ case 'r':
+ if (appendx >= appendnum)
+ appends = xrealloc(appends,
+ sizeof(struct s_appends) *
+ (appendnum *= 2));
+ appends[appendx].type = AP_FILE;
+ appends[appendx].s = cp->t;
+ appends[appendx].len = strlen(cp->t);
+ appendx++;
+ break;
+ case 's':
+ sdone |= substitute(cp);
+ break;
+ case 't':
+ if (sdone) {
+ sdone = 0;
+ cp = cp->u.c;
+ goto redirect;
+ }
+ break;
+ case 'w':
+ if (pd)
+ break;
+ if (cp->u.fd == -1 && (cp->u.fd = open(cp->t,
+ O_WRONLY|O_APPEND|O_CREAT|O_TRUNC,
+ DEFFILEMODE)) == -1)
+ err(1, "%s", cp->t);
+ if (write(cp->u.fd, ps, psl) != (ssize_t)psl ||
+ write(cp->u.fd, "\n", 1) != 1)
+ err(1, "%s", cp->t);
+ break;
+ case 'x':
+ /*
+ * If the hold space is null, make it empty
+ * but not null. Otherwise the pattern space
+ * will become null after the swap, which is
+ * an abnormal condition.
+ */
+ if (hs == NULL)
+ cspace(&HS, "", 0, REPLACE);
+ tspace = PS;
+ PS = HS;
+ HS = tspace;
+ break;
+ case 'y':
+ if (pd || psl == 0)
+ break;
+ do_tr(cp->u.y);
+ break;
+ case ':':
+ case '}':
+ break;
+ case '=':
+ (void)fprintf(outfile, "%lu\n", linenum);
+ }
+ cp = cp->next;
+ } /* for all cp */
+
+new: if (!nflag && !pd)
+ OUT();
+ flush_appends();
+ } /* for all lines */
+}
+
+/*
+ * TRUE if the address passed matches the current program state
+ * (lastline, linenumber, ps).
+ */
+#define MATCH(a) \
+ ((a)->type == AT_RE ? regexec_e((a)->u.r, ps, 0, 1, psl) : \
+ (a)->type == AT_LINE ? linenum == (a)->u.l : lastline())
+
+/*
+ * Return TRUE if the command applies to the current line. Sets the start
+ * line for process ranges. Interprets the non-select (``!'') flag.
+ */
+static __inline int
+applies(struct s_command *cp)
+{
+ int r;
+
+ lastaddr = 0;
+ if (cp->a1 == NULL && cp->a2 == NULL)
+ r = 1;
+ else if (cp->a2)
+ if (cp->startline > 0) {
+ switch (cp->a2->type) {
+ case AT_RELLINE:
+ if (linenum - cp->startline <= cp->a2->u.l)
+ r = 1;
+ else {
+ cp->startline = 0;
+ r = 0;
+ }
+ break;
+ default:
+ if (MATCH(cp->a2)) {
+ cp->startline = 0;
+ lastaddr = 1;
+ r = 1;
+ } else if (cp->a2->type == AT_LINE &&
+ linenum > cp->a2->u.l) {
+ /*
+ * We missed the 2nd address due to a
+ * branch, so just close the range and
+ * return false.
+ */
+ cp->startline = 0;
+ r = 0;
+ } else
+ r = 1;
+ }
+ } else if (cp->a1 && MATCH(cp->a1)) {
+ /*
+ * If the second address is a number less than or
+ * equal to the line number first selected, only
+ * one line shall be selected.
+ * -- POSIX 1003.2
+ * Likewise if the relative second line address is zero.
+ */
+ if ((cp->a2->type == AT_LINE &&
+ linenum >= cp->a2->u.l) ||
+ (cp->a2->type == AT_RELLINE && cp->a2->u.l == 0))
+ lastaddr = 1;
+ else {
+ cp->startline = linenum;
+ }
+ r = 1;
+ } else
+ r = 0;
+ else
+ r = MATCH(cp->a1);
+ return (cp->nonsel ? ! r : r);
+}
+
+/*
+ * Reset the sed processor to its initial state.
+ */
+void
+resetstate(void)
+{
+ struct s_command *cp;
+
+ /*
+ * Reset all in-range markers.
+ */
+ for (cp = prog; cp; cp = cp->code == '{' ? cp->u.c : cp->next)
+ if (cp->a2)
+ cp->startline = 0;
+
+ /*
+ * Clear out the hold space.
+ */
+ cspace(&HS, "", 0, REPLACE);
+}
+
+/*
+ * substitute --
+ * Do substitutions in the pattern space. Currently, we build a
+ * copy of the new pattern space in the substitute space structure
+ * and then swap them.
+ */
+static int
+substitute(struct s_command *cp)
+{
+ SPACE tspace;
+ regex_t *re;
+ regoff_t re_off, slen;
+ int lastempty, n;
+ char *s;
+
+ s = ps;
+ re = cp->u.s->re;
+ if (re == NULL) {
+ if (defpreg != NULL && cp->u.s->maxbref > defpreg->re_nsub) {
+ linenum = cp->u.s->linenum;
+ errx(1, "%lu: %s: \\%u not defined in the RE",
+ linenum, fname, cp->u.s->maxbref);
+ }
+ }
+ if (!regexec_e(re, s, 0, 0, psl))
+ return (0);
+
+ SS.len = 0; /* Clean substitute space. */
+ slen = (regoff_t)psl;
+ n = cp->u.s->n;
+ lastempty = 1;
+
+ switch (n) {
+ case 0: /* Global */
+ do {
+ if (lastempty || match[0].rm_so != match[0].rm_eo) {
+ /* Locate start of replaced string. */
+ re_off = match[0].rm_so;
+ /* Copy leading retained string. */
+ cspace(&SS, s, (size_t)re_off, APPEND);
+ /* Add in regular expression. */
+ regsub(&SS, s, cp->u.s->new);
+ }
+
+ /* Move past this match. */
+ if (match[0].rm_so != match[0].rm_eo) {
+ s += match[0].rm_eo;
+ slen -= match[0].rm_eo;
+ lastempty = 0;
+ } else {
+ if (match[0].rm_so < slen)
+ cspace(&SS, s + match[0].rm_so, 1,
+ APPEND);
+ s += match[0].rm_so + 1;
+ slen -= match[0].rm_so + 1;
+ lastempty = 1;
+ }
+ } while (slen >= 0 && regexec_e(re, s, REG_NOTBOL, 0, (size_t)slen));
+ /* Copy trailing retained string. */
+ if (slen > 0)
+ cspace(&SS, s, (size_t)slen, APPEND);
+ break;
+ default: /* Nth occurrence */
+ while (--n) {
+ if (match[0].rm_eo == match[0].rm_so)
+ match[0].rm_eo = match[0].rm_so + 1;
+ s += match[0].rm_eo;
+ slen -= match[0].rm_eo;
+ if (slen < 0)
+ return (0);
+ if (!regexec_e(re, s, REG_NOTBOL, 0, (size_t)slen))
+ return (0);
+ }
+ /* FALLTHROUGH */
+ case 1: /* 1st occurrence */
+ /* Locate start of replaced string. */
+ re_off = match[0].rm_so + (s - ps);
+ /* Copy leading retained string. */
+ cspace(&SS, ps, (size_t)re_off, APPEND);
+ /* Add in regular expression. */
+ regsub(&SS, s, cp->u.s->new);
+ /* Copy trailing retained string. */
+ s += match[0].rm_eo;
+ slen -= match[0].rm_eo;
+ cspace(&SS, s, (size_t)slen, APPEND);
+ break;
+ }
+
+ /*
+ * Swap the substitute space and the pattern space, and make sure
+ * that any leftover pointers into stdio memory get lost.
+ */
+ tspace = PS;
+ PS = SS;
+ SS = tspace;
+ SS.space = SS.back;
+
+ /* Handle the 'p' flag. */
+ if (cp->u.s->p)
+ OUT();
+
+ /* Handle the 'w' flag. */
+ if (cp->u.s->wfile && !pd) {
+ if (cp->u.s->wfd == -1 && (cp->u.s->wfd = open(cp->u.s->wfile,
+ O_WRONLY|O_APPEND|O_CREAT|O_TRUNC, DEFFILEMODE)) == -1)
+ err(1, "%s", cp->u.s->wfile);
+ if (write(cp->u.s->wfd, ps, psl) != (ssize_t)psl ||
+ write(cp->u.s->wfd, "\n", 1) != 1)
+ err(1, "%s", cp->u.s->wfile);
+ }
+ return (1);
+}
+
+/*
+ * do_tr --
+ * Perform translation ('y' command) in the pattern space.
+ */
+static void
+do_tr(struct s_tr *y)
+{
+ SPACE tmp;
+ char c, *p;
+ size_t clen, left;
+ size_t i;
+
+ if (MB_CUR_MAX == 1) {
+ /*
+ * Single-byte encoding: perform in-place translation
+ * of the pattern space.
+ */
+ for (p = ps; p < &ps[psl]; p++)
+ *p = (char)y->bytetab[(u_char)*p];
+ } else {
+ /*
+ * Multi-byte encoding: perform translation into the
+ * translation space, then swap the translation and
+ * pattern spaces.
+ */
+ /* Clean translation space. */
+ YS.len = 0;
+ for (p = ps, left = psl; left > 0; p += clen, left -= clen) {
+ if ((c = (char)y->bytetab[(u_char)*p]) != '\0') {
+ cspace(&YS, &c, 1, APPEND);
+ clen = 1;
+ continue;
+ }
+ for (i = 0; i < y->nmultis; i++)
+ if (left >= y->multis[i].fromlen &&
+ memcmp(p, y->multis[i].from,
+ y->multis[i].fromlen) == 0)
+ break;
+ if (i < y->nmultis) {
+ cspace(&YS, y->multis[i].to,
+ y->multis[i].tolen, APPEND);
+ clen = y->multis[i].fromlen;
+ } else {
+ cspace(&YS, p, 1, APPEND);
+ clen = 1;
+ }
+ }
+ /* Swap the translation space and the pattern space. */
+ tmp = PS;
+ PS = YS;
+ YS = tmp;
+ YS.space = YS.back;
+ }
+}
+
+/*
+ * Flush append requests. Always called before reading a line,
+ * therefore it also resets the substitution done (sdone) flag.
+ */
+static void
+flush_appends(void)
+{
+ FILE *f;
+ size_t count, i;
+ char buf[8 * 1024];
+
+ for (i = 0; i < appendx; i++)
+ switch (appends[i].type) {
+ case AP_STRING:
+ fwrite(appends[i].s, sizeof(char), appends[i].len,
+ outfile);
+ break;
+ case AP_FILE:
+ /*
+ * Read files probably shouldn't be cached. Since
+ * it's not an error to read a non-existent file,
+ * it's possible that another program is interacting
+ * with the sed script through the filesystem. It
+ * would be truly bizarre, but possible. It's probably
+ * not that big a performance win, anyhow.
+ */
+ if ((f = fopen(appends[i].s, "r")) == NULL)
+ break;
+ while ((count = fread(buf, sizeof(char), sizeof(buf), f)))
+ (void)fwrite(buf, sizeof(char), count, outfile);
+ (void)fclose(f);
+ break;
+ }
+ if (ferror(outfile))
+ errx(1, "%s: %s", outfname, strerror(errno ? errno : EIO));
+ appendx = 0;
+ sdone = 0;
+}
+
+static void
+lputs(char *s, size_t len)
+{
+ static const char escapes[] = "\\\a\b\f\r\t\v";
+ int c;
+ size_t col, width;
+ const char *p;
+#ifdef TIOCGWINSZ
+ struct winsize win;
+#endif
+ static size_t termwidth = (size_t)-1;
+ size_t clen, i;
+ wchar_t wc;
+ mbstate_t mbs;
+
+ if (outfile != stdout)
+ termwidth = 60;
+ if (termwidth == (size_t)-1) {
+ if ((p = getenv("COLUMNS")) && *p != '\0')
+ termwidth = (size_t)atoi(p);
+#ifdef TIOCGWINSZ
+ else if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &win) == 0 &&
+ win.ws_col > 0)
+ termwidth = win.ws_col;
+#endif
+ else
+ termwidth = 60;
+ }
+ if (termwidth == 0)
+ termwidth = 1;
+
+ memset(&mbs, 0, sizeof(mbs));
+ col = 0;
+ while (len != 0) {
+ clen = mbrtowc(&wc, s, len, &mbs);
+ if (clen == 0)
+ clen = 1;
+ if (clen == (size_t)-1 || clen == (size_t)-2) {
+ wc = (unsigned char)*s;
+ clen = 1;
+ memset(&mbs, 0, sizeof(mbs));
+ }
+ if (wc == '\n') {
+ if (col + 1 >= termwidth)
+ fprintf(outfile, "\\\n");
+ fputc('$', outfile);
+ fputc('\n', outfile);
+ col = 0;
+ } else if (iswprint(wc)) {
+ width = (size_t)wcwidth(wc);
+ if (col + width >= termwidth) {
+ fprintf(outfile, "\\\n");
+ col = 0;
+ }
+ fwrite(s, 1, clen, outfile);
+ col += width;
+ } else if (wc != L'\0' && (c = wctob(wc)) != EOF &&
+ (p = strchr(escapes, c)) != NULL) {
+ if (col + 2 >= termwidth) {
+ fprintf(outfile, "\\\n");
+ col = 0;
+ }
+ fprintf(outfile, "\\%c", "\\abfrtv"[p - escapes]);
+ col += 2;
+ } else {
+ if (col + 4 * clen >= termwidth) {
+ fprintf(outfile, "\\\n");
+ col = 0;
+ }
+ for (i = 0; i < clen; i++)
+ fprintf(outfile, "\\%03o",
+ (int)(unsigned char)s[i]);
+ col += 4 * clen;
+ }
+ s += clen;
+ len -= clen;
+ }
+ if (col + 1 >= termwidth)
+ fprintf(outfile, "\\\n");
+ (void)fputc('$', outfile);
+ (void)fputc('\n', outfile);
+ if (ferror(outfile))
+ errx(1, "%s: %s", outfname, strerror(errno ? errno : EIO));
+}
+
+static __inline int
+regexec_e(regex_t *preg, const char *string, int eflags, int nomatch,
+ size_t slen)
+{
+ int eval;
+#ifndef REG_STARTEND
+ char *buf;
+#endif
+
+ if (preg == NULL) {
+ if (defpreg == NULL)
+ errx(1, "first RE may not be empty");
+ } else
+ defpreg = preg;
+
+ /* Set anchors */
+#ifndef REG_STARTEND
+ buf = xmalloc(slen + 1);
+ (void)memcpy(buf, string, slen);
+ buf[slen] = '\0';
+ eval = regexec(defpreg, buf,
+ nomatch ? 0 : maxnsub + 1, match, eflags);
+ free(buf);
+#else
+ match[0].rm_so = 0;
+ match[0].rm_eo = (regoff_t)slen;
+ eval = regexec(defpreg, string,
+ nomatch ? 0 : maxnsub + 1, match, eflags | REG_STARTEND);
+#endif
+ switch(eval) {
+ case 0:
+ return (1);
+ case REG_NOMATCH:
+ return (0);
+ }
+ errx(1, "RE error: %s", strregerror(eval, defpreg));
+ /* NOTREACHED */
+}
+
+/*
+ * regsub - perform substitutions after a regexp match
+ * Based on a routine by Henry Spencer
+ */
+static void
+regsub(SPACE *sp, char *string, char *src)
+{
+ size_t len;
+ int no;
+ char c, *dst;
+
+#define NEEDSP(reqlen) \
+ /* XXX What is the +1 for? */ \
+ if (sp->len + (reqlen) + 1 >= sp->blen) { \
+ sp->blen += (reqlen) + 1024; \
+ sp->space = sp->back = xrealloc(sp->back, sp->blen); \
+ dst = sp->space + sp->len; \
+ }
+
+ dst = sp->space + sp->len;
+ while ((c = *src++) != '\0') {
+ if (c == '&')
+ no = 0;
+ else if (c == '\\' && isdigit((unsigned char)*src))
+ no = *src++ - '0';
+ else
+ no = -1;
+ if (no < 0) { /* Ordinary character. */
+ if (c == '\\' && (*src == '\\' || *src == '&'))
+ c = *src++;
+ NEEDSP(1);
+ *dst++ = c;
+ ++sp->len;
+ } else if (match[no].rm_so != -1 && match[no].rm_eo != -1) {
+ len = (size_t)(match[no].rm_eo - match[no].rm_so);
+ NEEDSP(len);
+ memmove(dst, string + match[no].rm_so, len);
+ dst += len;
+ sp->len += len;
+ }
+ }
+ NEEDSP(1);
+ *dst = '\0';
+}
+
+/*
+ * cspace --
+ * Concatenate space: append the source space to the destination space,
+ * allocating new space as necessary.
+ */
+void
+cspace(SPACE *sp, const char *p, size_t len, enum e_spflag spflag)
+{
+ size_t tlen;
+
+ /* Make sure SPACE has enough memory and ramp up quickly. */
+ tlen = sp->len + len + 1;
+ if (tlen > sp->blen) {
+ sp->blen = tlen + 1024;
+ sp->space = sp->back = xrealloc(sp->back, sp->blen);
+ }
+
+ if (spflag == REPLACE)
+ sp->len = 0;
+
+ memmove(sp->space + sp->len, p, len);
+
+ sp->space[sp->len += len] = '\0';
+}
+
+/*
+ * Close all cached opened files and report any errors
+ */
+void
+cfclose(struct s_command *cp, struct s_command *end)
+{
+
+ for (; cp != end; cp = cp->next)
+ switch(cp->code) {
+ case 's':
+ if (cp->u.s->wfd != -1 && close(cp->u.s->wfd))
+ err(1, "%s", cp->u.s->wfile);
+ cp->u.s->wfd = -1;
+ break;
+ case 'w':
+ if (cp->u.fd != -1 && close(cp->u.fd))
+ err(1, "%s", cp->t);
+ cp->u.fd = -1;
+ break;
+ case '{':
+ cfclose(cp->u.c, cp->next);
+ break;
+ }
+}
diff --git a/usr.bin/sed/sed.1 b/usr.bin/sed/sed.1
new file mode 100644
index 0000000..82b3cbd
--- /dev/null
+++ b/usr.bin/sed/sed.1
@@ -0,0 +1,640 @@
+.\" $NetBSD: sed.1,v 1.40 2014/06/25 02:05:58 uwe Exp $
+.\" Copyright (c) 1992, 1993
+.\" The Regents of the University of California. All rights reserved.
+.\"
+.\" This code is derived from software contributed to Berkeley by
+.\" the Institute of Electrical and Electronics Engineers, Inc.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\" notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must reproduce the above copyright
+.\" notice, this list of conditions and the following disclaimer in the
+.\" documentation and/or other materials provided with the distribution.
+.\" 3. Neither the name of the University nor the names of its contributors
+.\" may be used to endorse or promote products derived from this software
+.\" without specific prior written permission.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+.\" ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+.\" SUCH DAMAGE.
+.\"
+.\" @(#)sed.1 8.2 (Berkeley) 12/30/93
+.\" $FreeBSD: head/usr.bin/sed/sed.1 259132 2013-12-09 18:57:20Z eadler $
+.\"
+.Dd June 18, 2014
+.Dt SED 1
+.Os
+.Sh NAME
+.Nm sed
+.Nd stream editor
+.Sh SYNOPSIS
+.Nm
+.Op Fl aElnru
+.Ar command
+.Op Ar
+.Nm
+.Op Fl aElnru
+.Op Fl e Ar command
+.Op Fl f Ar command_file
+.Op Fl I Ns Op Ar extension
+.Op Fl i Ns Op Ar extension
+.Op Ar
+.Sh DESCRIPTION
+The
+.Nm
+utility reads the specified files, or the standard input if no files
+are specified, modifying the input as specified by a list of commands.
+The input is then written to the standard output.
+.Pp
+A single command may be specified as the first argument to
+.Nm .
+Multiple commands may be specified by using the
+.Fl e
+or
+.Fl f
+options.
+All commands are applied to the input in the order they are specified
+regardless of their origin.
+.Pp
+The following options are available:
+.Bl -tag -width indent
+.It Fl a
+The files listed as parameters for the
+.Dq w
+functions are created (or truncated) before any processing begins,
+by default.
+The
+.Fl a
+option causes
+.Nm
+to delay opening each file until a command containing the related
+.Dq w
+function is applied to a line of input.
+.It Fl E
+Interpret regular expressions as extended (modern) regular expressions
+rather than basic regular expressions (BRE's).
+The
+.Xr re_format 7
+manual page fully describes both formats.
+.It Fl e Ar command
+Append the editing commands specified by the
+.Ar command
+argument
+to the list of commands.
+.It Fl f Ar command_file
+Append the editing commands found in the file
+.Ar command_file
+to the list of commands.
+The editing commands should each be listed on a separate line.
+.It Fl I Ns Op Ar extension
+Edit files in-place, saving backups with the specified
+.Ar extension .
+If no
+.Ar extension
+is given, no backup will be saved.
+It is not recommended to give a zero-length
+.Ar extension
+when in-place editing files, as you risk corruption or partial content
+in situations where disk space is exhausted, etc.
+.Pp
+Note that in-place editing with
+.Fl I
+still takes place in a single continuous line address space covering
+all files, although each file preserves its individuality instead of
+forming one output stream.
+The line counter is never reset between files, address ranges can span
+file boundaries, and the
+.Dq $
+address matches only the last line of the last file.
+(See
+.Sx "Sed Addresses" . )
+That can lead to unexpected results in many cases of in-place editing,
+where using
+.Fl i
+is desired.
+.It Fl i Ns Op Ar extension
+Edit files in-place similarly to
+.Fl I ,
+but treat each file independently from other files.
+In particular, line numbers in each file start at 1,
+the
+.Dq $
+address matches the last line of the current file,
+and address ranges are limited to the current file.
+(See
+.Sx "Sed Addresses" . )
+The net result is as though each file were edited by a separate
+.Nm
+instance.
+.It Fl l
+Make output line buffered.
+.It Fl n
+By default, each line of input is echoed to the standard output after
+all of the commands have been applied to it.
+The
+.Fl n
+option suppresses this behavior.
+.It Fl r
+Same as
+.Fl E
+for compatibility with GNU sed.
+.It Fl u
+Make output unbuffered.
+.El
+.Pp
+The form of a
+.Nm
+command is as follows:
+.Pp
+.Dl [address[,address]]function[arguments]
+.Pp
+Whitespace may be inserted before the first address and the function
+portions of the command.
+.Pp
+Normally,
+.Nm
+cyclically copies a line of input, not including its terminating newline
+character, into a
+.Em "pattern space" ,
+(unless there is something left after a
+.Dq D
+function),
+applies all of the commands with addresses that select that pattern space,
+copies the pattern space to the standard output, appending a newline, and
+deletes the pattern space.
+.Pp
+Some of the functions use a
+.Em "hold space"
+to save all or part of the pattern space for subsequent retrieval.
+.Ss "Sed Addresses"
+An address is not required, but if specified must have one of the
+following formats:
+.Bl -bullet -offset indent
+.It
+a number that counts
+input lines
+cumulatively across input files (or in each file independently
+if a
+.Fl i
+option is in effect);
+.It
+a dollar
+.Pq Dq $
+character that addresses the last line of input (or the last line
+of the current file if a
+.Fl i
+option was specified);
+.It
+a context address
+that consists of a regular expression preceded and followed by a
+delimiter.
+The closing delimiter can also optionally be followed by the
+.Dq i
+character, to indicate that the regular expression is to be matched
+in a case-insensitive way.
+.El
+.Pp
+A command line with no addresses selects every pattern space.
+.Pp
+A command line with one address selects all of the pattern spaces
+that match the address.
+.Pp
+A command line with two addresses selects an inclusive range.
+This
+range starts with the first pattern space that matches the first
+address.
+The end of the range is the next following pattern space
+that matches the second address.
+If the second address is a number
+less than or equal to the line number first selected, only that
+line is selected.
+The number in the second address may be prefixed with a
+.Pq Dq \&+
+to specify the number of lines to match after the first pattern.
+In the case when the second address is a context
+address,
+.Nm
+does not re-match the second address against the
+pattern space that matched the first address.
+Starting at the
+first line following the selected range,
+.Nm
+starts looking again for the first address.
+.Pp
+Editing commands can be applied to non-selected pattern spaces by use
+of the exclamation character
+.Pq Dq \&!
+function.
+.Ss "Sed Regular Expressions"
+The regular expressions used in
+.Nm ,
+by default, are basic regular expressions (BREs, see
+.Xr re_format 7
+for more information), but extended (modern) regular expressions can be used
+instead if the
+.Fl E
+flag is given.
+In addition,
+.Nm
+has the following two additions to regular expressions:
+.Pp
+.Bl -enum -compact
+.It
+In a context address, any character other than a backslash
+.Pq Dq \e
+or newline character may be used to delimit the regular expression.
+The opening delimiter needs to be preceded by a backslash
+unless it is a slash.
+For example, the context address
+.Li \exabcx
+is equivalent to
+.Li /abc/ .
+Also, putting a backslash character before the delimiting character
+within the regular expression causes the character to be treated literally.
+For example, in the context address
+.Li \exabc\exdefx ,
+the RE delimiter is an
+.Dq x
+and the second
+.Dq x
+stands for itself, so that the regular expression is
+.Dq abcxdef .
+.Pp
+.It
+The escape sequence \en matches a newline character embedded in the
+pattern space.
+You cannot, however, use a literal newline character in an address or
+in the substitute command.
+.El
+.Pp
+One special feature of
+.Nm
+regular expressions is that they can default to the last regular
+expression used.
+If a regular expression is empty, i.e., just the delimiter characters
+are specified, the last regular expression encountered is used instead.
+The last regular expression is defined as the last regular expression
+used as part of an address or substitute command, and at run-time, not
+compile-time.
+For example, the command
+.Dq /abc/s//XXX/
+will substitute
+.Dq XXX
+for the pattern
+.Dq abc .
+.Ss "Sed Functions"
+In the following list of commands, the maximum number of permissible
+addresses for each command is indicated by [0addr], [1addr], or [2addr],
+representing zero, one, or two addresses.
+.Pp
+The argument
+.Em text
+consists of one or more lines.
+To embed a newline in the text, precede it with a backslash.
+Other backslashes in text are deleted and the following character
+taken literally.
+.Pp
+The
+.Dq r
+and
+.Dq w
+functions take an optional file parameter, which should be separated
+from the function letter by white space.
+Each file given as an argument to
+.Nm
+is created (or its contents truncated) before any input processing begins.
+.Pp
+The
+.Dq b ,
+.Dq r ,
+.Dq s ,
+.Dq t ,
+.Dq w ,
+.Dq y ,
+.Dq \&! ,
+and
+.Dq \&:
+functions all accept additional arguments.
+The following synopses indicate which arguments have to be separated from
+the function letters by white space characters.
+.Pp
+Two of the functions take a function-list.
+This is a list of
+.Nm
+functions separated by newlines, as follows:
+.Bd -literal -offset indent
+{ function
+ function
+ ...
+ function
+}
+.Ed
+.Pp
+The
+.Dq {
+can be preceded by white space and can be followed by white space.
+The function can be preceded by white space.
+The terminating
+.Dq }
+must be preceded by a newline, and may also be preceded by white space.
+.Pp
+.Bl -tag -width "XXXXXX" -compact
+.It [2addr] function-list
+Execute function-list only when the pattern space is selected.
+.Pp
+.It [1addr]a\e
+.It text
+Write
+.Em text
+to standard output immediately before each attempt to read a line of input,
+whether by executing the
+.Dq N
+function or by beginning a new cycle.
+.Pp
+.It [2addr]b[label]
+Branch to the
+.Dq \&:
+function with the specified label.
+If the label is not specified, branch to the end of the script.
+.Pp
+.It [2addr]c\e
+.It text
+Delete the pattern space.
+With 0 or 1 address or at the end of a 2-address range,
+.Em text
+is written to the standard output.
+.Pp
+.It [2addr]d
+Delete the pattern space and start the next cycle.
+.Pp
+.It [2addr]D
+Delete the initial segment of the pattern space through the first
+newline character and start the next cycle.
+.Pp
+.It [2addr]g
+Replace the contents of the pattern space with the contents of the
+hold space.
+.Pp
+.It [2addr]G
+Append a newline character followed by the contents of the hold space
+to the pattern space.
+.Pp
+.It [2addr]h
+Replace the contents of the hold space with the contents of the
+pattern space.
+.Pp
+.It [2addr]H
+Append a newline character followed by the contents of the pattern space
+to the hold space.
+.Pp
+.It [1addr]i\e
+.It text
+Write
+.Em text
+to the standard output.
+.Pp
+.It [2addr]l
+(The letter ell.)
+Write the pattern space to the standard output in a visually unambiguous
+form.
+This form is as follows:
+.Pp
+.Bl -tag -width "carriage-returnXX" -offset indent -compact
+.It backslash
+\e\e
+.It alert
+\ea
+.It form-feed
+\ef
+.It carriage-return
+\er
+.It tab
+\et
+.It vertical tab
+\ev
+.El
+.Pp
+Nonprintable characters are written as three-digit octal numbers (with a
+preceding backslash) for each byte in the character (most significant byte
+first).
+Long lines are folded, with the point of folding indicated by displaying
+a backslash followed by a newline.
+The end of each line is marked with a
+.Dq $ .
+.Pp
+.It [2addr]n
+Write the pattern space to the standard output if the default output has
+not been suppressed, and replace the pattern space with the next line of
+input.
+.Pp
+.It [2addr]N
+Append the next line of input to the pattern space, using an embedded
+newline character to separate the appended material from the original
+contents.
+Note that the current line number changes.
+.Pp
+.It [2addr]p
+Write the pattern space to standard output.
+.Pp
+.It [2addr]P
+Write the pattern space, up to the first newline character to the
+standard output.
+.Pp
+.It [1addr]q
+Branch to the end of the script and quit without starting a new cycle.
+.Pp
+.It [1addr]r file
+Copy the contents of
+.Em file
+to the standard output immediately before the next attempt to read a
+line of input.
+If
+.Em file
+cannot be read for any reason, it is silently ignored and no error
+condition is set.
+.Pp
+.It [2addr]s/regular expression/replacement/flags
+Substitute the replacement string for the first instance of the regular
+expression in the pattern space.
+Any character other than backslash or newline can be used instead of
+a slash to delimit the RE and the replacement.
+Within the RE and the replacement, the RE delimiter itself can be used as
+a literal character if it is preceded by a backslash.
+.Pp
+An ampersand
+.Pq Dq &
+appearing in the replacement is replaced by the string matching the RE.
+The special meaning of
+.Dq &
+in this context can be suppressed by preceding it by a backslash.
+The string
+.Dq \e# ,
+where
+.Dq #
+is a digit, is replaced by the text matched
+by the corresponding backreference expression (see
+.Xr re_format 7 ) .
+.Pp
+A line can be split by substituting a newline character into it.
+To specify a newline character in the replacement string, precede it with
+a backslash.
+.Pp
+The value of
+.Em flags
+in the substitute function is zero or more of the following:
+.Bl -tag -width "XXXXXX" -offset indent
+.It Ar N
+Make the substitution only for the
+.Ar N Ns 'th
+occurrence of the regular expression in the pattern space.
+.It g
+Make the substitution for all non-overlapping matches of the
+regular expression, not just the first one.
+.It p
+Write the pattern space to standard output if a replacement was made.
+If the replacement string is identical to that which it replaces, it
+is still considered to have been a replacement.
+.It w Em file
+Append the pattern space to
+.Em file
+if a replacement was made.
+If the replacement string is identical to that which it replaces, it
+is still considered to have been a replacement.
+.It i or I
+Match the regular expression in a case-insensitive way.
+.El
+.Pp
+.It [2addr]t [label]
+Branch to the
+.Dq \&:
+function bearing the label if any substitutions have been made since the
+most recent reading of an input line or execution of a
+.Dq t
+function.
+If no label is specified, branch to the end of the script.
+.Pp
+.It [2addr]w Em file
+Append the pattern space to the
+.Em file .
+.Pp
+.It [2addr]x
+Swap the contents of the pattern and hold spaces.
+.Pp
+.It [2addr]y/string1/string2/
+Replace all occurrences of characters in
+.Em string1
+in the pattern space with the corresponding characters from
+.Em string2 .
+Any character other than a backslash or newline can be used instead of
+a slash to delimit the strings.
+Within
+.Em string1
+and
+.Em string2 ,
+a backslash followed by any character other than a newline is that literal
+character, and a backslash followed by an ``n'' is replaced by a newline
+character.
+.Pp
+.It [2addr]!function
+.It [2addr]!function-list
+Apply the function or function-list only to the lines that are
+.Em not
+selected by the address(es).
+.Pp
+.It [0addr]:label
+This function does nothing; it bears a label to which the
+.Dq b
+and
+.Dq t
+commands may branch.
+.Pp
+.It [1addr]=
+Write the line number to the standard output followed by a newline
+character.
+.Pp
+.It [0addr]
+Empty lines are ignored.
+.Pp
+.It [0addr]#
+The
+.Dq #
+and the remainder of the line are ignored (treated as a comment), with
+the single exception that if the first two characters in the file are
+.Dq #n ,
+the default output is suppressed.
+This is the same as specifying the
+.Fl n
+option on the command line.
+.El
+.Sh ENVIRONMENT
+The
+.Ev COLUMNS , LANG , LC_ALL , LC_CTYPE
+and
+.Ev LC_COLLATE
+environment variables affect the execution of
+.Nm
+as described in
+.Xr environ 7 .
+.Sh EXIT STATUS
+.Ex -std
+.Sh SEE ALSO
+.Xr awk 1 ,
+.Xr ed 1 ,
+.Xr grep 1 ,
+.Xr regex 3 ,
+.Xr re_format 7
+.Sh STANDARDS
+The
+.Nm
+utility is expected to be a superset of the
+.St -p1003.2
+specification.
+.Pp
+The
+.Fl a , E , I ,
+and
+.Fl i
+options, the prefixing
+.Dq \&+
+in the second member of an address range,
+as well as the
+.Dq I
+flag to the address regular expression and substitution command are
+non-standard
+.Fx
+extensions and may not be available on other operating systems.
+.Sh HISTORY
+A
+.Nm
+command, written by
+.An L. E. McMahon ,
+appeared in
+.At v7 .
+.Sh AUTHORS
+.An "Diomidis D. Spinellis" Aq dds@FreeBSD.org
+.Sh BUGS
+Multibyte characters containing a byte with value 0x5C
+.Tn ( ASCII
+.Ql \e )
+may be incorrectly treated as line continuation characters in arguments to the
+.Dq a ,
+.Dq c
+and
+.Dq i
+commands.
+Multibyte characters cannot be used as delimiters with the
+.Dq s
+and
+.Dq y
+commands.
diff --git a/usr.bin/sort/append.c b/usr.bin/sort/append.c
new file mode 100644
index 0000000..07e15e1
--- /dev/null
+++ b/usr.bin/sort/append.c
@@ -0,0 +1,94 @@
+/* $NetBSD: append.c,v 1.23 2009/11/06 18:34:22 joerg Exp $ */
+
+/*-
+ * Copyright (c) 2000-2003 The NetBSD Foundation, Inc.
+ * All rights reserved.
+ *
+ * This code is derived from software contributed to The NetBSD Foundation
+ * by Ben Harris and Jaromir Dolecek.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
+ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/*-
+ * Copyright (c) 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Peter McIlroy.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include "sort.h"
+
+__RCSID("$NetBSD: append.c,v 1.23 2009/11/06 18:34:22 joerg Exp $");
+
+#include <stdlib.h>
+
+/*
+ * copy sorted lines to output
+ * Ignore duplicates (marked with -ve keylen)
+ */
+void
+append(RECHEADER **keylist, int nelem, FILE *fp, put_func_t put)
+{
+ RECHEADER **cpos, **lastkey;
+ RECHEADER *crec;
+
+ lastkey = keylist + nelem;
+ if (REVERSE) {
+ for (cpos = lastkey; cpos-- > keylist;) {
+ crec = *cpos;
+ if (crec->keylen >= 0)
+ put(crec, fp);
+ }
+ } else {
+ for (cpos = keylist; cpos < lastkey; cpos++) {
+ crec = *cpos;
+ if (crec->keylen >= 0)
+ put(crec, fp);
+ }
+ }
+}
diff --git a/usr.bin/sort/fields.c b/usr.bin/sort/fields.c
new file mode 100644
index 0000000..6208a78
--- /dev/null
+++ b/usr.bin/sort/fields.c
@@ -0,0 +1,380 @@
+/* $NetBSD: fields.c,v 1.33 2013/01/20 10:12:58 apb Exp $ */
+
+/*-
+ * Copyright (c) 2000-2003 The NetBSD Foundation, Inc.
+ * All rights reserved.
+ *
+ * This code is derived from software contributed to The NetBSD Foundation
+ * by Ben Harris and Jaromir Dolecek.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
+ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/*-
+ * Copyright (c) 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Peter McIlroy.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+/* Subroutines to generate sort keys. */
+
+#include "sort.h"
+
+__RCSID("$NetBSD: fields.c,v 1.33 2013/01/20 10:12:58 apb Exp $");
+
+#define SKIP_BLANKS(ptr) { \
+ if (BLANK & d_mask[*(ptr)]) \
+ while (BLANK & d_mask[*(++(ptr))]); \
+}
+
+#define NEXTCOL(pos) { \
+ if (!SEP_FLAG) \
+ while (BLANK & l_d_mask[*(++pos)]); \
+ while ((*(pos+1) != '\0') && !((FLD_D | REC_D_F) & l_d_mask[*++pos]));\
+}
+
+static u_char *enterfield(u_char *, const u_char *, struct field *, int);
+static u_char *number(u_char *, const u_char *, u_char *, u_char *, int);
+static u_char *length(u_char *, const u_char *, u_char *, u_char *, int);
+
+#define DECIMAL_POINT '.'
+
+/*
+ * constructs sort key with leading recheader, followed by the key,
+ * followed by the original line.
+ */
+length_t
+enterkey(RECHEADER *keybuf, const u_char *keybuf_end, u_char *line_data,
+ size_t line_size, struct field fieldtable[])
+ /* keybuf: pointer to start of key */
+{
+ int i;
+ u_char *l_d_mask;
+ u_char *lineend, *pos;
+ const u_char *endkey;
+ u_char *keypos;
+ struct coldesc *clpos;
+ int col = 1;
+ struct field *ftpos;
+
+ l_d_mask = d_mask;
+ pos = line_data - 1;
+ lineend = line_data + line_size-1;
+ /* don't include rec_delimiter */
+
+ for (i = 0; i < ncols; i++) {
+ clpos = clist + i;
+ for (; (col < clpos->num) && (pos < lineend); col++) {
+ NEXTCOL(pos);
+ }
+ if (pos >= lineend)
+ break;
+ clpos->start = SEP_FLAG ? pos + 1 : pos;
+ NEXTCOL(pos);
+ clpos->end = pos;
+ col++;
+ if (pos >= lineend) {
+ clpos->end = lineend;
+ i++;
+ break;
+ }
+ }
+ for (; i <= ncols; i++)
+ clist[i].start = clist[i].end = lineend;
+ if (clist[0].start < line_data)
+ clist[0].start++;
+
+ /*
+ * We write the sort keys (concatenated) followed by the
+ * original line data (for output) as the 'keybuf' data.
+ * keybuf->length is the number of key bytes + data bytes.
+ * keybuf->offset is the number of key bytes.
+ * We add a record separator weight after the key in case
+ * (as is usual) we need to preserve the order of equal lines,
+ * and for 'sort -u'.
+ * The key itself will have had the correct weight applied.
+ */
+ keypos = keybuf->data;
+ endkey = keybuf_end - line_size - 1;
+ if (endkey <= keypos)
+ /* No room for any key bytes */
+ return 1;
+
+ for (ftpos = fieldtable + 1; ftpos->icol.num; ftpos++) {
+ if ((keypos = enterfield(keypos, endkey, ftpos,
+ fieldtable->flags)) == NULL)
+ return (1);
+ }
+
+ keybuf->offset = keypos - keybuf->data;
+ keybuf->length = keybuf->offset + line_size;
+
+ /*
+ * Posix requires that equal keys be further sorted by the
+ * entire original record.
+ * NetBSD has (at least for some time) kept equal keys in
+ * their original order.
+ * For 'sort -u' posix_sort is unset.
+ */
+ keybuf->keylen = posix_sort ? keybuf->length : keybuf->offset;
+
+ memcpy(keypos, line_data, line_size);
+ return (0);
+}
+
+/*
+ * constructs a field (as defined by -k) within a key
+ */
+static u_char *
+enterfield(u_char *tablepos, const u_char *endkey, struct field *cur_fld,
+ int gflags)
+{
+ u_char *start, *end, *lineend, *mask, *lweight;
+ struct column icol, tcol;
+ u_int flags;
+
+ icol = cur_fld->icol;
+ tcol = cur_fld->tcol;
+ flags = cur_fld->flags;
+ start = icol.p->start;
+ lineend = clist[ncols].end;
+ if (flags & BI)
+ SKIP_BLANKS(start);
+ start += icol.indent;
+ start = min(start, lineend);
+
+ if (!tcol.num)
+ end = lineend;
+ else {
+ if (tcol.indent) {
+ end = tcol.p->start;
+ if (flags & BT)
+ SKIP_BLANKS(end);
+ end += tcol.indent;
+ end = min(end, lineend);
+ } else
+ end = tcol.p->end;
+ }
+
+ if (flags & L)
+ return length(tablepos, endkey, start, end, flags);
+ if (flags & N)
+ return number(tablepos, endkey, start, end, flags);
+
+ /* Bound check space - assuming nothing is skipped */
+ if (tablepos + (end - start) + 1 >= endkey)
+ return NULL;
+
+ mask = cur_fld->mask;
+ lweight = cur_fld->weights;
+ for (; start < end; start++) {
+ if (!mask || mask[*start]) {
+ *tablepos++ = lweight[*start];
+ }
+ }
+ /* Add extra byte (absent from lweight) to sort short keys correctly */
+ *tablepos++ = lweight[REC_D];
+ return tablepos;
+}
+
+/*
+ * Numbers are converted to a floating point format (exponent & mantissa)
+ * so that they compare correctly as sequence of unsigned bytes.
+ * Bytes 0x00 and 0xff are used to terminate positive and negative numbers
+ * to ensure that 0.123 sorts after 0.12 and -0.123 sorts before -0.12.
+ *
+ * The first byte contain the overall sign, exponent sign and some of the
+ * exponent. These have to be ordered (-ve value, decreasing exponent),
+ * zero, (+ve value, increasing exponent).
+ *
+ * The first byte is 0x80 for zero, 0xc0 for +ve with exponent 0.
+ * -ve values are the 1's compliments (so 0x7f isn't used!).
+ *
+ * This only leaves 63 byte values for +ve exponents - which isn't enough.
+ * The largest 4 exponent values are used to hold a byte count of the
+ * number of following bytes that contain 8 exponent bits per byte,
+ * This lets us sort exponents from -2^31 to +2^31.
+ *
+ * The mantissa is stored 2 digits per byte offset by 0x40, for negative
+ * numbers the order must be reversed (they are bit inverted).
+ *
+ * Reverse sorts are done by inverting the sign of the number.
+ */
+#define MAX_EXP_ENC ((int)sizeof(int))
+
+static u_char *
+number(u_char *pos, const u_char *bufend, u_char *line, u_char *lineend,
+ int reverse)
+{
+ int exponent = -1;
+ int had_dp = 0;
+ u_char *tline;
+ char ch;
+ unsigned int val;
+ u_char *last_nz_pos;
+ u_char negate;
+
+ if (reverse & R)
+ negate = 0xff;
+ else
+ negate = 0;
+
+ /* Give ourselves space for the key terminator */
+ bufend--;
+
+ /* Ensure we have enough space for the exponent */
+ if (pos + 1 + MAX_EXP_ENC > bufend)
+ return (NULL);
+
+ SKIP_BLANKS(line);
+ if (*line == '-') { /* set the sign */
+ negate ^= 0xff;
+ line++;
+ } else if (*line == '+') {
+ line++;
+ }
+
+ /* eat initial zeroes */
+ for (; *line == '0' && line < lineend; line++)
+ continue;
+
+ /* calculate exponents */
+ if (*line == DECIMAL_POINT) {
+ /* Decimal fraction */
+ had_dp = 1;
+ while (*++line == '0' && line < lineend)
+ exponent--;
+ } else {
+ /* Large (absolute) value, count digits */
+ for (tline = line; *tline >= '0' &&
+ *tline <= '9' && tline < lineend; tline++)
+ exponent++;
+ }
+
+ /* If the first/next character isn't a digit, value is zero */
+ if (*line < '1' || *line > '9' || line >= lineend) {
+ /* This may be "0", "0.00", "000" or "fubar" but sorts as 0 */
+ /* XXX what about NaN, NAN, inf and INF */
+ *pos++ = 0x80;
+ return pos;
+ }
+
+ /* Maybe here we should allow for e+12 (etc) */
+
+ if (exponent < 0x40 - MAX_EXP_ENC && -exponent < 0x40 - MAX_EXP_ENC) {
+ /* Value ok for simple encoding */
+ /* exponent 0 is 0xc0 for +ve numbers and 0x40 for -ve ones */
+ exponent += 0xc0;
+ *pos++ = negate ^ exponent;
+ } else {
+ /* Out or range for a single byte */
+ int c, t;
+ t = exponent > 0 ? exponent : -exponent;
+ /* Count how many 8-bit bytes are needed */
+ for (c = 0; ; c++) {
+ t >>= 8;
+ if (t == 0)
+ break;
+ }
+ /* 'c' better be 0..3 here - but probably 0..1 */
+ /* Offset just outside valid range */
+ t = c + 0x40 - MAX_EXP_ENC;
+ if (exponent < 0)
+ t = -t;
+ *pos++ = negate ^ (t + 0xc0);
+ /* now add each byte, most significant first */
+ for (; c >= 0; c--)
+ *pos++ = negate ^ (exponent >> (c * 8));
+ }
+
+ /* Finally add mantissa, 2 digits per byte */
+ for (last_nz_pos = pos; line < lineend; ) {
+ if (pos >= bufend)
+ return NULL;
+ ch = *line++;
+ val = (ch - '0') * 10;
+ if (val > 90) {
+ if (ch == DECIMAL_POINT && !had_dp) {
+ had_dp = 1;
+ continue;
+ }
+ break;
+ }
+ while (line < lineend) {
+ ch = *line++;
+ if (ch == DECIMAL_POINT && !had_dp) {
+ had_dp = 1;
+ continue;
+ }
+ if (ch < '0' || ch > '9')
+ line = lineend;
+ else
+ val += ch - '0';
+ break;
+ }
+ *pos++ = negate ^ (val + 0x40);
+ if (val != 0)
+ last_nz_pos = pos;
+ }
+
+ /* Add key terminator, deleting any trailing "00" */
+ *last_nz_pos++ = negate;
+
+ return (last_nz_pos);
+}
+
+static u_char *
+length(u_char *pos, const u_char *bufend, u_char *line, u_char *lineend,
+ int flag)
+{
+ u_char buf[32];
+ int l;
+ SKIP_BLANKS(line);
+ l = snprintf((char *)buf, sizeof(buf), "%td", lineend - line);
+ return number(pos, bufend, buf, buf + l, flag);
+}
diff --git a/usr.bin/sort/files.c b/usr.bin/sort/files.c
new file mode 100644
index 0000000..7cb27c5
--- /dev/null
+++ b/usr.bin/sort/files.c
@@ -0,0 +1,279 @@
+/* $NetBSD: files.c,v 1.42 2015/08/05 07:10:03 mrg Exp $ */
+
+/*-
+ * Copyright (c) 2000-2003 The NetBSD Foundation, Inc.
+ * All rights reserved.
+ *
+ * This code is derived from software contributed to The NetBSD Foundation
+ * by Ben Harris and Jaromir Dolecek.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
+ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/*-
+ * Copyright (c) 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Peter McIlroy.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include "sort.h"
+#include "fsort.h"
+
+__RCSID("$NetBSD: files.c,v 1.42 2015/08/05 07:10:03 mrg Exp $");
+
+#include <string.h>
+
+/* Align records in temporary files to avoid misaligned copies */
+#define REC_ROUNDUP(n) (((n) + sizeof (long) - 1) & ~(sizeof (long) - 1))
+
+static ssize_t seq(FILE *, u_char **);
+
+/*
+ * this is called when there is no special key. It's only called
+ * in the first fsort pass.
+ */
+
+static u_char *opos;
+static size_t osz;
+
+void
+makeline_copydown(RECHEADER *recbuf)
+{
+ memmove(recbuf->data, opos, osz);
+}
+
+int
+makeline(FILE *fp, RECHEADER *recbuf, u_char *bufend, struct field *dummy2)
+{
+ u_char *pos;
+ int c;
+
+ pos = recbuf->data;
+ if (osz != 0) {
+ /*
+ * Buffer shortage is solved by either of two ways:
+ * o flush previous buffered data and start using the
+ * buffer from start.
+ * makeline_copydown() above must be called.
+ * o realloc buffer
+ *
+ * This code has relied on realloc changing 'bufend',
+ * but that isn't necessarily true.
+ */
+ pos += osz;
+ osz = 0;
+ }
+
+ while (pos < bufend) {
+ c = getc(fp);
+ if (c == EOF) {
+ if (pos == recbuf->data) {
+ FCLOSE(fp);
+ return EOF;
+ }
+ /* Add terminator to partial line */
+ c = REC_D;
+ }
+ *pos++ = c;
+ if (c == REC_D) {
+ recbuf->offset = 0;
+ recbuf->length = pos - recbuf->data;
+ recbuf->keylen = recbuf->length - 1;
+ return (0);
+ }
+ }
+
+ /* Ran out of buffer space... */
+ if (recbuf->data < bufend) {
+ /* Remember where the partial record is */
+ osz = pos - recbuf->data;
+ opos = recbuf->data;
+ }
+ return (BUFFEND);
+}
+
+/*
+ * This generates keys. It's only called in the first fsort pass
+ */
+int
+makekey(FILE *fp, RECHEADER *recbuf, u_char *bufend, struct field *ftbl)
+{
+ static u_char *line_data;
+ static ssize_t line_size;
+ static int overflow = 0;
+
+ /* We get re-entered after returning BUFFEND - save old data */
+ if (overflow) {
+ overflow = enterkey(recbuf, bufend, line_data, line_size, ftbl);
+ return overflow ? BUFFEND : 0;
+ }
+
+ line_size = seq(fp, &line_data);
+ if (line_size == 0) {
+ FCLOSE(fp);
+ return EOF;
+ }
+
+ if (line_size > bufend - recbuf->data) {
+ overflow = 1;
+ } else {
+ overflow = enterkey(recbuf, bufend, line_data, line_size, ftbl);
+ }
+ return overflow ? BUFFEND : 0;
+}
+
+/*
+ * get a line of input from fp
+ */
+static ssize_t
+seq(FILE *fp, u_char **line)
+{
+ static u_char *buf;
+ static size_t buf_size = DEFLLEN;
+ u_char *end, *pos;
+ int c;
+ u_char *new_buf;
+
+ if (!buf) {
+ /* one-time initialization */
+ buf = malloc(buf_size);
+ if (!buf)
+ err(2, "malloc of linebuf for %zu bytes failed",
+ buf_size);
+ }
+
+ end = buf + buf_size;
+ pos = buf;
+ while ((c = getc(fp)) != EOF) {
+ *pos++ = c;
+ if (c == REC_D) {
+ *line = buf;
+ return pos - buf;
+ }
+ if (pos == end) {
+ /* Long line - double size of buffer */
+ /* XXX: Check here for stupidly long lines */
+ buf_size *= 2;
+ new_buf = realloc(buf, buf_size);
+ if (!new_buf)
+ err(2, "realloc of linebuf to %zu bytes failed",
+ buf_size);
+
+ end = new_buf + buf_size;
+ pos = new_buf + (pos - buf);
+ buf = new_buf;
+ }
+ }
+
+ if (pos != buf) {
+ /* EOF part way through line - add line terminator */
+ *pos++ = REC_D;
+ *line = buf;
+ return pos - buf;
+ }
+
+ return 0;
+}
+
+/*
+ * write a key/line pair to a temporary file
+ */
+void
+putrec(const RECHEADER *rec, FILE *fp)
+{
+ EWRITE(rec, 1, REC_ROUNDUP(offsetof(RECHEADER, data) + rec->length), fp,
+ "failed to write temp file");
+}
+
+/*
+ * write a line to output
+ */
+void
+putline(const RECHEADER *rec, FILE *fp)
+{
+ EWRITE(rec->data+rec->offset, 1, rec->length - rec->offset, fp,
+ "failed to write");
+}
+
+/*
+ * write dump of key to output (for -Dk)
+ */
+void
+putkeydump(const RECHEADER *rec, FILE *fp)
+{
+ EWRITE(rec, 1, REC_ROUNDUP(offsetof(RECHEADER, data) + rec->offset), fp,
+ "failed to write debug key");
+}
+
+/*
+ * get a record from a temporary file. (Used by merge sort.)
+ */
+int
+geteasy(FILE *fp, RECHEADER *rec, u_char *end, struct field *dummy2)
+{
+ length_t file_len;
+ int i;
+
+ (void)sizeof (char[offsetof(RECHEADER, length) == 0 ? 1 : -1]);
+
+ if ((u_char *)(rec + 1) > end)
+ return (BUFFEND);
+ if (!fread(&rec->length, 1, sizeof rec->length, fp)) {
+ fclose(fp);
+ return (EOF);
+ }
+ file_len = REC_ROUNDUP(offsetof(RECHEADER, data) + rec->length);
+ if (end - rec->data < (ptrdiff_t)file_len) {
+ for (i = sizeof rec->length - 1; i >= 0; i--)
+ ungetc(*((char *) rec + i), fp);
+ return (BUFFEND);
+ }
+
+ fread(&rec->length + 1, file_len - sizeof rec->length, 1, fp);
+ return (0);
+}
diff --git a/usr.bin/sort/fsort.c b/usr.bin/sort/fsort.c
new file mode 100644
index 0000000..c229ae2
--- /dev/null
+++ b/usr.bin/sort/fsort.c
@@ -0,0 +1,202 @@
+/* $NetBSD: fsort.c,v 1.47 2010/02/05 21:58:41 enami Exp $ */
+
+/*-
+ * Copyright (c) 2000-2003 The NetBSD Foundation, Inc.
+ * All rights reserved.
+ *
+ * This code is derived from software contributed to The NetBSD Foundation
+ * by Ben Harris and Jaromir Dolecek.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
+ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/*-
+ * Copyright (c) 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Peter McIlroy.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+/*
+ * Read in a block of records (until 'enough').
+ * sort, write to temp file.
+ * Merge sort temp files into output file
+ * Small files miss out the temp file stage.
+ * Large files might get multiple merges.
+ */
+#include "sort.h"
+#include "fsort.h"
+
+__RCSID("$NetBSD: fsort.c,v 1.47 2010/02/05 21:58:41 enami Exp $");
+
+#include <stdlib.h>
+#include <string.h>
+
+#define SALIGN(n) ((n+sizeof(length_t)-1) & ~(sizeof(length_t)-1))
+
+void
+fsort(struct filelist *filelist, int nfiles, FILE *outfp, struct field *ftbl)
+{
+ RECHEADER **keylist;
+ RECHEADER **keypos, **keyp;
+ RECHEADER *buffer;
+ size_t bufsize = DEFBUFSIZE;
+ u_char *bufend;
+ int mfct = 0;
+ int c, nelem;
+ get_func_t get;
+ RECHEADER *crec;
+ RECHEADER *nbuffer;
+ FILE *fp, *tmp_fp;
+ int file_no;
+ int max_recs = DEBUG('m') ? 16 : MAXNUM;
+
+ buffer = allocrec(NULL, bufsize);
+ bufend = (u_char *)buffer + bufsize;
+ /* Allocate double length keymap for radix_sort */
+ keylist = malloc(2 * max_recs * sizeof(*keylist));
+ if (buffer == NULL || keylist == NULL)
+ err(2, "failed to malloc initial buffer or keylist");
+
+ if (SINGL_FLD)
+ /* Key and data are one! */
+ get = makeline;
+ else
+ /* Key (merged key fields) added before data */
+ get = makekey;
+
+ file_no = 0;
+ fp = fopen(filelist->names[0], "r");
+ if (fp == NULL)
+ err(2, "%s", filelist->names[0]);
+
+ /* Loop through reads of chunk of input files that get sorted
+ * and then merged together. */
+ for (;;) {
+ keypos = keylist;
+ nelem = 0;
+ crec = buffer;
+ makeline_copydown(crec);
+
+ /* Loop reading records */
+ for (;;) {
+ c = get(fp, crec, bufend, ftbl);
+ /* 'c' is 0, EOF or BUFFEND */
+ if (c == 0) {
+ /* Save start of key in input buffer */
+ *keypos++ = crec;
+ if (++nelem == max_recs) {
+ c = BUFFEND;
+ break;
+ }
+ crec = (RECHEADER *)(crec->data + SALIGN(crec->length));
+ continue;
+ }
+ if (c == EOF) {
+ /* try next file */
+ if (++file_no >= nfiles)
+ /* no more files */
+ break;
+ fp = fopen(filelist->names[file_no], "r");
+ if (fp == NULL)
+ err(2, "%s", filelist->names[file_no]);
+ continue;
+ }
+ if (nelem >= max_recs
+ || (bufsize >= MAXBUFSIZE && nelem > 8))
+ /* Need to sort and save this lot of data */
+ break;
+
+ /* c == BUFFEND, and we can process more data */
+ /* Allocate a larger buffer for this lot of data */
+ bufsize *= 2;
+ nbuffer = allocrec(buffer, bufsize);
+ if (!nbuffer) {
+ err(2, "failed to realloc buffer to %zu bytes",
+ bufsize);
+ }
+
+ /* patch up keylist[] */
+ for (keyp = &keypos[-1]; keyp >= keylist; keyp--)
+ *keyp = nbuffer + (*keyp - buffer);
+
+ crec = nbuffer + (crec - buffer);
+ buffer = nbuffer;
+ bufend = (u_char *)buffer + bufsize;
+ }
+
+ /* Sort this set of records */
+ radix_sort(keylist, keylist + max_recs, nelem);
+
+ if (c == EOF && mfct == 0) {
+ /* all the data is (sorted) in the buffer */
+ append(keylist, nelem, outfp,
+ DEBUG('k') ? putkeydump : putline);
+ break;
+ }
+
+ /* Save current data to a temporary file for a later merge */
+ if (nelem != 0) {
+ tmp_fp = ftmp();
+ append(keylist, nelem, tmp_fp, putrec);
+ save_for_merge(tmp_fp, geteasy, ftbl);
+ }
+ mfct = 1;
+
+ if (c == EOF) {
+ /* merge to output file */
+ merge_sort(outfp,
+ DEBUG('k') ? putkeydump : putline, ftbl);
+ break;
+ }
+ }
+
+ free(keylist);
+ keylist = NULL;
+ free(buffer);
+ buffer = NULL;
+}
diff --git a/usr.bin/sort/fsort.h b/usr.bin/sort/fsort.h
new file mode 100644
index 0000000..3d8ef4c
--- /dev/null
+++ b/usr.bin/sort/fsort.h
@@ -0,0 +1,78 @@
+/* $NetBSD: fsort.h,v 1.17 2009/09/26 21:16:55 dsl Exp $ */
+
+/*-
+ * Copyright (c) 2000-2003 The NetBSD Foundation, Inc.
+ * All rights reserved.
+ *
+ * This code is derived from software contributed to The NetBSD Foundation
+ * by Ben Harris and Jaromir Dolecek.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
+ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/*-
+ * Copyright (c) 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Peter McIlroy.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * @(#)fsort.h 8.1 (Berkeley) 6/6/93
+ */
+
+#define BUFSIZE (1<<20)
+#define MAXNUM 131072 /* low guess at average record count */
+#define BUFFEND (EOF-2)
+#define MAXFCT 1000
+#define DEFLLEN 65536
+
+/*
+ * Default (initial) and maximum size of record buffer for fsort().
+ * Note that no more than MAXNUM records are stored in the buffer,
+ * even if the buffer is not full yet.
+ */
+#define DEFBUFSIZE (1 << 20) /* 1MB */
+#define MAXBUFSIZE (8 << 20) /* 10 MB */
diff --git a/usr.bin/sort/init.c b/usr.bin/sort/init.c
new file mode 100644
index 0000000..ff1799b
--- /dev/null
+++ b/usr.bin/sort/init.c
@@ -0,0 +1,447 @@
+/* $NetBSD: init.c,v 1.29 2013/10/18 20:47:06 christos Exp $ */
+
+/*-
+ * Copyright (c) 2000-2003 The NetBSD Foundation, Inc.
+ * All rights reserved.
+ *
+ * This code is derived from software contributed to The NetBSD Foundation
+ * by Ben Harris and Jaromir Dolecek.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
+ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/*-
+ * Copyright (c) 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Peter McIlroy.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include "sort.h"
+
+__RCSID("$NetBSD: init.c,v 1.29 2013/10/18 20:47:06 christos Exp $");
+
+#include <ctype.h>
+#include <string.h>
+
+static void insertcol(struct field *);
+static const char *setcolumn(const char *, struct field *);
+
+/*
+ * masks of ignored characters.
+ */
+static u_char dtable[NBINS], itable[NBINS];
+
+/*
+ * parsed key options
+ */
+struct coldesc *clist = NULL;
+int ncols = 0;
+
+/*
+ * clist (list of columns which correspond to one or more icol or tcol)
+ * is in increasing order of columns.
+ * Fields are kept in increasing order of fields.
+ */
+
+/*
+ * keep clist in order--inserts a column in a sorted array
+ */
+static void
+insertcol(struct field *field)
+{
+ int i;
+ struct coldesc *p;
+
+ /* Make space for new item */
+ p = realloc(clist, (ncols + 2) * sizeof(*clist));
+ if (!p)
+ err(1, "realloc");
+ clist = p;
+ memset(&clist[ncols], 0, sizeof(clist[ncols]));
+
+ for (i = 0; i < ncols; i++)
+ if (field->icol.num <= clist[i].num)
+ break;
+ if (field->icol.num != clist[i].num) {
+ memmove(clist+i+1, clist+i, sizeof(COLDESC)*(ncols-i));
+ clist[i].num = field->icol.num;
+ ncols++;
+ }
+ if (field->tcol.num && field->tcol.num != field->icol.num) {
+ for (i = 0; i < ncols; i++)
+ if (field->tcol.num <= clist[i].num)
+ break;
+ if (field->tcol.num != clist[i].num) {
+ memmove(clist+i+1, clist+i,sizeof(COLDESC)*(ncols-i));
+ clist[i].num = field->tcol.num;
+ ncols++;
+ }
+ }
+}
+
+/*
+ * matches fields with the appropriate columns--n^2 but who cares?
+ */
+void
+fldreset(struct field *fldtab)
+{
+ int i;
+
+ fldtab[0].tcol.p = clist + ncols - 1;
+ for (++fldtab; fldtab->icol.num; ++fldtab) {
+ for (i = 0; fldtab->icol.num != clist[i].num; i++)
+ ;
+ fldtab->icol.p = clist + i;
+ if (!fldtab->tcol.num)
+ continue;
+ for (i = 0; fldtab->tcol.num != clist[i].num; i++)
+ ;
+ fldtab->tcol.p = clist + i;
+ }
+}
+
+/*
+ * interprets a column in a -k field
+ */
+static const char *
+setcolumn(const char *pos, struct field *cur_fld)
+{
+ struct column *col;
+ char *npos;
+ int tmp;
+ col = cur_fld->icol.num ? (&cur_fld->tcol) : (&cur_fld->icol);
+ col->num = (int) strtol(pos, &npos, 10);
+ pos = npos;
+ if (col->num <= 0 && !(col->num == 0 && col == &(cur_fld->tcol)))
+ errx(2, "field numbers must be positive");
+ if (*pos == '.') {
+ if (!col->num)
+ errx(2, "cannot indent end of line");
+ ++pos;
+ col->indent = (int) strtol(pos, &npos, 10);
+ pos = npos;
+ if (&cur_fld->icol == col)
+ col->indent--;
+ if (col->indent < 0)
+ errx(2, "illegal offset");
+ }
+ for(; (tmp = optval(*pos, cur_fld->tcol.num)); pos++)
+ cur_fld->flags |= tmp;
+ if (cur_fld->icol.num == 0)
+ cur_fld->icol.num = 1;
+ return (pos);
+}
+
+int
+setfield(const char *pos, struct field *cur_fld, int gflag)
+{
+ cur_fld->mask = NULL;
+
+ pos = setcolumn(pos, cur_fld);
+ if (*pos == '\0') /* key extends to EOL. */
+ cur_fld->tcol.num = 0;
+ else {
+ if (*pos != ',')
+ errx(2, "illegal field descriptor");
+ setcolumn((++pos), cur_fld);
+ }
+ if (!cur_fld->flags)
+ cur_fld->flags = gflag;
+ if (REVERSE)
+ /* A local 'r' doesn't invert the global one */
+ cur_fld->flags &= ~R;
+
+ /* Assign appropriate mask table and weight table. */
+ cur_fld->weights = weight_tables[cur_fld->flags & (R | F)];
+ if (cur_fld->flags & I)
+ cur_fld->mask = itable;
+ else if (cur_fld->flags & D)
+ cur_fld->mask = dtable;
+
+ cur_fld->flags |= (gflag & (BI | BT));
+ if (!cur_fld->tcol.indent) /* BT has no meaning at end of field */
+ cur_fld->flags &= ~BT;
+
+ if (cur_fld->tcol.num
+ && !(!(cur_fld->flags & BI) && cur_fld->flags & BT)
+ && (cur_fld->tcol.num <= cur_fld->icol.num
+ /* indent if 0 -> end of field, i.e. okay */
+ && cur_fld->tcol.indent != 0
+ && cur_fld->tcol.indent < cur_fld->icol.indent))
+ errx(2, "fields out of order");
+
+ insertcol(cur_fld);
+ return (cur_fld->tcol.num);
+}
+
+int
+optval(int desc, int tcolflag)
+{
+ switch(desc) {
+ case 'b':
+ if (!tcolflag)
+ return BI;
+ else
+ return BT;
+ case 'd': return D;
+ case 'f': return F;
+ case 'i': return I;
+ case 'l': return L;
+ case 'n': return N;
+ case 'r': return R;
+ default: return 0;
+ }
+}
+
+/*
+ * Return true if the options found in ARG, according to the getopt
+ * spec in OPTS, require an additional argv word as an option
+ * argument.
+ */
+static int
+options_need_argument(const char *arg, const char *opts)
+{
+ size_t pos;
+ const char *s;
+
+ /*assert(arg[0] == '-');*/
+
+ pos = 1;
+ while (arg[pos]) {
+ s = strchr(opts, arg[pos]);
+ if (s == NULL) {
+ /* invalid option */
+ return 0;
+ }
+ if (s[1] == ':') {
+ /* option requires argument */
+ if (arg[pos+1] == '\0') {
+ /* no argument in this arg */
+ return 1;
+ }
+ else {
+ /* argument is in this arg; no more options */
+ return 0;
+ }
+ }
+ pos++;
+ }
+ return 0;
+}
+
+/*
+ * Replace historic +SPEC arguments with appropriate -kSPEC.
+ *
+ * The form can be either a single +SPEC or a pair +SPEC -SPEC.
+ * The following -SPEC is not recognized unless it follows
+ * immediately.
+ */
+void
+fixit(int *argc, char **argv, const char *opts)
+{
+ int i, j, sawplus;
+ char *vpos, *tpos, spec[20];
+ int col, indent;
+
+ sawplus = 0;
+ for (i = 1; i < *argc; i++) {
+ /*
+ * This loop must stop exactly where getopt will stop.
+ * Otherwise it turns e.g. "sort x +3" into "sort x
+ * -k4.1", which will croak if +3 was in fact really a
+ * file name. In order to do this reliably we need to
+ * be able to identify argv words that are option
+ * arguments.
+ */
+
+ if (!strcmp(argv[i], "--")) {
+ /* End of options; stop. */
+ break;
+ }
+
+ if (argv[i][0] == '+') {
+ /* +POS argument */
+ sawplus = 1;
+ } else if (argv[i][0] == '-' && sawplus &&
+ isdigit((unsigned char)argv[i][1])) {
+ /* -POS argument */
+ sawplus = 0;
+ } else if (argv[i][0] == '-') {
+ /* other option */
+ sawplus = 0;
+ if (options_need_argument(argv[i], opts)) {
+ /* skip over the argument */
+ i++;
+ }
+ continue;
+ } else {
+ /* not an option at all; stop */
+ sawplus = 0;
+ break;
+ }
+
+ /*
+ * At this point argv[i] is an old-style spec. The
+ * sawplus flag used by the above loop logic also
+ * tells us if it's a +SPEC or -SPEC.
+ */
+
+ /* parse spec */
+ tpos = argv[i]+1;
+ col = (int)strtol(tpos, &tpos, 10);
+ if (*tpos == '.') {
+ ++tpos;
+ indent = (int) strtol(tpos, &tpos, 10);
+ } else
+ indent = 0;
+ /* tpos now points to the optional flags */
+
+ /*
+ * In the traditional form, x.0 means beginning of line;
+ * in the new form, x.0 means end of line. Adjust the
+ * value of INDENT accordingly.
+ */
+ if (sawplus) {
+ /* +POS */
+ col += 1;
+ indent += 1;
+ } else {
+ /* -POS */
+ if (indent > 0)
+ col += 1;
+ }
+
+ /* make the new style spec */
+ (void)snprintf(spec, sizeof(spec), "%d.%d%s", col, indent,
+ tpos);
+
+ if (sawplus) {
+ /* Replace the +POS argument with new-style -kSPEC */
+ asprintf(&vpos, "-k%s", spec);
+ argv[i] = vpos;
+ } else {
+ /*
+ * Append the spec to the one from the
+ * preceding +POS argument, and remove the
+ * current argv element entirely.
+ */
+ asprintf(&vpos, "%s,%s", argv[i-1], spec);
+ free(argv[i-1]);
+ argv[i-1] = vpos;
+ for (j=i; j < *argc; j++)
+ argv[j] = argv[j+1];
+ *argc -= 1;
+ i--;
+ }
+ }
+}
+
+/*
+ * ascii, Rascii, Ftable, and RFtable map
+ *
+ * Sorting 'weight' tables.
+ * Convert 'ascii' characters into their sort order.
+ * The 'F' variants fold lower case to upper equivalent
+ * The 'R' variants are for reverse sorting.
+ *
+ * The record separator (REC_D) never needs a weight, this frees one
+ * byte value as an 'end of key' marker. This must be 0 for normal
+ * weight tables, and 0xff for reverse weight tables - and is used
+ * to terminate keys so that short keys sort before (after if reverse)
+ * longer keys.
+ *
+ * The field separator has a normal weight - although it cannot occur
+ * within a key unless it is the default (space+tab).
+ *
+ * All other bytes map to the appropriate value for the sort order.
+ * Numeric sorts don't need any tables, they are reversed by negation.
+ *
+ * Global reverse sorts are done by writing the sorted keys in reverse
+ * order - the sort itself is stil forwards.
+ * This means that weights are only ever used when generating keys, any
+ * sort of the original data bytes is always forwards and unweighted.
+ *
+ * Note: this is only good for ASCII sorting. For different LC 's,
+ * all bets are off.
+ *
+ * itable[] and dtable[] are the masks for -i (ignore non-printables)
+ * and -d (only sort blank and alphanumerics).
+ */
+void
+settables(void)
+{
+ int i;
+ int next_weight = 1;
+ int rev_weight = 254;
+
+ ascii[REC_D] = 0;
+ Rascii[REC_D] = 255;
+ Ftable[REC_D] = 0;
+ RFtable[REC_D] = 255;
+
+ for (i = 0; i < 256; i++) {
+ if (i == REC_D)
+ continue;
+ ascii[i] = next_weight;
+ Rascii[i] = rev_weight;
+ if (Ftable[i] == 0) {
+ Ftable[i] = next_weight;
+ RFtable[i] = rev_weight;
+ Ftable[tolower(i)] = next_weight;
+ RFtable[tolower(i)] = rev_weight;
+ }
+ next_weight++;
+ rev_weight--;
+
+ if (i == '\n' || isprint(i))
+ itable[i] = 1;
+
+ if (i == '\n' || i == '\t' || i == ' ' || isalnum(i))
+ dtable[i] = 1;
+ }
+}
diff --git a/usr.bin/sort/msort.c b/usr.bin/sort/msort.c
new file mode 100644
index 0000000..6cbcf90
--- /dev/null
+++ b/usr.bin/sort/msort.c
@@ -0,0 +1,431 @@
+/* $NetBSD: msort.c,v 1.31 2016/06/01 02:37:55 kre Exp $ */
+
+/*-
+ * Copyright (c) 2000-2003 The NetBSD Foundation, Inc.
+ * All rights reserved.
+ *
+ * This code is derived from software contributed to The NetBSD Foundation
+ * by Ben Harris and Jaromir Dolecek.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
+ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/*-
+ * Copyright (c) 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Peter McIlroy.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include "sort.h"
+#include "fsort.h"
+
+__RCSID("$NetBSD: msort.c,v 1.31 2016/06/01 02:37:55 kre Exp $");
+
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <util.h>
+
+/* Subroutines using comparisons: merge sort and check order */
+#define DELETE (1)
+
+typedef struct mfile {
+ FILE *fp;
+ get_func_t get;
+ RECHEADER *rec;
+ u_char *end;
+} MFILE;
+
+static int cmp(RECHEADER *, RECHEADER *);
+static int insert(struct mfile **, struct mfile *, int, int);
+static void merge_sort_fstack(FILE *, put_func_t, struct field *);
+
+/*
+ * Number of files merge() can merge in one pass.
+ */
+#define MERGE_FNUM 16
+
+static struct mfile fstack[MERGE_FNUM];
+static struct mfile fstack_1[MERGE_FNUM];
+static struct mfile fstack_2[MERGE_FNUM];
+static int fstack_count, fstack_1_count, fstack_2_count;
+
+void
+save_for_merge(FILE *fp, get_func_t get, struct field *ftbl)
+{
+ FILE *mfp, *mfp1, *mfp2;
+
+ if (fstack_count == MERGE_FNUM) {
+ /* Must reduce the number of temporary files */
+ mfp = ftmp();
+ merge_sort_fstack(mfp, putrec, ftbl);
+ /* Save output in next layer */
+ if (fstack_1_count == MERGE_FNUM) {
+ mfp1 = ftmp();
+ memcpy(fstack, fstack_1, sizeof fstack);
+ merge_sort_fstack(mfp1, putrec, ftbl);
+ if (fstack_2_count == MERGE_FNUM) {
+ /* More than 4096 files! */
+ mfp2 = ftmp();
+ memcpy(fstack, fstack_2, sizeof fstack);
+ merge_sort_fstack(mfp2, putrec, ftbl);
+ fstack_2[0].fp = mfp2;
+ fstack_2_count = 1;
+ }
+ fstack_2[fstack_2_count].fp = mfp1;
+ fstack_2[fstack_2_count].get = geteasy;
+ fstack_2_count++;
+ fstack_1_count = 0;
+ }
+ fstack_1[fstack_1_count].fp = mfp;
+ fstack_1[fstack_1_count].get = geteasy;
+ fstack_1_count++;
+ fstack_count = 0;
+ }
+
+ fstack[fstack_count].fp = fp;
+ fstack[fstack_count++].get = get;
+}
+
+void
+fmerge(struct filelist *filelist, int nfiles, FILE *outfp, struct field *ftbl)
+{
+ get_func_t get = SINGL_FLD ? makeline : makekey;
+ FILE *fp;
+ int i;
+
+ for (i = 0; i < nfiles; i++) {
+ fp = fopen(filelist->names[i], "r");
+ if (fp == NULL)
+ err(2, "%s", filelist->names[i]);
+ save_for_merge(fp, get, ftbl);
+ }
+
+ merge_sort(outfp, putline, ftbl);
+}
+
+void
+merge_sort(FILE *outfp, put_func_t put, struct field *ftbl)
+{
+ int count = fstack_1_count + fstack_2_count;
+ FILE *mfp;
+ int i;
+
+ if (count == 0) {
+ /* All files in initial array */
+ merge_sort_fstack(outfp, put, ftbl);
+ return;
+ }
+
+ count += fstack_count;
+
+ /* Too many files for one merge sort */
+ for (;;) {
+ /* Sort latest 16 files */
+ i = count;
+ if (i > MERGE_FNUM)
+ i = MERGE_FNUM;
+ while (fstack_count > 0)
+ fstack[--i] = fstack[--fstack_count];
+ while (i > 0 && fstack_1_count > 0)
+ fstack[--i] = fstack_1[--fstack_1_count];
+ while (i > 0)
+ fstack[--i] = fstack_2[--fstack_2_count];
+ if (count <= MERGE_FNUM) {
+ /* Got all the data */
+ fstack_count = count;
+ merge_sort_fstack(outfp, put, ftbl);
+ return;
+ }
+ mfp = ftmp();
+ fstack_count = count > MERGE_FNUM ? MERGE_FNUM : count;
+ merge_sort_fstack(mfp, putrec, ftbl);
+ fstack[0].fp = mfp;
+ fstack[0].get = geteasy;
+ fstack_count = 1;
+ count -= MERGE_FNUM - 1;
+ }
+}
+
+static void
+merge_sort_fstack(FILE *outfp, put_func_t put, struct field *ftbl)
+{
+ struct mfile *flistb[MERGE_FNUM], **flist = flistb, *cfile;
+ RECHEADER *new_rec;
+ u_char *new_end;
+ void *tmp;
+ int c, i, nfiles;
+ size_t sz;
+
+ /* Read one record from each file (read again if a duplicate) */
+ for (nfiles = i = 0; i < fstack_count; i++) {
+ cfile = &fstack[i];
+ if (cfile->rec == NULL) {
+ cfile->rec = allocrec(NULL, DEFLLEN);
+ cfile->end = (u_char *)cfile->rec + DEFLLEN;
+ }
+ rewind(cfile->fp);
+
+ for (;;) {
+ c = cfile->get(cfile->fp, cfile->rec, cfile->end, ftbl);
+ if (c == EOF)
+ break;
+
+ if (c == BUFFEND) {
+ /* Double buffer size */
+ sz = (cfile->end - (u_char *)cfile->rec) * 2;
+ cfile->rec = allocrec(cfile->rec, sz);
+ cfile->end = (u_char *)cfile->rec + sz;
+ continue;
+ }
+
+ if (nfiles != 0) {
+ if (insert(flist, cfile, nfiles, !DELETE))
+ /* Duplicate removed */
+ continue;
+ } else
+ flist[0] = cfile;
+ nfiles++;
+ break;
+ }
+ }
+
+ if (nfiles == 0)
+ return;
+
+ /*
+ * We now loop reading a new record from the file with the
+ * 'sorted first' existing record.
+ * As each record is added, the 'first' record is written to the
+ * output file - maintaining one record from each file in the sorted
+ * list.
+ */
+ new_rec = allocrec(NULL, DEFLLEN);
+ new_end = (u_char *)new_rec + DEFLLEN;
+ for (;;) {
+ cfile = flist[0];
+ c = cfile->get(cfile->fp, new_rec, new_end, ftbl);
+ if (c == EOF) {
+ /* Write out last record from now-empty input */
+ put(cfile->rec, outfp);
+ if (--nfiles == 0)
+ break;
+ /* Replace from file with now-first sorted record. */
+ /* (Moving base 'flist' saves copying everything!) */
+ flist++;
+ continue;
+ }
+ if (c == BUFFEND) {
+ /* Buffer not large enough - double in size */
+ sz = (new_end - (u_char *)new_rec) * 2;
+ new_rec = allocrec(new_rec, sz);
+ new_end = (u_char *)new_rec +sz;
+ continue;
+ }
+
+ /* Swap in new buffer, saving old */
+ tmp = cfile->rec;
+ cfile->rec = new_rec;
+ new_rec = tmp;
+ tmp = cfile->end;
+ cfile->end = new_end;
+ new_end = tmp;
+
+ /* Add into sort, removing the original first entry */
+ c = insert(flist, cfile, nfiles, DELETE);
+ if (c != 0 || (UNIQUE && cfile == flist[0]
+ && cmp(new_rec, cfile->rec) == 0)) {
+ /* Was an unwanted duplicate, restore buffer */
+ tmp = cfile->rec;
+ cfile->rec = new_rec;
+ new_rec = tmp;
+ tmp = cfile->end;
+ cfile->end = new_end;
+ new_end = tmp;
+ continue;
+ }
+
+ /* Write out 'old' record */
+ put(new_rec, outfp);
+ }
+
+ free(new_rec);
+}
+
+/*
+ * if delete: inserts rec in flist, deletes flist[0];
+ * otherwise just inserts *rec in flist.
+ * Returns 1 if record is a duplicate to be ignored.
+ */
+static int
+insert(struct mfile **flist, struct mfile *rec, int ttop, int delete)
+{
+ int mid, top = ttop, bot = 0, cmpv = 1;
+
+ for (mid = top / 2; bot + 1 != top; mid = (bot + top) / 2) {
+ cmpv = cmp(rec->rec, flist[mid]->rec);
+ if (cmpv == 0 ) {
+ if (UNIQUE)
+ /* Duplicate key, read another record */
+ /* NB: This doesn't guarantee to keep any
+ * particular record. */
+ return 1;
+ /*
+ * Apply sort by input file order.
+ * We could truncate the sort is the fileno are
+ * adjacent - but that is all too hard!
+ * The fileno cannot be equal, since we only have one
+ * record from each file (+ flist[0] which never
+ * comes here).
+ */
+ cmpv = rec < flist[mid] ? -1 : 1;
+ if (REVERSE)
+ cmpv = -cmpv;
+ }
+ if (cmpv < 0)
+ top = mid;
+ else
+ bot = mid;
+ }
+
+ /* At this point we haven't yet compared against flist[0] */
+
+ if (delete) {
+ /* flist[0] is ourselves, only the caller knows the old data */
+ if (bot != 0) {
+ memmove(flist, flist + 1, bot * sizeof(MFILE *));
+ flist[bot] = rec;
+ }
+ return 0;
+ }
+
+ /* Inserting original set of records */
+
+ if (bot == 0 && cmpv != 0) {
+ /* Doesn't match flist[1], must compare with flist[0] */
+ cmpv = cmp(rec->rec, flist[0]->rec);
+ if (cmpv == 0 && UNIQUE)
+ return 1;
+ /* Add matching keys in file order (ie new is later) */
+ if (cmpv < 0)
+ bot = -1;
+ }
+ bot++;
+ memmove(flist + bot + 1, flist + bot, (ttop - bot) * sizeof(MFILE *));
+ flist[bot] = rec;
+ return 0;
+}
+
+/*
+ * check order on one file
+ */
+void
+order(struct filelist *filelist, struct field *ftbl, int quiet)
+{
+ get_func_t get = SINGL_FLD ? makeline : makekey;
+ RECHEADER *crec, *prec, *trec;
+ u_char *crec_end, *prec_end, *trec_end;
+ FILE *fp;
+ int c;
+
+ fp = fopen(filelist->names[0], "r");
+ if (fp == NULL)
+ err(2, "%s", filelist->names[0]);
+
+ crec = malloc(offsetof(RECHEADER, data[DEFLLEN]));
+ crec_end = crec->data + DEFLLEN;
+ prec = malloc(offsetof(RECHEADER, data[DEFLLEN]));
+ prec_end = prec->data + DEFLLEN;
+
+ /* XXX this does exit(0) for overlong lines */
+ if (get(fp, prec, prec_end, ftbl) != 0)
+ exit(0);
+ while (get(fp, crec, crec_end, ftbl) == 0) {
+ if (0 < (c = cmp(prec, crec))) {
+ if (quiet)
+ exit(1);
+ crec->data[crec->length-1] = 0;
+ errx(1, "found disorder: %s", crec->data+crec->offset);
+ }
+ if (UNIQUE && !c) {
+ if (quiet)
+ exit(1);
+ crec->data[crec->length-1] = 0;
+ errx(1, "found non-uniqueness: %s",
+ crec->data+crec->offset);
+ }
+ /*
+ * Swap pointers so that this record is on place pointed
+ * to by prec and new record is read to place pointed to by
+ * crec.
+ */
+ trec = prec;
+ prec = crec;
+ crec = trec;
+ trec_end = prec_end;
+ prec_end = crec_end;
+ crec_end = trec_end;
+ }
+ exit(0);
+}
+
+static int
+cmp(RECHEADER *rec1, RECHEADER *rec2)
+{
+ int len;
+ int r;
+
+ /* key is weights */
+ len = min(rec1->keylen, rec2->keylen);
+ r = memcmp(rec1->data, rec2->data, len);
+ if (r == 0)
+ r = rec1->keylen - rec2->keylen;
+ if (REVERSE)
+ r = -r;
+ return r;
+}
diff --git a/usr.bin/sort/pathnames.h b/usr.bin/sort/pathnames.h
new file mode 100644
index 0000000..534671e
--- /dev/null
+++ b/usr.bin/sort/pathnames.h
@@ -0,0 +1,66 @@
+/* $NetBSD: pathnames.h,v 1.6 2008/04/28 20:24:15 martin Exp $ */
+
+/*-
+ * Copyright (c) 2000-2003 The NetBSD Foundation, Inc.
+ * All rights reserved.
+ *
+ * This code is derived from software contributed to The NetBSD Foundation
+ * by Ben Harris and Jaromir Dolecek.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
+ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/*-
+ * Copyright (c) 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Peter McIlroy.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * @(#)pathnames.h 8.1 (Berkeley) 6/6/93
+ */
+
+#define _PATH_STDIN "/dev/stdin"
diff --git a/usr.bin/sort/radix_sort.c b/usr.bin/sort/radix_sort.c
new file mode 100644
index 0000000..4ec5ff8
--- /dev/null
+++ b/usr.bin/sort/radix_sort.c
@@ -0,0 +1,217 @@
+/* $NetBSD: radix_sort.c,v 1.4 2009/09/19 16:18:00 dsl Exp $ */
+
+/*-
+ * Copyright (c) 1990, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Peter McIlroy and by Dan Bernstein at New York University,
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+#if defined(LIBC_SCCS) && !defined(lint)
+#if 0
+static char sccsid[] = "@(#)radixsort.c 8.2 (Berkeley) 4/28/95";
+#else
+__RCSID("$NetBSD: radix_sort.c,v 1.4 2009/09/19 16:18:00 dsl Exp $");
+#endif
+#endif /* LIBC_SCCS and not lint */
+
+/*
+ * 'stable' radix sort initially from libc/stdlib/radixsort.c
+ */
+
+#include <sys/types.h>
+
+#include <assert.h>
+#include <errno.h>
+#include <util.h>
+#include "sort.h"
+
+typedef struct {
+ RECHEADER **sa; /* Base of saved area */
+ int sn; /* Number of entries */
+ int si; /* index into data for compare */
+} stack;
+
+static void simplesort(RECHEADER **, int, int);
+
+#define THRESHOLD 20 /* Divert to simplesort(). */
+
+#define empty(s) (s >= sp)
+#define pop(a, n, i) a = (--sp)->sa, n = sp->sn, i = sp->si
+#define push(a, n, i) sp->sa = a, sp->sn = n, (sp++)->si = i
+#define swap(a, b, t) t = a, a = b, b = t
+
+void
+radix_sort(RECHEADER **a, RECHEADER **ta, int n)
+{
+ u_int count[256], nc, bmin;
+ u_int c;
+ RECHEADER **ak, **tai, **lim;
+ RECHEADER *hdr;
+ int stack_size = 512;
+ stack *s, *sp, *sp0, *sp1, temp;
+ RECHEADER **top[256];
+ u_int *cp, bigc;
+ int data_index = 0;
+
+ if (n < THRESHOLD && !DEBUG('r')) {
+ simplesort(a, n, 0);
+ return;
+ }
+
+ s = emalloc(stack_size * sizeof *s);
+ memset(&count, 0, sizeof count);
+ /* Technically 'top' doesn't need zeroing */
+ memset(&top, 0, sizeof top);
+
+ sp = s;
+ push(a, n, data_index);
+ while (!empty(s)) {
+ pop(a, n, data_index);
+ if (n < THRESHOLD && !DEBUG('r')) {
+ simplesort(a, n, data_index);
+ continue;
+ }
+
+ /* Count number of times each 'next byte' occurs */
+ nc = 0;
+ bmin = 255;
+ lim = a + n;
+ for (ak = a, tai = ta; ak < lim; ak++) {
+ hdr = *ak;
+ if (data_index >= hdr->keylen) {
+ /* Short key, copy to start of output */
+ if (UNIQUE && a != sp->sa)
+ /* Stop duplicate being written out */
+ hdr->keylen = -1;
+ *a++ = hdr;
+ n--;
+ continue;
+ }
+ /* Save in temp buffer for distribute */
+ *tai++ = hdr;
+ c = hdr->data[data_index];
+ if (++count[c] == 1) {
+ if (c < bmin)
+ bmin = c;
+ nc++;
+ }
+ }
+ /*
+ * We need save the bounds for each 'next byte' that
+ * occurs more so we can sort each block.
+ */
+ if (sp + nc > s + stack_size) {
+ stack_size *= 2;
+ sp1 = erealloc(s, stack_size * sizeof *s);
+ sp = sp1 + (sp - s);
+ s = sp1;
+ }
+
+ /* Minor optimisation to do the largest set last */
+ sp0 = sp1 = sp;
+ bigc = 2;
+ /* Convert 'counts' positions, saving bounds for later sorts */
+ ak = a;
+ for (cp = count + bmin; nc > 0; cp++) {
+ while (*cp == 0)
+ cp++;
+ if ((c = *cp) > 1) {
+ if (c > bigc) {
+ bigc = c;
+ sp1 = sp;
+ }
+ push(ak, c, data_index+1);
+ }
+ ak += c;
+ top[cp-count] = ak;
+ *cp = 0; /* Reset count[]. */
+ nc--;
+ }
+ swap(*sp0, *sp1, temp);
+
+ for (ak = ta+n; --ak >= ta;) /* Deal to piles. */
+ *--top[(*ak)->data[data_index]] = *ak;
+ }
+
+ free(s);
+}
+
+/* insertion sort, short records are sorted before long ones */
+static void
+simplesort(RECHEADER **a, int n, int data_index)
+{
+ RECHEADER **ak, **ai;
+ RECHEADER *akh;
+ RECHEADER **lim = a + n;
+ const u_char *s, *t;
+ int s_len, t_len;
+ int i;
+ int r;
+
+ if (n <= 1)
+ return;
+
+ for (ak = a+1; ak < lim; ak++) {
+ akh = *ak;
+ s = akh->data;
+ s_len = akh->keylen;
+ for (ai = ak; ;) {
+ ai--;
+ t_len = (*ai)->keylen;
+ if (t_len != -1) {
+ t = (*ai)->data;
+ for (i = data_index; ; i++) {
+ if (i >= s_len || i >= t_len) {
+ r = s_len - t_len;
+ break;
+ }
+ r = s[i] - t[i];
+ if (r != 0)
+ break;
+ }
+ if (r >= 0) {
+ if (r == 0 && UNIQUE) {
+ /* Put record below existing */
+ ai[1] = ai[0];
+ /* Mark as duplicate - ignore */
+ akh->keylen = -1;
+ } else {
+ ai++;
+ }
+ break;
+ }
+ }
+ ai[1] = ai[0];
+ if (ai == a)
+ break;
+ }
+ ai[0] = akh;
+ }
+}
diff --git a/usr.bin/sort/sort.1 b/usr.bin/sort/sort.1
new file mode 100644
index 0000000..548aafd
--- /dev/null
+++ b/usr.bin/sort/sort.1
@@ -0,0 +1,525 @@
+.\" $NetBSD: sort.1,v 1.38 2017/07/03 21:34:21 wiz Exp $
+.\"
+.\" Copyright (c) 2000-2003 The NetBSD Foundation, Inc.
+.\" All rights reserved.
+.\"
+.\" This code is derived from software contributed to The NetBSD Foundation
+.\" by Ben Harris and Jaromir Dolecek.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\" notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must reproduce the above copyright
+.\" notice, this list of conditions and the following disclaimer in the
+.\" documentation and/or other materials provided with the distribution.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
+.\" ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+.\" TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+.\" PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
+.\" BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+.\" CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+.\" SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+.\" INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+.\" CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+.\" ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+.\" POSSIBILITY OF SUCH DAMAGE.
+.\"
+.\" Copyright (c) 1991, 1993
+.\" The Regents of the University of California. All rights reserved.
+.\"
+.\" This code is derived from software contributed to Berkeley by
+.\" the Institute of Electrical and Electronics Engineers, Inc.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\" notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must reproduce the above copyright
+.\" notice, this list of conditions and the following disclaimer in the
+.\" documentation and/or other materials provided with the distribution.
+.\" 3. Neither the name of the University nor the names of its contributors
+.\" may be used to endorse or promote products derived from this software
+.\" without specific prior written permission.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+.\" ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+.\" SUCH DAMAGE.
+.\"
+.\" @(#)sort.1 8.1 (Berkeley) 6/6/93
+.\"
+.Dd June 1, 2016
+.Dt SORT 1
+.Os
+.Sh NAME
+.Nm sort
+.Nd sort or merge text files
+.Sh SYNOPSIS
+.Nm
+.Op Fl bdfHilmnrSsu
+.Oo
+.Fl k
+.Ar kstart Ns Op Li \&, Ns Ar kend
+.Oc
+.Op Fl o Ar output
+.Op Fl R Ar char
+.Op Fl T Ar dir
+.Op Fl t Ar char
+.Op Ar
+.Nm
+.Fl C Ns | Ns Fl c
+.Op Fl bdfilnru
+.Oo
+.Fl k
+.Ar kstart Ns Op Li \&, Ns Ar kend
+.Op Fl t Ar char
+.Oc
+.Op Fl R Ar char
+.Op Ar file
+.Sh DESCRIPTION
+The
+.Nm
+utility sorts text files by lines.
+Comparisons are based on one or more sort keys extracted
+from each line of input, and are performed lexicographically.
+By default, if keys are not given,
+.Nm
+regards each input line as a single field.
+.Pp
+The following options are available:
+.Bl -tag -width Fl
+.It Fl C
+Identical to
+.Fl c
+without the error messages in the case of unsorted input.
+.It Fl c
+Check that the single input file is sorted.
+If the file is not sorted,
+.Nm
+produces the appropriate error messages and exits with code 1; otherwise,
+.Nm
+returns 0.
+.Nm
+.Fl c
+produces no output.
+See also
+.Fl u .
+.It Fl H
+Ignored for compatibility with earlier versions of
+.Nm .
+.It Fl m
+Merge only; the input files are assumed to be pre-sorted.
+.It Fl o Ar output
+The argument given is the name of an
+.Ar output
+file to be used instead of the standard output.
+This file can be the same as one of the input files.
+.It Fl S
+Don't use stable sort.
+Default is to use stable sort.
+.It Fl s
+Use stable sort, keeps records with equal keys in their original order.
+This is the default.
+Provided for compatibility with other
+.Nm
+implementations only.
+.It Fl T Ar dir
+Use
+.Ar dir
+as the directory for temporary files.
+The default is the value specified in the environment variable
+.Ev TMPDIR or
+.Pa /tmp
+if
+.Ev TMPDIR
+is not defined.
+.It Fl u
+Unique: suppress all but one in each set of lines having equal keys.
+If used with the
+.Fl c
+option, check that there are no lines with duplicate keys.
+.El
+.Pp
+The following options,
+which should be given before any
+.Fl k
+options, override the default ordering rules.
+When ordering options appear independent of,
+and before, key field specifications,
+the requested field ordering rules are
+applied globally to all sort keys.
+When attached to a specific key (see
+.Fl k ) ,
+the ordering options override
+all global ordering options for that key.
+.Bl -tag -width Fl
+.It Fl d
+Only blank space and alphanumeric characters
+.\" according
+.\" to the current setting of LC_CTYPE
+are used
+in making comparisons.
+.It Fl f
+Considers all lowercase characters that have uppercase
+equivalents to be the same for purposes of comparison.
+.It Fl i
+Ignore all non-printable characters.
+.It Fl l
+Sort by the string length of the field, not by the field itself.
+.It Fl n
+An initial numeric string, consisting of optional blank space, optional
+plus or minus sign, and zero or more digits (including decimal point)
+.\" with
+.\" optional radix character and thousands
+.\" separator
+.\" (as defined in the current locale),
+is sorted by arithmetic value.
+(The
+.Fl n
+option no longer implies the
+.Fl b
+option.)
+.It Fl r
+Reverse the sense of comparisons.
+.El
+.Pp
+The treatment of field separators can be altered using these options:
+.Bl -tag -width Fl
+.It Fl b
+Ignores leading blank space when determining the start
+and end of a restricted sort key.
+A
+.Fl b
+option specified before the first
+.Fl k
+option applies globally to all
+.Fl k
+options.
+Otherwise, the
+.Fl b
+option can be attached independently to each
+.Ar field
+argument of the
+.Fl k
+option (see below).
+Note that the
+.Fl b
+option has no effect unless key fields are specified.
+.It Fl k Ar kstart Ns Op Li \&, Ns Ar kend
+Designates the starting position,
+.Ar kstart ,
+and optional ending position,
+.Ar kend ,
+of a key field.
+The
+.Fl k
+option replaces the obsolescent options
+.Cm \(pl Ns Ar pos1
+and
+.Fl Ns Ar pos2 .
+.It Fl R Ar char
+.Ar char
+is used as the record separator character.
+This should be used with discretion;
+.Fl R Aq Ar alphanumeric
+usually produces undesirable results.
+If char is not a single character, then it
+specifies the value of the desired record
+separator as an integer specified in any
+of the normal NNN, 0ooo, or 0xXXX ways,
+or as an octal value preceded by \e.
+Caution: do not attempt to specify Ctl-A
+as
+.Dq -R 1
+which will not do what was intended at all!
+The default record separator is newline.
+.It Fl t Ar char
+.Ar char
+is used as the field separator character.
+The initial
+.Ar char
+is not considered to be part of a field when determining
+key offsets (see below).
+Each occurrence of
+.Ar char
+is significant (for example,
+.Dq Ar charchar
+delimits an empty field).
+If
+.Fl t
+is not specified, the default field separator is a sequence of
+blank-space characters, and consecutive blank spaces do
+.Em not
+delimit an empty field; further, the initial blank space
+.Em is
+considered part of a field when determining key offsets.
+.El
+.Pp
+The following operands are available:
+.Bl -tag -width Ar
+.It Ar file
+The pathname of a file to be sorted, merged, or checked.
+If no
+.Ar file
+operands are specified, or if
+a
+.Ar file
+operand is
+.Fl ,
+the standard input is used.
+.El
+.Pp
+A field is defined as a minimal sequence of characters followed by a
+field separator or a newline character.
+By default, the first
+blank space of a sequence of blank spaces acts as the field separator.
+All blank spaces in a sequence of blank spaces are considered
+as part of the next field; for example, all blank spaces at
+the beginning of a line are considered to be part of the
+first field.
+.Pp
+Fields are specified
+by the
+.Fl k
+.Ar kstart Ns Op \&, Ns Ar kend
+argument.
+A missing
+.Ar kend
+argument defaults to the end of a line.
+.Pp
+The arguments
+.Ar kstart
+and
+.Ar kend
+have the form
+.Ar m Ns Li \&. Ns Ar n
+and can be followed by one or more of the letters
+.Cm b , d , f , i ,
+.Cm l , n ,
+and
+.Cm r ,
+which correspond to the options discussed above.
+A
+.Ar kstart
+position specified by
+.Ar m Ns Li \&. Ns Ar n
+.Pq Ar m , n No > 0
+is interpreted as the
+.Ar n Ns th
+character in the
+.Ar m Ns th
+field.
+A missing
+.Li \&. Ns Ar n
+in
+.Ar kstart
+means
+.Ql \&.1 ,
+indicating the first character of the
+.Ar m Ns th
+field; if the
+.Fl b
+option is in effect,
+.Ar n
+is counted from the first non-blank character in the
+.Ar m Ns th
+field;
+.Ar m Ns Li \&.1b
+refers to the first non-blank character in the
+.Ar m Ns th
+field.
+.Pp
+A
+.Ar kend
+position specified by
+.Ar m Ns Li \&. Ns Ar n
+is interpreted as
+the
+.Ar n Ns th
+character (including separators) of the
+.Ar m Ns th
+field.
+A missing
+.Li \&. Ns Ar n
+indicates the last character of the
+.Ar m Ns th
+field;
+.Ar m
+= \&0
+designates the end of a line.
+Thus the option
+.Fl k
+.Sm off
+.Xo
+.Ar v Li \&. Ar x Li \&,
+.Ar w Li \&. Ar y
+.Xc
+.Sm on
+is synonymous with the obsolescent option
+.Sm off
+.Cm \(pl Ar v-\&1 Li \&. Ar x-\&1
+.Fl Ar w-\&1 Li \&. Ar y ;
+.Sm on
+when
+.Ar y
+is omitted,
+.Fl k
+.Sm off
+.Ar v Li \&. Ar x Li \&, Ar w
+.Sm on
+is synonymous with
+.Sm off
+.Cm \(pl Ar v-\&1 Li \&. Ar x-\&1
+.Fl Ar w+1 Li \&.0 .
+.Sm on
+The obsolescent
+.Cm \(pl Ns Ar pos1
+.Fl Ns Ar pos2
+option is still supported, except for
+.Fl Ns Ar w Ns Li \&.0b ,
+which has no
+.Fl k
+equivalent.
+.Pp
+.Nm
+compares records by comparing the key fields selected by
+.Fl k
+arguments,
+from first given to last,
+until discovering a difference.
+If there are no
+.Fl k
+arguments, the whole record is treated as a single key.
+After exhausting the
+.Fl k
+arguments, if no difference has been found,
+then the result depends upon the
+.Fl u
+and
+.Fl S
+option settings.
+With
+.Fl u
+the records are considered identical, and one is supressed.
+Otherwise with
+.Fl s
+set (default) the records are left in their original order,
+or with
+.Fl S
+(posix mode) the whole record is considered as a tie breaker.
+.\"
+.\" If you fail to understand why it doesn't matter which order
+.\" the records are output when they are wholly identical, there
+.\" is nothing that this man page can say that wll help!
+.\"
+.Sh ENVIRONMENT
+If the following environment variable exists, it is used by
+.Nm .
+.Bl -tag -width Ev
+.It Ev TMPDIR
+.Nm
+uses the contents of the
+.Ev TMPDIR
+environment variable as the path in which to store
+temporary files.
+.El
+.Sh FILES
+.Bl -tag -width outputNUMBER+some -compact
+.It Pa /tmp/sort.*
+Default temporary files.
+.It Ar output Ns NUMBER
+Temporary file which is used for output if
+.Ar output
+already exists.
+Once sorting is finished, this file replaces
+.Ar output
+(via
+.Xr link 2
+and
+.Xr unlink 2 ) .
+.El
+.Sh EXIT STATUS
+Sort exits with one of the following values:
+.Bl -tag -width flag -compact
+.It 0
+Normal behavior.
+.It 1
+On disorder (or non-uniqueness) with the
+.Fl c
+(or
+.Fl C )
+option.
+.It 2
+An error occurred.
+.El
+.Sh SEE ALSO
+.Xr comm 1 ,
+.Xr join 1 ,
+.Xr uniq 1 ,
+.Xr qsort 3 ,
+.Xr radixsort 3
+.Sh HISTORY
+A
+.Nm
+command appeared in
+.At v5 .
+This
+.Nm
+implementation appeared in
+.Bx 4.4
+and is used since
+.Nx 1.6 .
+.Sh BUGS
+Posix requires the locale's thousands separator be ignored in numbers.
+It may be faster to sort very large files in pieces and then explicitly
+merge them.
+.Sh NOTES
+This
+.Nm
+has no limits on input line length (other than imposed by available
+memory) or any restrictions on bytes allowed within lines.
+.Pp
+To protect data
+.Nm
+.Fl o
+calls
+.Xr link 2
+and
+.Xr unlink 2 ,
+and thus fails on protected directories.
+.Pp
+Input files should be text files.
+If file doesn't end with record separator (which is typically newline), the
+.Nm
+utility silently supplies one.
+.Pp
+The current
+.Nm
+uses lexicographic radix sorting, which requires
+that sort keys be kept in memory (as opposed to previous versions which used quick
+and merge sorts and did not.)
+Thus performance depends highly on efficient choice of sort keys, and the
+.Fl b
+option and the
+.Ar kend
+argument of the
+.Fl k
+option should be used whenever possible.
+Similarly,
+.Nm
+.Fl k1f
+is equivalent to
+.Nm
+.Fl f
+and may take twice as long.
diff --git a/usr.bin/sort/sort.c b/usr.bin/sort/sort.c
new file mode 100644
index 0000000..ee5ffdb
--- /dev/null
+++ b/usr.bin/sort/sort.c
@@ -0,0 +1,419 @@
+/* $NetBSD: sort.c,v 1.64 2017/01/10 21:13:45 christos Exp $ */
+
+/*-
+ * Copyright (c) 2000-2003 The NetBSD Foundation, Inc.
+ * All rights reserved.
+ *
+ * This code is derived from software contributed to The NetBSD Foundation
+ * by Ben Harris and Jaromir Dolecek.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
+ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/*-
+ * Copyright (c) 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Peter McIlroy.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+#include <sys/cdefs.h>
+#ifndef lint
+__COPYRIGHT("@(#) Copyright (c) 1993\
+ The Regents of the University of California. All rights reserved.");
+#endif /* not lint */
+__RCSID("$NetBSD: sort.c,v 1.64 2017/01/10 21:13:45 christos Exp $");
+
+/* Sort sorts a file using an optional user-defined key.
+ * Sort uses radix sort for internal sorting, and allows
+ * a choice of merge sort and radix sort for external sorting.
+ */
+
+#include <sys/types.h>
+#include <sys/time.h>
+#include <sys/stat.h>
+#include <sys/resource.h>
+
+#include <locale.h>
+#include <paths.h>
+#include <signal.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <util.h>
+
+#include "sort.h"
+#include "fsort.h"
+#include "pathnames.h"
+
+int REC_D = '\n';
+u_char d_mask[NBINS]; /* flags for rec_d, field_d, <blank> */
+
+/*
+ * weight tables. Gweights is one of ascii, Rascii..
+ * modified to weight rec_d = 0 (or 255)
+ */
+u_char *const weight_tables[4] = { ascii, Rascii, Ftable, RFtable };
+u_char ascii[NBINS], Rascii[NBINS], RFtable[NBINS], Ftable[NBINS];
+
+int SINGL_FLD = 0, SEP_FLAG = 0, UNIQUE = 0;
+int REVERSE = 0;
+int posix_sort;
+
+unsigned int debug_flags = 0;
+
+static char toutpath[MAXPATHLEN];
+
+const char *tmpdir; /* where temporary files should be put */
+
+static void cleanup(void);
+static void onsignal(int);
+__dead static void usage(const char *);
+
+int
+main(int argc, char *argv[])
+{
+ int ch, i, stdinflag = 0;
+ char mode = 0;
+ char *outfile, *outpath = 0;
+ struct field *fldtab;
+ size_t fldtab_sz, fld_cnt;
+ struct filelist filelist;
+ int num_input_files;
+ FILE *outfp = NULL;
+ struct rlimit rl;
+ struct stat st;
+
+ setlocale(LC_ALL, "");
+
+ /* bump RLIMIT_NOFILE to maximum our hard limit allows */
+ if (getrlimit(RLIMIT_NOFILE, &rl) < 0)
+ err(2, "getrlimit");
+ rl.rlim_cur = rl.rlim_max;
+ if (setrlimit(RLIMIT_NOFILE, &rl) < 0)
+ err(2, "setrlimit");
+
+ d_mask[REC_D = '\n'] = REC_D_F;
+ d_mask['\t'] = d_mask[' '] = BLANK | FLD_D;
+
+ /* fldtab[0] is the global options. */
+ fldtab_sz = 3;
+ fld_cnt = 0;
+ fldtab = emalloc(fldtab_sz * sizeof(*fldtab));
+ memset(fldtab, 0, fldtab_sz * sizeof(*fldtab));
+
+#define SORT_OPTS "bcCdD:fHik:lmno:rR:sSt:T:u"
+
+ /* Convert "+field" args to -k format */
+ fixit(&argc, argv, SORT_OPTS);
+
+ if (!(tmpdir = getenv("TMPDIR")))
+ tmpdir = _PATH_TMP;
+
+ while ((ch = getopt(argc, argv, SORT_OPTS)) != -1) {
+ switch (ch) {
+ case 'b':
+ fldtab[0].flags |= BI | BT;
+ break;
+ case 'c': case 'C': case 'm':
+ if (mode)
+ usage("Incompatible operation modes");
+ mode = ch;
+ break;
+ case 'D': /* Debug flags */
+ for (i = 0; optarg[i]; i++)
+ debug_flags |= 1 << (optarg[i] & 31);
+ break;
+ case 'd': case 'f': case 'i': case 'n': case 'l':
+ fldtab[0].flags |= optval(ch, 0);
+ break;
+ case 'H':
+ /* -H was ; use merge sort for blocks of large files' */
+ /* That is now the default. */
+ break;
+ case 'k':
+ fldtab = erealloc(fldtab, (fldtab_sz + 1) * sizeof(*fldtab));
+ memset(&fldtab[fldtab_sz], 0, sizeof(fldtab[0]));
+ fldtab_sz++;
+
+ setfield(optarg, &fldtab[++fld_cnt], fldtab[0].flags);
+ break;
+ case 'o':
+ outpath = optarg;
+ break;
+ case 'r':
+ REVERSE = 1;
+ break;
+ case 'R':
+ if (REC_D != '\n')
+ usage("multiple record delimiters");
+ REC_D = *optarg;
+ if (optarg[1] != '\0') {
+ char *ep;
+ int t = 0;
+
+ if (optarg[0] == '\\')
+ optarg++, t = 8;
+ REC_D = (int)strtol(optarg, &ep, t);
+ if (*ep != '\0' || REC_D < 0 ||
+ REC_D >= (int)__arraycount(d_mask))
+ errx(2, "invalid record delimiter %s",
+ optarg);
+ }
+ if (REC_D == '\n')
+ break;
+ d_mask['\n'] = d_mask[' '];
+ d_mask[REC_D] = REC_D_F;
+ break;
+ case 's':
+ /*
+ * Nominally 'stable sort', keep lines with equal keys
+ * in input file order. (Default for NetBSD)
+ * (-s for GNU sort compatibility.)
+ */
+ posix_sort = 0;
+ break;
+ case 'S':
+ /*
+ * Reverse of -s!
+ * This needs to enforce a POSIX sort where records
+ * with equal keys are then sorted by the raw data.
+ * Currently not implemented!
+ * (using libc radixsort() v sradixsort() doesn't
+ * have the desired effect.)
+ */
+ posix_sort = 1;
+ break;
+ case 't':
+ if (SEP_FLAG)
+ usage("multiple field delimiters");
+ SEP_FLAG = 1;
+ d_mask[' '] &= ~FLD_D;
+ d_mask['\t'] &= ~FLD_D;
+ d_mask['\n'] &= ~FLD_D;
+ d_mask[(u_char)*optarg] |= FLD_D;
+ if (d_mask[(u_char)*optarg] & REC_D_F)
+ errx(2, "record/field delimiter clash");
+ break;
+ case 'T':
+ /* -T tmpdir */
+ tmpdir = optarg;
+ break;
+ case 'u':
+ UNIQUE = 1;
+ break;
+ case '?':
+ default:
+ usage(NULL);
+ }
+ }
+
+ if (UNIQUE)
+ /* Don't sort on raw record if keys match */
+ posix_sort = 0;
+
+ if ((mode == 'c' || mode == 'C') && argc > optind+1)
+ errx(2, "too many input files for -c option");
+ if (argc - 2 > optind && !strcmp(argv[argc-2], "-o")) {
+ outpath = argv[argc-1];
+ argc -= 2;
+ }
+ if (mode == 'm' && argc - optind > (MAXFCT - (16+1))*16)
+ errx(2, "too many input files for -m option");
+
+ for (i = optind; i < argc; i++) {
+ /* allow one occurrence of /dev/stdin */
+ if (!strcmp(argv[i], "-") || !strcmp(argv[i], _PATH_STDIN)) {
+ if (stdinflag)
+ warnx("ignoring extra \"%s\" in file list",
+ argv[i]);
+ else
+ stdinflag = 1;
+
+ /* change to /dev/stdin if '-' */
+ if (argv[i][0] == '-') {
+ static char path_stdin[] = _PATH_STDIN;
+ argv[i] = path_stdin;
+ }
+
+ } else if ((ch = access(argv[i], R_OK)))
+ err(2, "%s", argv[i]);
+ }
+
+ if (fldtab[1].icol.num == 0) {
+ /* No sort key specified */
+ if (fldtab[0].flags & (I|D|F|N|L)) {
+ /* Modified - generate a key that covers the line */
+ fldtab[0].flags &= ~(BI|BT);
+ setfield("1", &fldtab[++fld_cnt], fldtab->flags);
+ fldreset(fldtab);
+ } else {
+ /* Unmodified, just compare the line */
+ SINGL_FLD = 1;
+ fldtab[0].icol.num = 1;
+ }
+ } else {
+ fldreset(fldtab);
+ }
+
+ settables();
+
+ if (optind == argc) {
+ static const char * const names[] = { _PATH_STDIN, NULL };
+ filelist.names = names;
+ num_input_files = 1;
+ } else {
+ filelist.names = (const char * const *) &argv[optind];
+ num_input_files = argc - optind;
+ }
+
+ if (mode == 'c' || mode == 'C') {
+ order(&filelist, fldtab, mode == 'C');
+ /* NOT REACHED */
+ }
+
+ if (!outpath) {
+ toutpath[0] = '\0'; /* path not used in this case */
+ outfile = outpath = toutpath;
+ outfp = stdout;
+ } else if (lstat(outpath, &st) == 0
+ && !S_ISCHR(st.st_mode) && !S_ISBLK(st.st_mode)) {
+ /* output file exists and isn't character or block device */
+ struct sigaction act;
+ static const int sigtable[] = {SIGHUP, SIGINT, SIGPIPE,
+ SIGXCPU, SIGXFSZ, SIGVTALRM, SIGPROF, 0};
+ int outfd;
+ errno = 0;
+ if (access(outpath, W_OK))
+ err(2, "%s", outpath);
+ (void)snprintf(toutpath, sizeof(toutpath), "%sXXXXXX",
+ outpath);
+ if ((outfd = mkstemp(toutpath)) == -1)
+ err(2, "Cannot create temporary file `%s'", toutpath);
+ (void)atexit(cleanup);
+ act.sa_handler = onsignal;
+ (void) sigemptyset(&act.sa_mask);
+ act.sa_flags = SA_RESTART | SA_RESETHAND;
+ for (i = 0; sigtable[i]; ++i) /* always unlink toutpath */
+ sigaction(sigtable[i], &act, 0);
+ outfile = toutpath;
+ if ((outfp = fdopen(outfd, "w")) == NULL)
+ err(2, "Cannot open temporary file `%s'", toutpath);
+ } else {
+ outfile = outpath;
+
+ if ((outfp = fopen(outfile, "w")) == NULL)
+ err(2, "output file %s", outfile);
+ }
+
+ if (mode == 'm')
+ fmerge(&filelist, num_input_files, outfp, fldtab);
+ else
+ fsort(&filelist, num_input_files, outfp, fldtab);
+
+ if (outfile != outpath) {
+ if (access(outfile, F_OK))
+ err(2, "%s", outfile);
+
+ /*
+ * Copy file permissions bits of the original file.
+ * st is initialized above, when we create the
+ * temporary spool file.
+ */
+ if (lchmod(outfile, st.st_mode & ALLPERMS) != 0) {
+ err(2, "cannot chmod %s: output left in %s",
+ outpath, outfile);
+ }
+
+ (void)unlink(outpath);
+ if (link(outfile, outpath))
+ err(2, "cannot link %s: output left in %s",
+ outpath, outfile);
+ (void)unlink(outfile);
+ toutpath[0] = 0;
+ }
+ exit(0);
+}
+
+static void
+onsignal(int sig)
+{
+ cleanup();
+}
+
+static void
+cleanup(void)
+{
+ if (toutpath[0])
+ (void)unlink(toutpath);
+}
+
+static void
+usage(const char *msg)
+{
+ const char *pn = getprogname();
+
+ if (msg != NULL)
+ (void)fprintf(stderr, "%s: %s\n", getprogname(), msg);
+ (void)fprintf(stderr,
+ "usage: %s [-bdfHilmnrSsu] [-k kstart[,kend]] [-o output]"
+ " [-R char] [-T dir]\n", pn);
+ (void)fprintf(stderr,
+ " [-t char] [file ...]\n");
+ (void)fprintf(stderr,
+ " or: %s -C|-c [-bdfilnru] [-k kstart[,kend]] [-o output]"
+ " [-R char]\n", pn);
+ (void)fprintf(stderr,
+ " [-t char] [file]\n");
+ exit(2);
+}
+
+RECHEADER *
+allocrec(RECHEADER *rec, size_t size)
+{
+
+ return (erealloc(rec, size + sizeof(long) - 1));
+}
diff --git a/usr.bin/sort/sort.h b/usr.bin/sort/sort.h
new file mode 100644
index 0000000..d7a3144
--- /dev/null
+++ b/usr.bin/sort/sort.h
@@ -0,0 +1,201 @@
+/* $NetBSD: sort.h,v 1.36 2016/06/01 02:37:55 kre Exp $ */
+
+/*-
+ * Copyright (c) 2000-2003 The NetBSD Foundation, Inc.
+ * All rights reserved.
+ *
+ * This code is derived from software contributed to The NetBSD Foundation
+ * by Ben Harris and Jaromir Dolecek.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
+ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/*-
+ * Copyright (c) 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Peter McIlroy.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * @(#)sort.h 8.1 (Berkeley) 6/6/93
+ */
+
+#include <sys/param.h>
+
+#include <err.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <limits.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#define NBINS 256
+
+/* values for masks, weights, and other flags. */
+/* R and F get used to index weight_tables[] */
+#define R 0x01 /* Field is reversed */
+#define F 0x02 /* weight lower and upper case the same */
+#define I 0x04 /* mask out non-printable characters */
+#define D 0x08 /* sort alphanumeric characters only */
+#define N 0x10 /* Field is a number */
+#define BI 0x20 /* ignore blanks in icol */
+#define BT 0x40 /* ignore blanks in tcol */
+#define L 0x80 /* Sort by field length */
+
+/* masks for delimiters: blanks, fields, and termination. */
+#define BLANK 1 /* ' ', '\t'; '\n' if -R is invoked */
+#define FLD_D 2 /* ' ', '\t' default; from -t otherwise */
+#define REC_D_F 4 /* '\n' default; from -R otherwise */
+
+#define min(a, b) ((a) < (b) ? (a) : (b))
+#define max(a, b) ((a) > (b) ? (a) : (b))
+
+#define FCLOSE(file) { \
+ if (EOF == fclose(file)) \
+ err(2, "%p", file); \
+}
+
+#define EWRITE(ptr, size, n, f, fmt) { \
+ if (!fwrite(ptr, size, n, f)) \
+ err(2, fmt); \
+}
+
+/* Records are limited to MAXBUFSIZE (8MB) and less if you want to sort
+ * in a sane way.
+ * Anyone who wants to sort data records longer than 2GB definitely needs a
+ * different program! */
+typedef unsigned int length_t;
+
+/* A record is a key/line pair starting at rec.data. It has a total length
+ * and an offset to the start of the line half of the pair.
+ */
+typedef struct recheader {
+ length_t length; /* total length of key and line */
+ length_t offset; /* to line */
+ int keylen; /* length of key */
+ u_char data[]; /* key then line */
+} RECHEADER;
+
+/* This is the column as seen by struct field. It is used by enterfield.
+ * They are matched with corresponding coldescs during initialization.
+ */
+struct column {
+ struct coldesc *p;
+ int num;
+ int indent;
+};
+
+/* a coldesc has a number and pointers to the beginning and end of the
+ * corresponding column in the current line. This is determined in enterkey.
+ */
+typedef struct coldesc {
+ u_char *start;
+ u_char *end;
+ int num;
+} COLDESC;
+
+/* A field has an initial and final column; an omitted final column
+ * implies the end of the line. Flags regulate omission of blanks and
+ * numerical sorts; mask determines which characters are ignored (from -i, -d);
+ * weights determines the sort weights of a character (from -f, -r).
+ *
+ * The first field contain the global flags etc.
+ * The list terminates when icol = 0.
+ */
+struct field {
+ struct column icol;
+ struct column tcol;
+ u_int flags;
+ u_char *mask;
+ u_char *weights;
+};
+
+struct filelist {
+ const char * const * names;
+};
+
+typedef int (*get_func_t)(FILE *, RECHEADER *, u_char *, struct field *);
+typedef void (*put_func_t)(const RECHEADER *, FILE *);
+
+extern u_char ascii[NBINS], Rascii[NBINS], Ftable[NBINS], RFtable[NBINS];
+extern u_char *const weight_tables[4]; /* ascii, Rascii, Ftable, RFtable */
+extern u_char d_mask[NBINS];
+extern int SINGL_FLD, SEP_FLAG, UNIQUE, REVERSE;
+extern int posix_sort;
+extern int REC_D;
+extern const char *tmpdir;
+extern struct coldesc *clist;
+extern int ncols;
+
+#define DEBUG(ch) (debug_flags & (1 << ((ch) & 31)))
+extern unsigned int debug_flags;
+
+RECHEADER *allocrec(RECHEADER *, size_t);
+void append(RECHEADER **, int, FILE *, void (*)(const RECHEADER *, FILE *));
+void concat(FILE *, FILE *);
+length_t enterkey(RECHEADER *, const u_char *, u_char *, size_t, struct field *);
+void fixit(int *, char **, const char *);
+void fldreset(struct field *);
+FILE *ftmp(void);
+void fmerge(struct filelist *, int, FILE *, struct field *);
+void save_for_merge(FILE *, get_func_t, struct field *);
+void merge_sort(FILE *, put_func_t, struct field *);
+void fsort(struct filelist *, int, FILE *, struct field *);
+int geteasy(FILE *, RECHEADER *, u_char *, struct field *);
+int makekey(FILE *, RECHEADER *, u_char *, struct field *);
+int makeline(FILE *, RECHEADER *, u_char *, struct field *);
+void makeline_copydown(RECHEADER *);
+int optval(int, int);
+__dead void order(struct filelist *, struct field *, int);
+void putline(const RECHEADER *, FILE *);
+void putrec(const RECHEADER *, FILE *);
+void putkeydump(const RECHEADER *, FILE *);
+void rd_append(int, int, int, FILE *, u_char *, u_char *);
+void radix_sort(RECHEADER **, RECHEADER **, int);
+int setfield(const char *, struct field *, int);
+void settables(void);
diff --git a/usr.bin/sort/tmp.c b/usr.bin/sort/tmp.c
new file mode 100644
index 0000000..155b3f5
--- /dev/null
+++ b/usr.bin/sort/tmp.c
@@ -0,0 +1,106 @@
+/* $NetBSD: tmp.c,v 1.16 2009/11/06 18:34:22 joerg Exp $ */
+
+/*-
+ * Copyright (c) 2000-2003 The NetBSD Foundation, Inc.
+ * All rights reserved.
+ *
+ * This code is derived from software contributed to The NetBSD Foundation
+ * by Ben Harris and Jaromir Dolecek.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
+ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/*-
+ * Copyright (c) 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Peter McIlroy.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+
+__RCSID("$NetBSD: tmp.c,v 1.16 2009/11/06 18:34:22 joerg Exp $");
+
+#include <sys/param.h>
+
+#include <err.h>
+#include <errno.h>
+#include <limits.h>
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "sort.h"
+#include "pathnames.h"
+
+#define _NAME_TMP "sort.XXXXXXXX"
+
+FILE *
+ftmp(void)
+{
+ sigset_t set, oset;
+ FILE *fp;
+ int fd;
+ char path[MAXPATHLEN];
+
+ (void)snprintf(path, sizeof(path), "%s%s%s", tmpdir,
+ (tmpdir[strlen(tmpdir)-1] != '/') ? "/" : "", _NAME_TMP);
+
+ sigfillset(&set);
+ (void)sigprocmask(SIG_BLOCK, &set, &oset);
+ if ((fd = mkstemp(path)) < 0)
+ err(2, "ftmp: mkstemp(\"%s\")", path);
+ if (!(fp = fdopen(fd, "w+")))
+ err(2, "ftmp: fdopen(\"%s\")", path);
+ if (!DEBUG('t'))
+ (void)unlink(path);
+
+ (void)sigprocmask(SIG_SETMASK, &oset, NULL);
+ return (fp);
+}
diff --git a/usr.bin/split/split.1 b/usr.bin/split/split.1
new file mode 100644
index 0000000..a25e3c6
--- /dev/null
+++ b/usr.bin/split/split.1
@@ -0,0 +1,132 @@
+.\" $NetBSD: split.1,v 1.15 2007/05/31 01:35:35 jschauma Exp $
+.\"
+.\" Copyright (c) 1990, 1991, 1993, 1994
+.\" The Regents of the University of California. All rights reserved.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\" notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must reproduce the above copyright
+.\" notice, this list of conditions and the following disclaimer in the
+.\" documentation and/or other materials provided with the distribution.
+.\" 3. Neither the name of the University nor the names of its contributors
+.\" may be used to endorse or promote products derived from this software
+.\" without specific prior written permission.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+.\" ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+.\" SUCH DAMAGE.
+.\"
+.\" @(#)split.1 8.3 (Berkeley) 4/16/94
+.\"
+.Dd May 28, 2007
+.Dt SPLIT 1
+.Os
+.Sh NAME
+.Nm split
+.Nd split a file into pieces
+.Sh SYNOPSIS
+.Nm
+.Op Fl a Ar suffix_length
+.Oo
+.Fl b Ar byte_count Ns Oo Li k|m Oc |
+.Fl l Ar line_count
+.Fl n Ar chunk_count
+.Oc
+.Op Ar file Op Ar name
+.Sh DESCRIPTION
+The
+.Nm
+utility reads the given
+.Ar file
+and breaks it up into files of 1000 lines each.
+If
+.Ar file
+is a single dash or absent,
+.Nm
+reads from the standard input.
+.Ar file
+itself is not altered.
+.Pp
+The options are as follows:
+.Bl -tag -width Ds
+.It Fl a
+Use
+.Ar suffix_length
+letters to form the suffix of the file name.
+.It Fl b
+Create smaller files
+.Ar byte_count
+bytes in length.
+If
+.Ql k
+is appended to the number, the file is split into
+.Ar byte_count
+kilobyte pieces.
+If
+.Ql m
+is appended to the number, the file is split into
+.Ar byte_count
+megabyte pieces.
+.It Fl l
+Create smaller files
+.Ar line_count
+lines in length.
+.It Fl n
+Split file into
+.Ar chunk_count
+smaller files.
+.El
+.Pp
+If additional arguments are specified, the first is used as the name
+of the input file which is to be split.
+If a second additional argument is specified, it is used as a prefix
+for the names of the files into which the file is split.
+In this case, each file into which the file is split is named by the
+prefix followed by a lexically ordered suffix using
+.Ar suffix_length
+characters in the range
+.Dq Li a-z .
+If
+.Fl a
+is not specified, two letters are used as the suffix.
+.Pp
+If the
+.Ar name
+argument is not specified,
+.Ql x
+is used.
+.Sh STANDARDS
+The
+.Nm
+utility conforms to
+.St -p1003.1-2001 .
+.Sh HISTORY
+A
+.Nm
+command appeared in
+.At v6 .
+.Pp
+The
+.Fl a
+option was introduced in
+.Nx 2.0 .
+Before that, if
+.Ar name
+was not specified,
+.Nm
+would vary the first letter of the filename
+to increase the number of possible output files.
+The
+.Fl a
+option makes this unnecessary.
diff --git a/usr.bin/split/split.c b/usr.bin/split/split.c
new file mode 100644
index 0000000..e538da1
--- /dev/null
+++ b/usr.bin/split/split.c
@@ -0,0 +1,362 @@
+/* $NetBSD: split.c,v 1.27 2017/01/10 21:14:13 christos Exp $ */
+
+/*
+ * Copyright (c) 1987, 1993, 1994
+ * The Regents of the University of California. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+#ifndef lint
+__COPYRIGHT("@(#) Copyright (c) 1987, 1993, 1994\
+ The Regents of the University of California. All rights reserved.");
+#endif /* not lint */
+
+#ifndef lint
+#if 0
+static char sccsid[] = "@(#)split.c 8.3 (Berkeley) 4/25/94";
+#endif
+__RCSID("$NetBSD: split.c,v 1.27 2017/01/10 21:14:13 christos Exp $");
+#endif /* not lint */
+
+#include <sys/param.h>
+#include <sys/stat.h>
+
+#include <ctype.h>
+#include <err.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#define DEFLINE 1000 /* Default num lines per file. */
+
+static int file_open; /* If a file open. */
+static int ifd = STDIN_FILENO, ofd = -1; /* Input/output file descriptors. */
+static char *fname; /* File name prefix. */
+static size_t sfxlen = 2; /* suffix length. */
+
+static void newfile(void);
+static void split1(off_t, int) __dead;
+static void split2(off_t) __dead;
+static void split3(off_t) __dead;
+static void usage(void) __dead;
+static size_t bigwrite(int, void const *, size_t);
+
+int
+main(int argc, char *argv[])
+{
+ int ch;
+ char *ep, *p;
+ char const *base;
+ off_t bytecnt = 0; /* Byte count to split on. */
+ off_t numlines = 0; /* Line count to split on. */
+ off_t chunks = 0; /* Number of chunks to split into. */
+
+ while ((ch = getopt(argc, argv, "0123456789b:l:a:n:")) != -1)
+ switch (ch) {
+ case '0': case '1': case '2': case '3': case '4':
+ case '5': case '6': case '7': case '8': case '9':
+ /*
+ * Undocumented kludge: split was originally designed
+ * to take a number after a dash.
+ */
+ if (numlines == 0) {
+ p = argv[optind - 1];
+ if (p[0] == '-' && p[1] == ch && !p[2])
+ p++;
+ else
+ p = argv[optind] + 1;
+ numlines = strtoull(p, &ep, 10);
+ if (numlines == 0 || *ep != '\0')
+ errx(1, "%s: illegal line count.", p);
+ }
+ break;
+ case 'b': /* Byte count. */
+ if (!isdigit((unsigned char)optarg[0]) ||
+ (bytecnt = strtoull(optarg, &ep, 10)) == 0 ||
+ (*ep != '\0' && *ep != 'k' && *ep != 'm'))
+ errx(1, "%s: illegal byte count.", optarg);
+ if (*ep == 'k')
+ bytecnt *= 1024;
+ else if (*ep == 'm')
+ bytecnt *= 1024 * 1024;
+ break;
+ case 'l': /* Line count. */
+ if (numlines != 0)
+ usage();
+ if (!isdigit((unsigned char)optarg[0]) ||
+ (numlines = strtoull(optarg, &ep, 10)) == 0 ||
+ *ep != '\0')
+ errx(1, "%s: illegal line count.", optarg);
+ break;
+ case 'a': /* Suffix length. */
+ if (!isdigit((unsigned char)optarg[0]) ||
+ (sfxlen = (size_t)strtoul(optarg, &ep, 10)) == 0 ||
+ *ep != '\0')
+ errx(1, "%s: illegal suffix length.", optarg);
+ break;
+ case 'n': /* Chunks. */
+ if (!isdigit((unsigned char)optarg[0]) ||
+ (chunks = (size_t)strtoul(optarg, &ep, 10)) == 0 ||
+ *ep != '\0')
+ errx(1, "%s: illegal number of chunks.", optarg);
+ break;
+ default:
+ usage();
+ }
+ argv += optind;
+ argc -= optind;
+
+ if (*argv != NULL) {
+ if (strcmp(*argv, "-") != 0 &&
+ (ifd = open(*argv, O_RDONLY, 0)) < 0)
+ err(1, "%s", *argv);
+ ++argv;
+ }
+
+
+ base = (*argv != NULL) ? *argv++ : "x";
+ if ((fname = malloc(strlen(base) + sfxlen + 1)) == NULL)
+ err(EXIT_FAILURE, NULL);
+ (void)strcpy(fname, base); /* File name prefix. */
+
+ if (*argv != NULL)
+ usage();
+
+ if (numlines == 0)
+ numlines = DEFLINE;
+ else if (bytecnt || chunks)
+ usage();
+
+ if (bytecnt && chunks)
+ usage();
+
+ if (bytecnt)
+ split1(bytecnt, 0);
+ else if (chunks)
+ split3(chunks);
+ else
+ split2(numlines);
+
+ return 0;
+}
+
+/*
+ * split1 --
+ * Split the input by bytes.
+ */
+static void
+split1(off_t bytecnt, int maxcnt)
+{
+ off_t bcnt;
+ ssize_t dist, len;
+ char *C;
+ char bfr[MAXBSIZE];
+ int nfiles;
+
+ nfiles = 0;
+
+ for (bcnt = 0;;)
+ switch (len = read(ifd, bfr, MAXBSIZE)) {
+ case 0:
+ exit(0);
+ /* NOTREACHED */
+ case -1:
+ err(1, "read");
+ /* NOTREACHED */
+ default:
+ if (!file_open) {
+ if (!maxcnt || (nfiles < maxcnt)) {
+ newfile();
+ nfiles++;
+ file_open = 1;
+ }
+ }
+ if (bcnt + len >= bytecnt) {
+ /* LINTED: bytecnt - bcnt <= len */
+ dist = bytecnt - bcnt;
+ if (bigwrite(ofd, bfr, dist) != (size_t)dist)
+ err(1, "write");
+ len -= dist;
+ for (C = bfr + dist; len >= bytecnt;
+ /* LINTED: bytecnt <= len */
+ len -= bytecnt, C += bytecnt) {
+ if (!maxcnt || (nfiles < maxcnt)) {
+ newfile();
+ nfiles++;
+ }
+ /* LINTED: as above */
+ if (bigwrite(ofd,
+ C, bytecnt) != (size_t)bytecnt)
+ err(1, "write");
+ }
+ if (len) {
+ if (!maxcnt || (nfiles < maxcnt)) {
+ newfile();
+ nfiles++;
+ }
+ /* LINTED: len >= 0 */
+ if (bigwrite(ofd, C, len) != (size_t)len)
+ err(1, "write");
+ } else
+ file_open = 0;
+ bcnt = len;
+ } else {
+ bcnt += len;
+ /* LINTED: len >= 0 */
+ if (bigwrite(ofd, bfr, len) != (size_t)len)
+ err(1, "write");
+ }
+ }
+}
+
+/*
+ * split2 --
+ * Split the input by lines.
+ */
+static void
+split2(off_t numlines)
+{
+ off_t lcnt;
+ size_t bcnt;
+ ssize_t len;
+ char *Ce, *Cs;
+ char bfr[MAXBSIZE];
+
+ for (lcnt = 0;;)
+ switch (len = read(ifd, bfr, MAXBSIZE)) {
+ case 0:
+ exit(0);
+ /* NOTREACHED */
+ case -1:
+ err(1, "read");
+ /* NOTREACHED */
+ default:
+ if (!file_open) {
+ newfile();
+ file_open = 1;
+ }
+ for (Cs = Ce = bfr; len--; Ce++)
+ if (*Ce == '\n' && ++lcnt == numlines) {
+ bcnt = Ce - Cs + 1;
+ if (bigwrite(ofd, Cs, bcnt) != (size_t)bcnt)
+ err(1, "write");
+ lcnt = 0;
+ Cs = Ce + 1;
+ if (len)
+ newfile();
+ else
+ file_open = 0;
+ }
+ if (Cs < Ce) {
+ bcnt = Ce - Cs;
+ if (bigwrite(ofd, Cs, bcnt) != (size_t)bcnt)
+ err(1, "write");
+ }
+ }
+}
+
+/*
+ * split3 --
+ * Split the input into specified number of chunks
+ */
+static void
+split3(off_t chunks)
+{
+ struct stat sb;
+
+ if (fstat(ifd, &sb) == -1) {
+ err(1, "stat");
+ /* NOTREACHED */
+ }
+
+ if (chunks > sb.st_size) {
+ errx(1, "can't split into more than %d files",
+ (int)sb.st_size);
+ /* NOTREACHED */
+ }
+
+ split1(sb.st_size/chunks, chunks);
+}
+
+/*
+ * newfile --
+ * Open a new output file.
+ */
+static void
+newfile(void)
+{
+ static int fnum;
+ static char *fpnt;
+ int quot, i;
+
+ if (ofd == -1) {
+ fpnt = fname + strlen(fname);
+ fpnt[sfxlen] = '\0';
+ } else if (close(ofd) != 0)
+ err(1, "%s", fname);
+
+ quot = fnum;
+ for (i = sfxlen - 1; i >= 0; i--) {
+ fpnt[i] = quot % 26 + 'a';
+ quot = quot / 26;
+ }
+ if (quot > 0)
+ errx(1, "too many files.");
+ ++fnum;
+ if ((ofd = open(fname, O_WRONLY | O_CREAT | O_TRUNC, DEFFILEMODE)) < 0)
+ err(1, "%s", fname);
+}
+
+static size_t
+bigwrite(int fd, const void *buf, size_t len)
+{
+ const char *ptr = buf;
+ size_t sofar = 0;
+ ssize_t w;
+
+ while (len != 0) {
+ if ((w = write(fd, ptr, len)) == -1)
+ return sofar;
+ len -= w;
+ ptr += w;
+ sofar += w;
+ }
+ return sofar;
+}
+
+
+static void
+usage(void)
+{
+ (void)fprintf(stderr,
+"usage: %s [-b byte_count] [-l line_count] [-n chunk_count] [-a suffix_length] "
+"[file [prefix]]\n", getprogname());
+ exit(1);
+}
diff --git a/usr.bin/tabs/tabs.1 b/usr.bin/tabs/tabs.1
new file mode 100644
index 0000000..e88b29a
--- /dev/null
+++ b/usr.bin/tabs/tabs.1
@@ -0,0 +1,166 @@
+.\" $NetBSD: tabs.1,v 1.6 2014/03/18 18:20:45 riastradh Exp $
+.\"
+.\" Copyright (c) 2008 The NetBSD Foundation, Inc.
+.\" All rights reserved.
+.\"
+.\" This code is derived from software contributed to The NetBSD Foundation
+.\" by Roy Marples.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\" notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must reproduce the above copyright
+.\" notice, this list of conditions and the following disclaimer in the
+.\" documentation and/or other materials provided with the distribution.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
+.\" ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+.\" TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+.\" PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
+.\" BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+.\" CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+.\" SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+.\" INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+.\" CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+.\" ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+.\" POSSIBILITY OF SUCH DAMAGE.
+.\"
+.Dd April 5, 2012
+.Dt TABS 1
+.Os
+.Sh NAME
+.Nm tabs
+.Nd set terminal tabs
+.Sh SYNOPSIS
+.Nm
+.Op Fl n Ns | Ns Fl a Ns | Ns Fl a2 Ns | Ns Fl c Ns | Ns Fl c2 \
+Ns | Ns Fl c3 Ns | Ns Fl f Ns | Ns Fl p Ns | Ns Fl s Ns | Ns Fl u
+.Op +m Ns Op n
+.Op Fl T Ar type
+.Nm
+.Op Fl T Ar type
+.Op + Ns Op n
+.Ar n1 Ns Op , Ns Ar n2 , Ns Ar ...
+.Sh DESCRIPTION
+The
+.Nm
+utility displays a series of characters that first clears the hardware terminal
+tab settings and then initializes the tab stops at the specified positions
+and optionally adjusts the margin.
+.Pp
+The phrase "tab-stop position N" means that, from the start of a line of
+output, tabbing to position N shall cause the next character output to be in
+the (N+1)th column on that line.
+.Pp
+The following options are supported:
+.Bl -tag -width Fl
+.It Fl Ar n
+Specifies repetitive tab stops separated by a uniform number of columns,
+.Ar n ,
+where
+.Ar n
+is a single digit decimal number.
+The default usage of
+.Nm
+with no arguments is equivalent to
+.Nm
+.Fl 8 .
+When
+.Fl 0
+is used, the tab stops are cleared and no new ones set.
+.It Fl a
+Assembler, applicable to some mainframes.
+Equivalent to
+.Nm
+1,10,16,36,72 .
+.It Fl a2
+Assembler, applicable to some mainframes.
+Equivalent to
+.Nm
+1,10,16,40,72
+.It Fl c
+.Tn COBOL ,
+normal format.
+Equivalent to
+.Nm
+1,8,12,16,20,55
+.It Fl c2
+.Tn COBOL ,
+compact format (columns 1 to 6 omitted).
+Equivalent to
+.Nm
+1,6,10,14,49
+.It Fl c3
+.Tn COBOL ,
+compact format (columns 1 to 6 omitted), with more tabs than
+.Fl c2 .
+Equivalent to
+.Nm
+1,6,10,14,18,22,26,30,34,38,42,46,50,54,58,62,67
+.It Fl f
+.Tn FORTRAN .
+Equivalent to
+.Nm
+1,7,11,15,19,23
+.It Fl p
+.Tn PL/1 .
+Equivalent to
+.Nm
+1,5,9,13,17,21,25,29,33,37,41,45,49,53,57,61
+.It Fl s
+.Tn SNOBOL .
+Equivalent to
+.Nm
+1,10,55
+.It Fl T Ar type
+Indicates the type of terminal.
+.It Fl u
+Assembler, applicable to some mainframes.
+Equivalent to
+.Nm
+1,12,20,44
+.El
+.Sh ENVIRONMENT
+The
+.Ev COLUMNS
+and
+.Ev TERM
+environment variables affect the execution of
+.Nm
+as described in
+.Xr environ 7 .
+.Pp
+The
+.Fl T
+option overrides
+.Ev TERM .
+If neither
+.Ev TERM
+nor the
+.Fl T
+option are present,
+.Nm
+will fail.
+.Sh EXIT STATUS
+.Ex -std
+.Sh SEE ALSO
+.Xr expand 1 ,
+.Xr stty 1 ,
+.Xr tput 1 ,
+.Xr unexpand 1 ,
+.Xr terminfo 5
+.Sh STANDARDS
+The
+.Nm
+utility conforms to
+.St -p1003.1 .
+.Sh HISTORY
+A
+.Nm
+utility first appeared in PWB UNIX.
+This implementation was introduced in
+.Nx 6.0 .
+.Sh AUTHORS
+.An Roy Marples Aq Mt roy@NetBSD.org
diff --git a/usr.bin/tabs/tabs.c b/usr.bin/tabs/tabs.c
new file mode 100644
index 0000000..9e017a0
--- /dev/null
+++ b/usr.bin/tabs/tabs.c
@@ -0,0 +1,232 @@
+/* $NetBSD: tabs.c,v 1.5 2019/02/01 08:29:04 mrg Exp $ */
+
+/*-
+ * Copyright (c) 2008 The NetBSD Foundation, Inc.
+ * All rights reserved.
+ *
+ * This code is derived from software contributed to The NetBSD Foundation
+ * by Roy Marples.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
+ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+#ifndef lint
+__COPYRIGHT("@(#) Copyright (c) 2008 \
+The NetBSD Foundation, inc. All rights reserved.");
+__RCSID("$NetBSD: tabs.c,v 1.5 2019/02/01 08:29:04 mrg Exp $");
+#endif /* not lint */
+
+#include <sys/ioctl.h>
+#include <sys/types.h>
+
+#include <ctype.h>
+#include <err.h>
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <term.h>
+#include <unistd.h>
+
+#define NSTOPS 20
+
+struct tabspec {
+ const char *opt;
+ const char *spec;
+};
+static const struct tabspec tabspecs[] = {
+ {"a", "1,10,16,36,72"},
+ {"a2", "1,10,16,40,72"},
+ {"c", "1,8,12,16,20,55"},
+ {"c2", "1,6,10,14,49"},
+ {"c3", "1,6,10,14,18,22,26,30,34,38,42,46,50,54,58,62,67"},
+ {"f", "1,7,11,15,19,23"},
+ {"p", "1,5,9,13,17,21,25,29,33,37,41,45,49,53,57,61"},
+ {"s", "1,10,55"},
+ {"u", "1,12,20,44"}
+};
+static const size_t ntabspecs = sizeof(tabspecs) / sizeof(tabspecs[0]);
+
+__dead static void
+usage(void)
+{
+ fprintf(stderr,
+ "usage: tabs [-n|-a|-a2|-c|-c2|-c3|-f|-p|-s|-u] [+m[n]]"
+ " [-T type]\n"
+ " tabs [-T type] [+[n]] n1[,n2,...]\n");
+ exit(EXIT_FAILURE);
+ /* NOTREACHED */
+}
+
+int
+main(int argc, char **argv)
+{
+ char *term, *arg, *token, *end, *tabs = NULL, *p;
+ const char *cr, *spec = NULL;
+ int i, n, inc = 8, stops[NSTOPS], nstops, last, cols, margin = 0;
+ size_t j;
+ struct winsize ws;
+
+ term = getenv("TERM");
+ for (i = 1; i < argc; i++) {
+ if (argv[i][0] == '+') {
+ arg = argv[i] + 1;
+ if (arg[0] == 'm')
+ arg++;
+ if (arg[0] == '\0')
+ margin = 10;
+ else {
+ errno = 0;
+ margin = strtol(arg, &end, 10);
+ if (errno != 0 || *end != '\0' || margin < 0)
+ errx(EXIT_FAILURE,
+ "%s: invalid margin", arg);
+ }
+ continue;
+ }
+ if (argv[i][0] != '-') {
+ tabs = argv[i];
+ break;
+ }
+ arg = argv[i] + 1;
+ if (arg[0] == '\0')
+ usage();
+ if (arg[0] == '-' && arg[1] == '\0') {
+ if (argv[i + 1] != NULL)
+ tabs = argv[i + 1];
+ break;
+ }
+ if (arg[0] == 'T' && arg[1] == '\0') {
+ term = argv[++i];
+ if (term == NULL)
+ usage();
+ continue;
+ }
+ if (isdigit((int)arg[0])) {
+ if (arg[1] != '\0')
+ errx(EXIT_FAILURE,
+ "%s: invalid increament", arg);
+ inc = arg[0] - '0';
+ continue;
+ }
+ for (j = 0; j < ntabspecs; j++) {
+ if (arg[0] == tabspecs[j].opt[0] &&
+ arg[1] == tabspecs[j].opt[1]) {
+ spec = tabspecs[j].spec;
+ break;
+ }
+ }
+ if (j == ntabspecs)
+ usage();
+ }
+ if (tabs == NULL && spec != NULL)
+ tabs = strdup(spec);
+
+ if (tabs != NULL)
+ last = nstops = 0;
+ else
+ nstops = -1;
+ p = tabs;
+ while ((token = strsep(&p, ", ")) != NULL) {
+ if (*token == '\0')
+ continue;
+ if (nstops >= NSTOPS)
+ errx(EXIT_FAILURE,
+ "too many tab stops (max %d)", NSTOPS);
+ errno = 0;
+ n = strtol(token, &end, 10);
+ if (errno != 0 || *end != '\0' || n <= 0)
+ errx(EXIT_FAILURE, "%s: invalid tab stop", token);
+ if (*token == '+') {
+ if (nstops == 0)
+ errx(EXIT_FAILURE,
+ "first tab stop may not be relative");
+ n += last;
+ }
+ if (last > n)
+ errx(EXIT_FAILURE, "tab stops may not go backwards");
+ last = stops[nstops++] = n;
+ }
+
+ if (term == NULL)
+ errx(EXIT_FAILURE, "no value for $TERM and -T not given");
+ if (setupterm(term, STDOUT_FILENO, NULL) != 0)
+ err(EXIT_FAILURE, "setupterm:");
+ cr = carriage_return;
+ if (cr == NULL)
+ cr = "\r";
+ if (clear_all_tabs == NULL)
+ errx(EXIT_FAILURE, "terminal cannot clear tabs");
+ if (set_tab == NULL)
+ errx(EXIT_FAILURE, "terminal cannot set tabs");
+
+ /* Clear existing tabs */
+ putp(cr);
+ putp(clear_all_tabs);
+ putp(cr);
+
+ if (set_lr_margin != NULL) {
+ printf("%*s", margin, "");
+ putp(set_lr_margin);
+ } else if (margin != 0)
+ warnx("terminal cannot set left margin");
+
+ if (nstops >= 0) {
+ printf("%*s", stops[0] - 1, "");
+ putp(set_tab);
+ for (i = 1; i < nstops; i++) {
+ printf("%*s", stops[i] - stops[i - 1], "");
+ putp(set_tab);
+ }
+ } else if (inc > 0) {
+ cols = 0;
+ term = getenv("COLUMNS");
+ if (term != NULL) {
+ errno = 0;
+ cols = strtol(term, &end, 10);
+ if (errno != 0 || *end != '\0' || cols < 0)
+ cols = 0;
+ }
+ if (cols == 0) {
+ if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &ws) == 0)
+ cols = ws.ws_col;
+ else {
+ cols = tigetnum("cols");
+ if (cols == 0) {
+ cols = 80;
+ warnx("terminal does not specify number"
+ "columns; defaulting to %d",
+ cols);
+ }
+ }
+ }
+ for (i = 0; i < cols / inc; i++) {
+ printf("%*s", inc, "");
+ putp(set_tab);
+ }
+ }
+ putp(cr);
+
+ exit(EXIT_SUCCESS);
+ /* NOTREACHED */
+}
diff --git a/usr.bin/tail/extern.h b/usr.bin/tail/extern.h
new file mode 100644
index 0000000..24507b7
--- /dev/null
+++ b/usr.bin/tail/extern.h
@@ -0,0 +1,52 @@
+/* $NetBSD: extern.h,v 1.10 2011/09/03 09:02:20 christos Exp $ */
+
+/*-
+ * Copyright (c) 1991, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * @(#)extern.h 8.1 (Berkeley) 6/6/93
+ */
+
+#define WR(p, size) \
+ if (write(STDOUT_FILENO, p, size) != size) \
+ oerr();
+
+enum STYLE { NOTSET = 0, FBYTES, FLINES, RBYTES, RLINES, REVERSE };
+
+void forward(FILE *, enum STYLE, off_t, struct stat *);
+void reverse(FILE *, enum STYLE, off_t, struct stat *);
+
+int displaybytes(FILE *, off_t);
+int displaylines(FILE *, off_t);
+
+void xerr(int fatal, const char *fmt, ...) __printflike(2, 3);
+void xerrx(int fatal, const char *fmt, ...) __printflike(2, 3);
+void ierr(void);
+void oerr(void);
+
+extern int fflag, rflag, rval;
+extern const char *fname;
diff --git a/usr.bin/tail/forward.c b/usr.bin/tail/forward.c
new file mode 100644
index 0000000..e57e8b5
--- /dev/null
+++ b/usr.bin/tail/forward.c
@@ -0,0 +1,355 @@
+/* $NetBSD: forward.c,v 1.33 2015/10/09 17:51:26 christos Exp $ */
+
+/*-
+ * Copyright (c) 1991, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Edward Sze-Tyan Wang.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+#ifndef lint
+#if 0
+static char sccsid[] = "@(#)forward.c 8.1 (Berkeley) 6/6/93";
+#endif
+__RCSID("$NetBSD: forward.c,v 1.33 2015/10/09 17:51:26 christos Exp $");
+#endif /* not lint */
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/time.h>
+#include <sys/mman.h>
+#include <sys/event.h>
+
+#include <limits.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include "extern.h"
+
+static int rlines(FILE *, off_t, struct stat *);
+
+/* defines for inner loop actions */
+#define USE_SLEEP 0
+#define USE_KQUEUE 1
+#define ADD_EVENTS 2
+
+/*
+ * forward -- display the file, from an offset, forward.
+ *
+ * There are eight separate cases for this -- regular and non-regular
+ * files, by bytes or lines and from the beginning or end of the file.
+ *
+ * FBYTES byte offset from the beginning of the file
+ * REG seek
+ * NOREG read, counting bytes
+ *
+ * FLINES line offset from the beginning of the file
+ * REG read, counting lines
+ * NOREG read, counting lines
+ *
+ * RBYTES byte offset from the end of the file
+ * REG seek
+ * NOREG cyclically read characters into a wrap-around buffer
+ *
+ * RLINES
+ * REG mmap the file and step back until reach the correct offset.
+ * NOREG cyclically read lines into a wrap-around array of buffers
+ */
+void
+forward(FILE *fp, enum STYLE style, off_t off, struct stat *sbp)
+{
+ int ch, n;
+ int kq=-1, action=USE_SLEEP;
+ struct stat statbuf;
+ struct kevent ev[2];
+
+ switch(style) {
+ case FBYTES:
+ if (off == 0)
+ break;
+ if (S_ISREG(sbp->st_mode)) {
+ if (sbp->st_size < off)
+ off = sbp->st_size;
+ if (fseeko(fp, off, SEEK_SET) == -1) {
+ ierr();
+ return;
+ }
+ } else while (off--)
+ if ((ch = getc(fp)) == EOF) {
+ if (ferror(fp)) {
+ ierr();
+ return;
+ }
+ break;
+ }
+ break;
+ case FLINES:
+ if (off == 0)
+ break;
+ for (;;) {
+ if ((ch = getc(fp)) == EOF) {
+ if (ferror(fp)) {
+ ierr();
+ return;
+ }
+ break;
+ }
+ if (ch == '\n' && !--off)
+ break;
+ }
+ break;
+ case RBYTES:
+ if (S_ISREG(sbp->st_mode)) {
+ if (sbp->st_size >= off &&
+ fseeko(fp, -off, SEEK_END) == -1) {
+ ierr();
+ return;
+ }
+ } else if (off == 0) {
+ while (getc(fp) != EOF);
+ if (ferror(fp)) {
+ ierr();
+ return;
+ }
+ } else {
+ if (displaybytes(fp, off))
+ return;
+ }
+ break;
+ case RLINES:
+ if (S_ISREG(sbp->st_mode)) {
+ if (!off) {
+ if (fseek(fp, 0L, SEEK_END) == -1) {
+ ierr();
+ return;
+ }
+ } else {
+ if (rlines(fp, off, sbp))
+ return;
+ }
+ } else if (off == 0) {
+ while (getc(fp) != EOF);
+ if (ferror(fp)) {
+ ierr();
+ return;
+ }
+ } else {
+ if (displaylines(fp, off))
+ return;
+ }
+ break;
+ default:
+ break;
+ }
+
+ if (fflag) {
+ kq = kqueue();
+ if (kq < 0)
+ xerr(1, "kqueue");
+ action = ADD_EVENTS;
+ }
+
+ for (;;) {
+ while ((ch = getc(fp)) != EOF) {
+ if (putchar(ch) == EOF)
+ oerr();
+ }
+ if (ferror(fp)) {
+ ierr();
+ return;
+ }
+ (void)fflush(stdout);
+ if (!fflag)
+ break;
+
+ clearerr(fp);
+
+ switch (action) {
+ case ADD_EVENTS:
+ n = 0;
+
+ memset(ev, 0, sizeof(ev));
+ if (fflag == 2 && fp != stdin) {
+ EV_SET(&ev[n], fileno(fp), EVFILT_VNODE,
+ EV_ADD | EV_ENABLE | EV_CLEAR,
+ NOTE_DELETE | NOTE_RENAME, 0, 0);
+ n++;
+ }
+ EV_SET(&ev[n], fileno(fp), EVFILT_READ,
+ EV_ADD | EV_ENABLE, 0, 0, 0);
+ n++;
+
+ if (kevent(kq, ev, n, NULL, 0, NULL) == -1) {
+ close(kq);
+ kq = -1;
+ action = USE_SLEEP;
+ } else {
+ action = USE_KQUEUE;
+ }
+ break;
+
+ case USE_KQUEUE:
+ if (kevent(kq, NULL, 0, ev, 1, NULL) == -1)
+ xerr(1, "kevent");
+
+ if (ev[0].filter == EVFILT_VNODE) {
+ /* file was rotated, wait until it reappears */
+ action = USE_SLEEP;
+ } else if (ev[0].data < 0) {
+ /* file shrank, reposition to end */
+ if (fseek(fp, 0L, SEEK_END) == -1) {
+ ierr();
+ return;
+ }
+ }
+ break;
+
+ case USE_SLEEP:
+ /*
+ * We pause for one second after displaying any data
+ * that has accumulated since we read the file.
+ */
+ (void) sleep(1);
+
+ if (fflag == 2 && fp != stdin &&
+ stat(fname, &statbuf) != -1) {
+ if (statbuf.st_ino != sbp->st_ino ||
+ statbuf.st_dev != sbp->st_dev ||
+ statbuf.st_rdev != sbp->st_rdev ||
+ statbuf.st_nlink == 0) {
+ fp = freopen(fname, "r", fp);
+ if (fp == NULL) {
+ ierr();
+ goto out;
+ }
+ *sbp = statbuf;
+ if (kq != -1)
+ action = ADD_EVENTS;
+ } else if (kq != -1)
+ action = USE_KQUEUE;
+ }
+ break;
+ }
+ }
+out:
+ if (fflag && kq != -1)
+ close(kq);
+}
+
+/*
+ * rlines -- display the last offset lines of the file.
+ *
+ * Non-zero return means than a (non-fatal) error occurred.
+ */
+static int
+rlines(FILE *fp, off_t off, struct stat *sbp)
+{
+ off_t file_size;
+ off_t file_remaining;
+ char *p = NULL;
+ char *start = NULL;
+ off_t mmap_size;
+ off_t mmap_offset;
+ off_t mmap_remaining = 0;
+
+#define MMAP_MAXSIZE (10 * 1024 * 1024)
+
+ if (!(file_size = sbp->st_size))
+ return 0;
+ file_remaining = file_size;
+
+ if (file_remaining > MMAP_MAXSIZE) {
+ mmap_size = MMAP_MAXSIZE;
+ mmap_offset = file_remaining - MMAP_MAXSIZE;
+ } else {
+ mmap_size = file_remaining;
+ mmap_offset = 0;
+ }
+
+ while (off) {
+ start = mmap(NULL, (size_t)mmap_size, PROT_READ,
+ MAP_FILE|MAP_SHARED, fileno(fp), mmap_offset);
+ if (start == MAP_FAILED) {
+ xerr(0, "%s", fname);
+ return 1;
+ }
+
+ mmap_remaining = mmap_size;
+ /* Last char is special, ignore whether newline or not. */
+ for (p = start + mmap_remaining - 1 ; --mmap_remaining ; )
+ if (*--p == '\n' && !--off) {
+ ++p;
+ break;
+ }
+
+ file_remaining -= mmap_size - mmap_remaining;
+
+ if (off == 0)
+ break;
+
+ if (file_remaining == 0)
+ break;
+
+ if (munmap(start, mmap_size)) {
+ xerr(0, "%s", fname);
+ return 1;
+ }
+
+ if (mmap_offset >= MMAP_MAXSIZE) {
+ mmap_offset -= MMAP_MAXSIZE;
+ } else {
+ mmap_offset = 0;
+ mmap_size = file_remaining;
+ }
+ }
+
+ /*
+ * Output the (perhaps partial) data in this mmap'd block.
+ */
+ WR(p, mmap_size - mmap_remaining);
+ file_remaining += mmap_size - mmap_remaining;
+ if (munmap(start, mmap_size)) {
+ xerr(0, "%s", fname);
+ return 1;
+ }
+
+ /*
+ * Set the file pointer to reflect the length displayed.
+ * This will cause the caller to redisplay the data if/when
+ * needed.
+ */
+ if (fseeko(fp, file_remaining, SEEK_SET) == -1) {
+ ierr();
+ return 1;
+ }
+ return 0;
+}
diff --git a/usr.bin/tail/misc.c b/usr.bin/tail/misc.c
new file mode 100644
index 0000000..0af8f68
--- /dev/null
+++ b/usr.bin/tail/misc.c
@@ -0,0 +1,89 @@
+/*-
+ * Copyright (c) 1991, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Edward Sze-Tyan Wang.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+#ifndef lint
+#if 0
+static char sccsid[] = "@(#)misc.c 8.1 (Berkeley) 6/6/93";
+#endif
+__RCSID("$NetBSD: misc.c,v 1.7 2011/09/03 09:02:20 christos Exp $");
+#endif /* not lint */
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <errno.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <err.h>
+
+#include "extern.h"
+
+void
+ierr(void)
+{
+ xerr(0, "%s", fname);
+}
+
+void
+oerr(void)
+{
+ xerr(1, "stdout");
+}
+
+void
+xerr(int fatal, const char *fmt, ...)
+{
+ va_list ap;
+
+ va_start(ap, fmt);
+ vwarn(fmt, ap);
+ va_end(ap);
+ if (fatal)
+ exit(1);
+ rval = 1;
+}
+
+void
+xerrx(int fatal, const char *fmt, ...)
+{
+ va_list ap;
+
+ va_start(ap, fmt);
+ vwarnx(fmt, ap);
+ va_end(ap);
+ if (fatal)
+ exit(1);
+ rval = 1;
+}
diff --git a/usr.bin/tail/read.c b/usr.bin/tail/read.c
new file mode 100644
index 0000000..4e9374d
--- /dev/null
+++ b/usr.bin/tail/read.c
@@ -0,0 +1,210 @@
+/* $NetBSD: read.c,v 1.17 2011/09/03 10:59:10 christos Exp $ */
+
+/*-
+ * Copyright (c) 1991, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Edward Sze-Tyan Wang.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+#ifndef lint
+#if 0
+static char sccsid[] = "@(#)read.c 8.1 (Berkeley) 6/6/93";
+#endif
+__RCSID("$NetBSD: read.c,v 1.17 2011/09/03 10:59:10 christos Exp $");
+#endif /* not lint */
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include "extern.h"
+
+/*
+ * displaybytes -- read bytes to an offset from the end and display.
+ *
+ * This is the function that reads to a byte offset from the end of the input,
+ * storing the data in a wrap-around buffer which is then displayed. If the
+ * rflag is set, the data is displayed in lines in reverse order, and this
+ * routine has the usual nastiness of trying to find the newlines. Otherwise,
+ * it is displayed from the character closest to the beginning of the input to
+ * the end.
+ *
+ * Non-zero return means than a (non-fatal) error occurred.
+ */
+int
+displaybytes(FILE *fp, off_t off)
+{
+ int ch, len, tlen;
+ char *ep, *p, *t;
+ int wrap;
+ char *sp;
+
+ if ((sp = p = malloc(off)) == NULL)
+ xerr(1, "malloc");
+
+ for (wrap = 0, ep = p + off; (ch = getc(fp)) != EOF;) {
+ *p = ch;
+ if (++p == ep) {
+ wrap = 1;
+ p = sp;
+ }
+ }
+ if (ferror(fp)) {
+ ierr();
+ return 1;
+ }
+
+ if (rflag) {
+ for (t = p - 1, len = 0; t >= sp; --t, ++len)
+ if (*t == '\n' && len) {
+ WR(t + 1, len);
+ len = 0;
+ }
+ if (wrap) {
+ tlen = len;
+ for (t = ep - 1, len = 0; t >= p; --t, ++len)
+ if (*t == '\n') {
+ if (len) {
+ WR(t + 1, len);
+ len = 0;
+ }
+ if (tlen) {
+ WR(sp, tlen);
+ tlen = 0;
+ }
+ }
+ if (len)
+ WR(t + 1, len);
+ if (tlen)
+ WR(sp, tlen);
+ }
+ } else {
+ if (wrap && (len = ep - p))
+ WR(p, len);
+ if ((len = p - sp) != 0)
+ WR(sp, len);
+ }
+ return 0;
+}
+
+/*
+ * displaylines -- read lines to an offset from the end and display.
+ *
+ * This is the function that reads to a line offset from the end of the input,
+ * storing the data in an array of buffers which is then displayed. If the
+ * rflag is set, the data is displayed in lines in reverse order, and this
+ * routine has the usual nastiness of trying to find the newlines. Otherwise,
+ * it is displayed from the line closest to the beginning of the input to
+ * the end.
+ *
+ * Non-zero return means than a (non-fatal) error occurred.
+ */
+int
+displaylines(FILE *fp, off_t off)
+{
+ struct {
+ int blen;
+ int len;
+ char *l;
+ } *lines;
+ int ch;
+ char *p;
+ int blen, cnt, recno, wrap;
+ char *sp, *n;
+
+ p = NULL;
+ if ((lines = malloc(off * sizeof(*lines))) == NULL)
+ xerr(1, "malloc");
+
+ memset(lines, 0, sizeof(*lines) * off);
+
+ sp = NULL;
+ blen = cnt = recno = wrap = 0;
+
+ while ((ch = getc(fp)) != EOF) {
+ if (++cnt > blen) {
+ if ((n = realloc(sp, blen + 1024)) == NULL)
+ xerr(1, "realloc");
+ sp = n;
+ blen += 1024;
+ p = sp + cnt - 1;
+ }
+ *p++ = ch;
+ if (ch == '\n') {
+ if (lines[recno].blen < cnt) {
+ if ((n = realloc(lines[recno].l,
+ cnt + 256)) == NULL)
+ xerr(1, "realloc");
+ lines[recno].l = n;
+ lines[recno].blen = cnt + 256;
+ }
+ memmove(lines[recno].l, sp, lines[recno].len = cnt);
+ cnt = 0;
+ p = sp;
+ if (++recno == off) {
+ wrap = 1;
+ recno = 0;
+ }
+ }
+ }
+ if (ferror(fp)) {
+ free(lines);
+ ierr();
+ return 1;
+ }
+ if (cnt) {
+ lines[recno].l = sp;
+ lines[recno].len = cnt;
+ if (++recno == off) {
+ wrap = 1;
+ recno = 0;
+ }
+ }
+
+ if (rflag) {
+ for (cnt = recno - 1; cnt >= 0; --cnt)
+ WR(lines[cnt].l, lines[cnt].len);
+ if (wrap)
+ for (cnt = off - 1; cnt >= recno; --cnt)
+ WR(lines[cnt].l, lines[cnt].len);
+ } else {
+ if (wrap)
+ for (cnt = recno; cnt < off; ++cnt)
+ WR(lines[cnt].l, lines[cnt].len);
+ for (cnt = 0; cnt < recno; ++cnt)
+ WR(lines[cnt].l, lines[cnt].len);
+ }
+ free(lines);
+ return 0;
+}
diff --git a/usr.bin/tail/reverse.c b/usr.bin/tail/reverse.c
new file mode 100644
index 0000000..bb8bcd9
--- /dev/null
+++ b/usr.bin/tail/reverse.c
@@ -0,0 +1,269 @@
+/* $NetBSD: reverse.c,v 1.23 2011/09/03 10:59:11 christos Exp $ */
+
+/*-
+ * Copyright (c) 1991, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Edward Sze-Tyan Wang.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+#ifndef lint
+#if 0
+static char sccsid[] = "@(#)reverse.c 8.1 (Berkeley) 6/6/93";
+#endif
+__RCSID("$NetBSD: reverse.c,v 1.23 2011/09/03 10:59:11 christos Exp $");
+#endif /* not lint */
+
+#include <sys/param.h>
+#include <sys/stat.h>
+#include <sys/mman.h>
+
+#include <limits.h>
+#include <errno.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include "extern.h"
+
+static void r_buf(FILE *);
+static void r_reg(FILE *, enum STYLE, off_t, struct stat *);
+
+/*
+ * reverse -- display input in reverse order by line.
+ *
+ * There are six separate cases for this -- regular and non-regular
+ * files by bytes, lines or the whole file.
+ *
+ * BYTES display N bytes
+ * REG mmap the file and display the lines
+ * NOREG cyclically read characters into a wrap-around buffer
+ *
+ * LINES display N lines
+ * REG mmap the file and display the lines
+ * NOREG cyclically read lines into a wrap-around array of buffers
+ *
+ * FILE display the entire file
+ * REG mmap the file and display the lines
+ * NOREG cyclically read input into a linked list of buffers
+ */
+void
+reverse(FILE *fp, enum STYLE style, off_t off, struct stat *sbp)
+{
+ if (style != REVERSE && off == 0)
+ return;
+
+ if (S_ISREG(sbp->st_mode))
+ r_reg(fp, style, off, sbp);
+ else
+ switch(style) {
+ case FBYTES:
+ case RBYTES:
+ (void)displaybytes(fp, off);
+ break;
+ case FLINES:
+ case RLINES:
+ (void)displaylines(fp, off);
+ break;
+ case REVERSE:
+ r_buf(fp);
+ break;
+ default:
+ break;
+ }
+}
+
+/*
+ * r_reg -- display a regular file in reverse order by line.
+ */
+static void
+r_reg(FILE *fp, enum STYLE style, off_t off, struct stat *sbp)
+{
+ off_t size;
+ int llen;
+ char *p;
+ char *start;
+
+ if (!(size = sbp->st_size))
+ return;
+
+ if ((uint64_t)size > SIZE_T_MAX) {
+ /* XXX: need a cleaner way to check this on amd64 */
+ errno = EFBIG;
+ xerr(0, "%s", fname);
+ return;
+ }
+
+ if ((start = mmap(NULL, (size_t)size, PROT_READ,
+ MAP_FILE|MAP_SHARED, fileno(fp), (off_t)0)) == MAP_FAILED) {
+ xerr(0, "%s", fname);
+ return;
+ }
+ p = start + size - 1;
+
+ if (style == RBYTES && off < size)
+ size = off;
+
+ /* Last char is special, ignore whether newline or not. */
+ for (llen = 1; --size; ++llen)
+ if (*--p == '\n') {
+ WR(p + 1, llen);
+ llen = 0;
+ if (style == RLINES && !--off) {
+ ++p;
+ break;
+ }
+ }
+ if (llen)
+ WR(p, llen);
+ if (munmap(start, (size_t)sbp->st_size))
+ xerr(0, "%s", fname);
+}
+
+typedef struct bf {
+ struct bf *next;
+ struct bf *prev;
+ int len;
+ char *l;
+} BF;
+
+/*
+ * r_buf -- display a non-regular file in reverse order by line.
+ *
+ * This is the function that saves the entire input, storing the data in a
+ * doubly linked list of buffers and then displays them in reverse order.
+ * It has the usual nastiness of trying to find the newlines, as there's no
+ * guarantee that a newline occurs anywhere in the file, let alone in any
+ * particular buffer. If we run out of memory, input is discarded (and the
+ * user warned).
+ */
+static void
+r_buf(FILE *fp)
+{
+ BF *mark, *tl, *tr;
+ int ch, len, llen;
+ char *p;
+ off_t enomem;
+
+#define BSZ (128 * 1024)
+ tl = NULL;
+ for (mark = NULL, enomem = 0;;) {
+ /*
+ * Allocate a new block and link it into place in a doubly
+ * linked list. If out of memory, toss the LRU block and
+ * keep going.
+ */
+ if (enomem) {
+ if (!mark) {
+ errno = ENOMEM;
+ xerr(1, NULL);
+ }
+ tl = tl->next;
+ enomem += tl->len;
+ } else if ((tl = malloc(sizeof(*tl))) == NULL ||
+ (tl->l = malloc(BSZ)) == NULL) {
+ if (tl)
+ free(tl);
+ if (!mark) {
+ errno = ENOMEM;
+ xerr(1, NULL);
+ }
+ tl = mark;
+ enomem += tl->len;
+ } else if (mark) {
+ tl->next = mark;
+ tl->prev = mark->prev;
+ mark->prev->next = tl;
+ mark->prev = tl;
+ } else {
+ mark = tl;
+ mark->next = mark->prev = mark;
+ }
+
+ /* Fill the block with input data. */
+ ch = 0;
+ for (p = tl->l, len = 0;
+ len < BSZ && (ch = getc(fp)) != EOF; ++len)
+ *p++ = ch;
+
+ /*
+ * If no input data for this block and we tossed some data,
+ * recover it.
+ */
+ if (!len) {
+ if (enomem)
+ enomem -= tl->len;
+ tl = tl->prev;
+ break;
+ }
+
+ tl->len = len;
+ if (ch == EOF)
+ break;
+ }
+
+ if (enomem) {
+ xerrx(0, "Warning: %lld bytes discarded", (long long)enomem);
+ }
+
+ /*
+ * Step through the blocks in the reverse order read. The last char
+ * is special, ignore whether newline or not.
+ */
+ for (mark = tl;;) {
+ for (p = tl->l + (len = tl->len) - 1, llen = 0; len--;
+ --p, ++llen)
+ if (*p == '\n') {
+ if (llen) {
+ WR(p + 1, llen);
+ llen = 0;
+ }
+ if (tl == mark)
+ continue;
+ for (tr = tl->next; tr->len; tr = tr->next) {
+ WR(tr->l, tr->len);
+ tr->len = 0;
+ if (tr == mark)
+ break;
+ }
+ }
+ tl->len = llen;
+ if ((tl = tl->prev) == mark)
+ break;
+ }
+ tl = tl->next;
+ if (tl->len) {
+ WR(tl->l, tl->len);
+ tl->len = 0;
+ }
+ while ((tl = tl->next)->len) {
+ WR(tl->l, tl->len);
+ tl->len = 0;
+ }
+}
diff --git a/usr.bin/tail/tac.1 b/usr.bin/tail/tac.1
new file mode 100644
index 0000000..d0dd1e8
--- /dev/null
+++ b/usr.bin/tail/tac.1
@@ -0,0 +1,56 @@
+.\" $NetBSD: tac.1,v 1.2 2017/10/01 22:35:23 kre Exp $
+.\"
+.\" Copyright (c) 2017 The NetBSD Foundation, Inc.
+.\" All rights reserved.
+.\"
+.\" This code is derived from software contributed to The NetBSD Foundation
+.\" by Maya Rashish.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\"
+.\" 1. Redistributions of source code must retain the above copyright
+.\" notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must reproduce the above copyright
+.\" notice, this list of conditions and the following disclaimer in
+.\" the documentation and/or other materials provided with the
+.\" distribution.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+.\" ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+.\" LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+.\" FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+.\" COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+.\" INCIDENTAL, SPECIAL, EXEMPLARY OR CONSEQUENTIAL DAMAGES (INCLUDING,
+.\" BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+.\" LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+.\" AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+.\" OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+.\" OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+.\" SUCH DAMAGE.
+.\"
+.Dd October 1, 2017
+.Dt TAC 1
+.Os
+.Sh NAME
+.Nm tac
+.Nd display file in reverse
+.Sh SYNOPSIS
+.Nm
+.Op Ar file ...
+.Sh DESCRIPTION
+.Nm
+.Pq cat backwards
+outputs the contents of each of each of the specified files,
+or the standard input if no files are specified,
+in reverse line order to the standard output.
+.Sh EXIT STATUS
+.Ex -std tac
+.Sh COMPATIBILITY
+The
+.Nm
+utility doesn't support any of the options of GNU tac.
+.Sh SEE ALSO
+.Xr cat 1 ,
+.Xr tail 1
diff --git a/usr.bin/tail/tail.1 b/usr.bin/tail/tail.1
new file mode 100644
index 0000000..1a68f88
--- /dev/null
+++ b/usr.bin/tail/tail.1
@@ -0,0 +1,202 @@
+.\" $NetBSD: tail.1,v 1.20 2017/10/15 03:57:47 pgoyette Exp $
+.\"
+.\" Copyright (c) 1980, 1990, 1991, 1993
+.\" The Regents of the University of California. All rights reserved.
+.\"
+.\" This code is derived from software contributed to Berkeley by
+.\" the Institute of Electrical and Electronics Engineers, Inc.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\" notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must reproduce the above copyright
+.\" notice, this list of conditions and the following disclaimer in the
+.\" documentation and/or other materials provided with the distribution.
+.\" 3. Neither the name of the University nor the names of its contributors
+.\" may be used to endorse or promote products derived from this software
+.\" without specific prior written permission.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+.\" ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+.\" SUCH DAMAGE.
+.\"
+.\" @(#)tail.1 8.1 (Berkeley) 6/6/93
+.\"
+.Dd October 1, 2017
+.Dt TAIL 1
+.Os
+.Sh NAME
+.Nm tail
+.Nd display the last part of a file
+.Sh SYNOPSIS
+.Nm
+.Fl qv
+.Oo
+.Fl f |
+.Fl F |
+.Fl r
+.Oc
+.Oo
+.Fl b Ar number |
+.Fl c Ar number |
+.Fl n Ar number
+.Oc
+.Op Ar file ...
+.Sh DESCRIPTION
+The
+.Nm
+utility displays the contents of
+.Ar file
+or, by default, its standard input, to the standard output.
+.Pp
+The display begins at a byte, line or 512-byte block location in the
+input.
+Numbers having a leading plus (``+'') sign are relative to the beginning
+of the input, for example,
+.Dq -c +2
+starts the display at the second
+byte of the input.
+Numbers having a leading minus (``-'') sign or no explicit sign are
+relative to the end of the input, for example,
+.Dq -n 2
+displays the last two lines of the input.
+The default starting location is
+.Dq -n 10 ,
+or the last 10 lines of the input.
+.Pp
+The options are as follows:
+.Bl -tag -width Ds
+.It Fl b Ar number
+The location is
+.Ar number
+512-byte blocks.
+.It Fl c Ar number
+The location is
+.Ar number
+bytes.
+.It Fl f
+The
+.Fl f
+option causes
+.Nm
+not to stop when end of file is reached, but rather to wait for additional
+data to be appended to the input.
+The
+.Fl f
+option is ignored if there are no file arguments and the standard
+input is a pipe or a FIFO.
+.It Fl F
+The
+.Fl F
+option is the same as the
+.Fl f
+option, except that every five seconds
+.Nm
+will check to see if the file named on the command line has been
+shortened or moved (it is considered moved if the inode or device
+number changes) and, if so, it will close
+the current file, open the filename given, print out the entire
+contents, and continue to wait for more data to be appended.
+This option is used to follow log files though rotation by
+.Xr newsyslog 8
+or similar programs.
+.It Fl n Ar number
+The location is
+.Ar number
+lines.
+.It Fl q
+Do not prepend a header for each file, even if multiple files
+are specified.
+.It Fl r
+The
+.Fl r
+option causes the input to be displayed in reverse order, by line.
+Additionally, this option changes the meaning of the
+.Fl b ,
+.Fl c
+and
+.Fl n
+options.
+When the
+.Fl r
+option is specified, these options specify the number of bytes, lines
+or 512-byte blocks to display, instead of the bytes, lines or blocks
+from the beginning or end of the input from which to begin the display.
+The default for the
+.Fl r
+option is to display all of the input.
+.It Fl v
+Prepend each file with a header.
+.El
+.Pp
+If more than a single file is specified, or the
+.Fl v
+option is used, each file is preceded by a
+header consisting of the string
+.Dq ==> XXX \*[Le]=
+where
+.Dq XXX
+is the name of the file.
+The
+.Fl q
+flag disables the printing of the header in all cases.
+.Sh EXIT STATUS
+.Ex -std tail
+.Sh SEE ALSO
+.Xr cat 1 ,
+.Xr head 1 ,
+.Xr sed 1
+.Sh STANDARDS
+The
+.Nm
+utility is expected to be a superset of the
+.St -p1003.2-92
+specification.
+In particular, the
+.Fl b ,
+.Fl r
+and
+.Fl F
+options are extensions to that standard.
+.Pp
+The historic command line syntax of
+.Nm
+is supported by this implementation.
+The only difference between this implementation and historic versions
+of
+.Nm ,
+once the command line syntax translation has been done, is that the
+.Fl b ,
+.Fl c
+and
+.Fl n
+options modify the
+.Fl r
+option, i.e., ``-r -c 4'' displays the last 4 characters of the last line
+of the input, while the historic tail (using the historic syntax ``-4cr'')
+would ignore the
+.Fl c
+option and display the last 4 lines of the input.
+.Sh HISTORY
+A
+.Nm
+command appeared in
+.At v7 .
+.Sh BUGS
+When using the
+.Fl F
+option,
+.Nm
+will not detect a file truncation if, between the truncation
+and the next check of the file size, data written to the file make
+it larger than the last known file size.
diff --git a/usr.bin/tail/tail.c b/usr.bin/tail/tail.c
new file mode 100644
index 0000000..dff9559
--- /dev/null
+++ b/usr.bin/tail/tail.c
@@ -0,0 +1,323 @@
+/*-
+ * Copyright (c) 1991, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Edward Sze-Tyan Wang.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+#ifndef lint
+__COPYRIGHT("@(#) Copyright (c) 1991, 1993\
+ The Regents of the University of California. All rights reserved.");
+#endif /* not lint */
+
+#ifndef lint
+#if 0
+static char sccsid[] = "@(#)tail.c 8.1 (Berkeley) 6/6/93";
+#endif
+__RCSID("$NetBSD: tail.c,v 1.20 2018/03/06 03:33:26 eadler Exp $");
+#endif /* not lint */
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <errno.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include "extern.h"
+
+int fflag, rflag, rval;
+const char *fname;
+
+static void obsolete(char **);
+static void usage(void) __dead;
+
+int
+main(int argc, char *argv[])
+{
+ struct stat sb;
+ FILE *fp;
+ off_t off;
+ enum STYLE style;
+ int ch, first;
+ char *p;
+ int qflag = 0;
+ int vflag = 0;
+
+ setprogname(argv[0]);
+ off = 0;
+ /*
+ * Tail's options are weird. First, -n10 is the same as -n-10, not
+ * -n+10. Second, the number options are 1 based and not offsets,
+ * so -n+1 is the first line, and -c-1 is the last byte. Third, the
+ * number options for the -r option specify the number of things that
+ * get displayed, not the starting point in the file. The one major
+ * incompatibility in this version as compared to historical versions
+ * is that the 'r' option couldn't be modified by the -lbc options,
+ * i.e., it was always done in lines. This version treats -rc as a
+ * number of characters in reverse order. Finally, the default for
+ * -r is the entire file, not 10 lines.
+ */
+#define ARG(units, forward, backward) { \
+ if (style) \
+ usage(); \
+ off = strtoll(optarg, &p, 10) * (units); \
+ if (*p) \
+ xerrx(1, "illegal offset -- %s", optarg); \
+ switch(optarg[0]) { \
+ case '+': \
+ if (off) \
+ off -= (units); \
+ style = (forward); \
+ break; \
+ case '-': \
+ off = -off; \
+ /* FALLTHROUGH */ \
+ default: \
+ style = (backward); \
+ break; \
+ } \
+}
+
+ obsolete(argv);
+ style = NOTSET;
+ if (strcmp(getprogname(), "tac") == 0) {
+ qflag = 1;
+ vflag = 0;
+ rflag = 1;
+ argc -= 1;
+ argv += 1;
+ } else { /* tail */
+ while ((ch = getopt(argc, argv, "Fb:c:fn:rqv")) != -1)
+ switch(ch) {
+ case 'F':
+ fflag = 2;
+ break;
+ case 'b':
+ ARG(512, FBYTES, RBYTES);
+ break;
+ case 'c':
+ ARG(1, FBYTES, RBYTES);
+ break;
+ case 'f':
+ fflag = 1;
+ break;
+ case 'n':
+ ARG(1, FLINES, RLINES);
+ break;
+ case 'r':
+ rflag = 1;
+ break;
+ case 'q':
+ qflag = 1;
+ vflag = 0;
+ break;
+ case 'v':
+ qflag = 0;
+ vflag = 1;
+ break;
+ case '?':
+ default:
+ usage();
+ }
+ argc -= optind;
+ argv += optind;
+ }
+
+ if (fflag && argc > 1)
+ xerrx(1,
+ "-f and -F options only appropriate for a single file");
+
+ /*
+ * If displaying in reverse, don't permit follow option, and convert
+ * style values.
+ */
+ if (rflag) {
+ if (fflag)
+ usage();
+ if (style == FBYTES)
+ style = RBYTES;
+ else if (style == FLINES)
+ style = RLINES;
+ }
+
+ /*
+ * If style not specified, the default is the whole file for -r, and
+ * the last 10 lines if not -r.
+ */
+ if (style == NOTSET) {
+ if (rflag) {
+ off = 0;
+ style = REVERSE;
+ } else {
+ off = 10;
+ style = RLINES;
+ }
+ }
+ if (*argv)
+ for (first = 1; (fname = *argv++) != NULL;) {
+ if ((fp = fopen(fname, "r")) == NULL ||
+ fstat(fileno(fp), &sb)) {
+ ierr();
+ continue;
+ }
+ if (vflag || (qflag == 0 && argc > 1)) {
+ (void)printf("%s==> %s <==\n",
+ first ? "" : "\n", fname);
+ first = 0;
+ (void)fflush(stdout);
+ }
+
+ if (rflag)
+ reverse(fp, style, off, &sb);
+ else
+ forward(fp, style, off, &sb);
+ (void)fclose(fp);
+ }
+ else {
+ fname = "stdin";
+
+ if (fstat(fileno(stdin), &sb)) {
+ ierr();
+ exit(1);
+ }
+
+ /*
+ * Determine if input is a pipe. 4.4BSD will set the SOCKET
+ * bit in the st_mode field for pipes. Fix this then.
+ */
+ if (lseek(fileno(stdin), (off_t)0, SEEK_CUR) == -1 &&
+ errno == ESPIPE) {
+ errno = 0;
+ fflag = 0; /* POSIX.2 requires this. */
+ }
+
+ if (rflag)
+ reverse(stdin, style, off, &sb);
+ else
+ forward(stdin, style, off, &sb);
+ }
+ exit(rval);
+}
+
+/*
+ * Convert the obsolete argument form into something that getopt can handle.
+ * This means that anything of the form [+-][0-9][0-9]*[lbc][fr] that isn't
+ * the option argument for a -b, -c or -n option gets converted.
+ */
+static void
+obsolete(char *argv[])
+{
+ char *ap, *p, *t;
+ size_t len;
+ char *start;
+
+ while ((ap = *++argv) != NULL) {
+ /* Return if "--" or not an option of any form. */
+ if (ap[0] != '-') {
+ if (ap[0] != '+')
+ return;
+ } else if (ap[1] == '-')
+ return;
+
+ switch (*++ap) {
+ /* Old-style option. */
+ case '0': case '1': case '2': case '3': case '4':
+ case '5': case '6': case '7': case '8': case '9':
+
+ /* Malloc space for dash, new option and argument. */
+ len = strlen(*argv);
+ if ((start = p = malloc(len + 3)) == NULL)
+ xerr(1, "malloc");
+ *p++ = '-';
+
+ /*
+ * Go to the end of the option argument. Save off any
+ * trailing options (-3lf) and translate any trailing
+ * output style characters.
+ */
+ t = *argv + len - 1;
+ if (*t == 'f' || *t == 'r') {
+ *p++ = *t;
+ *t-- = '\0';
+ }
+ switch(*t) {
+ case 'b':
+ *p++ = 'b';
+ *t = '\0';
+ break;
+ case 'c':
+ *p++ = 'c';
+ *t = '\0';
+ break;
+ case 'l':
+ *t = '\0';
+ /* FALLTHROUGH */
+ case '0': case '1': case '2': case '3': case '4':
+ case '5': case '6': case '7': case '8': case '9':
+ *p++ = 'n';
+ break;
+ default:
+ xerrx(1, "illegal option -- %s", *argv);
+ }
+ *p++ = *argv[0];
+ (void)strcpy(p, ap);
+ *argv = start;
+ continue;
+
+ /*
+ * Options w/ arguments, skip the argument and continue
+ * with the next option.
+ */
+ case 'b':
+ case 'c':
+ case 'n':
+ if (!ap[1])
+ ++argv;
+ /* FALLTHROUGH */
+ /* Options w/o arguments, continue with the next option. */
+ case 'f':
+ case 'r':
+ continue;
+
+ /* Illegal option, return and let getopt handle it. */
+ default:
+ return;
+ }
+ }
+}
+
+static void
+usage(void)
+{
+ (void)fprintf(stderr,
+ "Usage: %s [-qv] [-f | -F | -r] [-b # | -c # | -n #] [file ...]\n",
+ getprogname());
+ exit(1);
+}
diff --git a/usr.bin/talk/ctl.c b/usr.bin/talk/ctl.c
new file mode 100644
index 0000000..ef6f924
--- /dev/null
+++ b/usr.bin/talk/ctl.c
@@ -0,0 +1,125 @@
+/* $NetBSD: ctl.c,v 1.10 2012/03/20 20:34:59 matt Exp $ */
+
+/*
+ * Copyright (c) 1983, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+#ifndef lint
+#if 0
+static char sccsid[] = "@(#)ctl.c 8.1 (Berkeley) 6/6/93";
+#endif
+__RCSID("$NetBSD: ctl.c,v 1.10 2012/03/20 20:34:59 matt Exp $");
+#endif /* not lint */
+
+/*
+ * This file handles haggling with the various talk daemons to
+ * get a socket to talk to. sockt is opened and connected in
+ * the progress
+ */
+
+#include "talk.h"
+#include <arpa/inet.h>
+#include "talk_ctl.h"
+
+struct sockaddr_in daemon_addr = { sizeof(daemon_addr), AF_INET, 0, {0}, {0} };
+struct sockaddr_in ctl_addr = { sizeof(ctl_addr), AF_INET, 0, {0}, {0} };
+struct sockaddr_in my_addr = { sizeof(my_addr), AF_INET, 0, {0}, {0} };
+
+ /* inet addresses of the two machines */
+struct in_addr my_machine_addr;
+struct in_addr his_machine_addr;
+
+u_short daemon_port; /* port number of the talk daemon */
+
+int ctl_sockt;
+int sockt;
+int invitation_waiting = 0;
+
+CTL_MSG msg;
+
+void
+open_sockt(void)
+{
+ socklen_t length;
+
+
+ (void)memset(&my_addr, 0, sizeof(my_addr));
+ my_addr.sin_family = AF_INET;
+#ifdef BSD4_4
+ my_addr.sin_len = sizeof(my_addr);
+#endif
+ my_addr.sin_addr = my_machine_addr;
+ my_addr.sin_port = 0;
+ sockt = socket(AF_INET, SOCK_STREAM, 0);
+ if (sockt == -1)
+ p_error("Bad socket");
+ if (bind(sockt, (struct sockaddr *)&my_addr, sizeof(my_addr)) != 0)
+ p_error("Binding local socket");
+ length = sizeof(my_addr);
+ if (getsockname(sockt, (struct sockaddr *)&my_addr, &length) == -1)
+ p_error("Bad address for socket");
+}
+
+/* open the ctl socket */
+void
+open_ctl(void)
+{
+ socklen_t length;
+
+ (void)memset(&ctl_addr, 0, sizeof(ctl_addr));
+ ctl_addr.sin_family = AF_INET;
+#ifdef BSD4_4
+ ctl_addr.sin_len = sizeof(my_addr);
+#endif
+ ctl_addr.sin_port = 0;
+ ctl_addr.sin_addr = my_machine_addr;
+ ctl_sockt = socket(AF_INET, SOCK_DGRAM, 0);
+ if (ctl_sockt == -1)
+ p_error("Bad socket");
+ if (bind(ctl_sockt,
+ (struct sockaddr *)&ctl_addr, sizeof(ctl_addr)) != 0)
+ p_error("Couldn't bind to control socket");
+ length = sizeof(ctl_addr);
+ if (getsockname(ctl_sockt,
+ (struct sockaddr *)&ctl_addr, &length) == -1)
+ p_error("Bad address for ctl socket");
+}
+
+/* print_addr is a debug print routine */
+void
+print_addr(struct sockaddr_in addr)
+{
+ int i;
+
+ printf("addr = %s, port = %o, family = %o zero = ",
+ inet_ntoa(addr.sin_addr), addr.sin_port, addr.sin_family);
+ for (i = 0; i<8;i++)
+ printf("%o ", (int)addr.sin_zero[i]);
+ putchar('\n');
+}
diff --git a/usr.bin/talk/ctl_transact.c b/usr.bin/talk/ctl_transact.c
new file mode 100644
index 0000000..4d0613f
--- /dev/null
+++ b/usr.bin/talk/ctl_transact.c
@@ -0,0 +1,108 @@
+/* $NetBSD: ctl_transact.c,v 1.11 2011/09/06 18:32:03 joerg Exp $ */
+
+/*
+ * Copyright (c) 1983, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+#ifndef lint
+#if 0
+static char sccsid[] = "@(#)ctl_transact.c 8.1 (Berkeley) 6/6/93";
+#endif
+__RCSID("$NetBSD: ctl_transact.c,v 1.11 2011/09/06 18:32:03 joerg Exp $");
+#endif /* not lint */
+
+#include "talk.h"
+#include <sys/time.h>
+#include <errno.h>
+#include <unistd.h>
+#include <poll.h>
+#include "talk_ctl.h"
+
+#define CTL_WAIT 2 /* time to wait for a response, in seconds */
+
+/*
+ * SOCKDGRAM is unreliable, so we must repeat messages if we have
+ * not received an acknowledgement within a reasonable amount
+ * of time
+ */
+void
+ctl_transact(struct in_addr target, CTL_MSG tmsg, int type, CTL_RESPONSE *rp)
+{
+ struct pollfd set[1];
+ int nready, cc;
+
+ nready = 0;
+ tmsg.type = type;
+ daemon_addr.sin_addr = target;
+ daemon_addr.sin_port = daemon_port;
+ set[0].fd = ctl_sockt;
+ set[0].events = POLLIN;
+
+ /*
+ * Keep sending the message until a response of
+ * the proper type is obtained.
+ */
+ do {
+ /* resend message until a response is obtained */
+ do {
+ cc = sendto(ctl_sockt, (char *)&tmsg, sizeof (tmsg), 0,
+ (struct sockaddr *)&daemon_addr,
+ sizeof (daemon_addr));
+ if (cc != sizeof (tmsg)) {
+ if (errno == EINTR)
+ continue;
+ p_error("Error on write to talk daemon");
+ }
+ nready = poll(set, 1, CTL_WAIT * 1000);
+ if (nready < 0) {
+ if (errno == EINTR)
+ continue;
+ p_error("Error waiting for daemon response");
+ }
+ } while (nready == 0);
+ /*
+ * Keep reading while there are queued messages
+ * (this is not necessary, it just saves extra
+ * request/acknowledgements being sent)
+ */
+ do {
+ cc = recv(ctl_sockt, (char *)rp, sizeof (*rp), 0);
+ if (cc < 0) {
+ if (errno == EINTR)
+ continue;
+ p_error("Error on read from talk daemon");
+ }
+ /* an immediate poll */
+ nready = poll(set, 1, 0);
+ } while (nready > 0 && (rp->vers != TALK_VERSION ||
+ rp->type != type));
+ } while (rp->vers != TALK_VERSION || rp->type != type);
+ rp->id_num = ntohl(rp->id_num);
+ rp->addr.sa_family = ntohs(rp->addr.sa_family);
+}
diff --git a/usr.bin/talk/display.c b/usr.bin/talk/display.c
new file mode 100644
index 0000000..7017f3f
--- /dev/null
+++ b/usr.bin/talk/display.c
@@ -0,0 +1,195 @@
+/* $NetBSD: display.c,v 1.9 2011/09/06 18:32:03 joerg Exp $ */
+
+/*
+ * Copyright (c) 1983, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+#ifndef lint
+#if 0
+static char sccsid[] = "@(#)display.c 8.1 (Berkeley) 6/6/93";
+#endif
+__RCSID("$NetBSD: display.c,v 1.9 2011/09/06 18:32:03 joerg Exp $");
+#endif /* not lint */
+
+/*
+ * The window 'manager', initializes curses and handles the actual
+ * displaying of text
+ */
+#include "talk.h"
+#include <ctype.h>
+
+xwin_t my_win;
+xwin_t his_win;
+WINDOW *line_win;
+
+int curses_initialized = 0;
+
+/*
+ * max HAS to be a function, it is called with
+ * a argument of the form --foo at least once.
+ */
+int
+max(int a, int b)
+{
+
+ return (a > b ? a : b);
+}
+
+/*
+ * Display some text on somebody's window, processing some control
+ * characters while we are at it.
+ */
+void
+display(xwin_t *win, char *text, int size)
+{
+ int i;
+ char cch;
+
+ for (i = 0; i < size; i++) {
+ if (*text == '\n') {
+ xscroll(win, 0);
+ text++;
+ continue;
+ }
+ /* erase character */
+ if (*text == win->cerase) {
+ wmove(win->x_win, win->x_line, max(--win->x_col, 0));
+ getyx(win->x_win, win->x_line, win->x_col);
+ waddch(win->x_win, ' ');
+ wmove(win->x_win, win->x_line, win->x_col);
+ getyx(win->x_win, win->x_line, win->x_col);
+ text++;
+ continue;
+ }
+ /*
+ * On word erase search backwards until we find
+ * the beginning of a word or the beginning of
+ * the line.
+ */
+ if (*text == win->werase) {
+ int endcol, xcol, j, c;
+
+ endcol = win->x_col;
+ xcol = endcol - 1;
+ while (xcol >= 0) {
+ c = readwin(win->x_win, win->x_line, xcol);
+ if (c != ' ')
+ break;
+ xcol--;
+ }
+ while (xcol >= 0) {
+ c = readwin(win->x_win, win->x_line, xcol);
+ if (c == ' ')
+ break;
+ xcol--;
+ }
+ wmove(win->x_win, win->x_line, xcol + 1);
+ for (j = xcol + 1; j < endcol; j++)
+ waddch(win->x_win, ' ');
+ wmove(win->x_win, win->x_line, xcol + 1);
+ getyx(win->x_win, win->x_line, win->x_col);
+ text++;
+ continue;
+ }
+ /* line kill */
+ if (*text == win->kill) {
+ wmove(win->x_win, win->x_line, 0);
+ wclrtoeol(win->x_win);
+ getyx(win->x_win, win->x_line, win->x_col);
+ text++;
+ continue;
+ }
+ if (*text == '\f') {
+ if (win == &my_win)
+ wrefresh(curscr);
+ text++;
+ continue;
+ }
+ if (*text == '\07') {
+ beep();
+ text++;
+ continue;
+ }
+ if (win->x_col == COLS-1) {
+ /* check for wraparound */
+ xscroll(win, 0);
+ }
+ if ( !isprint((u_char)*text) && *text != '\t') {
+ waddch(win->x_win, '^');
+ getyx(win->x_win, win->x_line, win->x_col);
+ if (win->x_col == COLS-1) /* check for wraparound */
+ xscroll(win, 0);
+ cch = (*text & 63) + 64;
+ waddch(win->x_win, cch);
+ } else
+ waddch(win->x_win, *text);
+ getyx(win->x_win, win->x_line, win->x_col);
+ text++;
+ }
+ wrefresh(win->x_win);
+}
+
+/*
+ * Read the character at the indicated position in win
+ */
+int
+readwin(WINDOW *win, int line, int col)
+{
+ int oldline, oldcol;
+ int c;
+
+ getyx(win, oldline, oldcol);
+ wmove(win, line, col);
+ c = winch(win);
+ wmove(win, oldline, oldcol);
+ return (c);
+}
+
+/*
+ * Scroll a window, blanking out the line following the current line
+ * so that the current position is obvious
+ */
+void
+xscroll(xwin_t *win, int flag)
+{
+
+ if (flag == -1) {
+ wmove(win->x_win, 0, 0);
+ win->x_line = 0;
+ win->x_col = 0;
+ return;
+ }
+ win->x_line = (win->x_line + 1) % win->x_nlines;
+ win->x_col = 0;
+ wmove(win->x_win, win->x_line, win->x_col);
+ wclrtoeol(win->x_win);
+ wmove(win->x_win, (win->x_line + 1) % win->x_nlines, win->x_col);
+ wclrtoeol(win->x_win);
+ wmove(win->x_win, win->x_line, win->x_col);
+}
diff --git a/usr.bin/talk/get_addrs.c b/usr.bin/talk/get_addrs.c
new file mode 100644
index 0000000..f381cc7
--- /dev/null
+++ b/usr.bin/talk/get_addrs.c
@@ -0,0 +1,81 @@
+/* $NetBSD: get_addrs.c,v 1.12 2015/06/16 22:54:11 christos Exp $ */
+
+/*
+ * Copyright (c) 1983, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+#ifndef lint
+#if 0
+static char sccsid[] = "@(#)get_addrs.c 8.1 (Berkeley) 6/6/93";
+#endif
+__RCSID("$NetBSD: get_addrs.c,v 1.12 2015/06/16 22:54:11 christos Exp $");
+#endif /* not lint */
+
+#include "talk.h"
+#include <netdb.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <err.h>
+#include "talk_ctl.h"
+
+void
+get_addrs(const char *my_machine_name, const char *his_machine_name)
+{
+ struct hostent *hp;
+ struct servent *sp;
+
+ msg.pid = htonl(getpid());
+ /*
+ * If the callee is on-machine, just use loopback
+ * otherwise do a lookup...
+ */
+ if (strcmp(his_machine_name, my_machine_name) != 0) {
+ /* look up the address of the local host */
+ hp = gethostbyname(my_machine_name);
+ if (hp == NULL)
+ errx(EXIT_FAILURE, "%s: %s", my_machine_name,
+ hstrerror(h_errno));
+ memcpy(&my_machine_addr, hp->h_addr, sizeof(my_machine_addr));
+ hp = gethostbyname(his_machine_name);
+ if (hp == NULL)
+ errx(EXIT_FAILURE, "%s: %s", his_machine_name,
+ hstrerror(h_errno));
+ memcpy(&his_machine_addr, hp->h_addr, sizeof(his_machine_addr));
+ } else
+ his_machine_addr.s_addr = my_machine_addr.s_addr =
+ htonl(INADDR_LOOPBACK);
+
+ /* find the server's port */
+ sp = getservbyname("ntalk", "udp");
+ if (sp == 0)
+ errx(EXIT_FAILURE, "%s/%s: service is not registered.",
+ "ntalk", "udp");
+ daemon_port = sp->s_port;
+}
diff --git a/usr.bin/talk/get_names.c b/usr.bin/talk/get_names.c
new file mode 100644
index 0000000..5400548
--- /dev/null
+++ b/usr.bin/talk/get_names.c
@@ -0,0 +1,124 @@
+/* $NetBSD: get_names.c,v 1.16 2012/12/29 23:44:23 christos Exp $ */
+
+/*
+ * Copyright (c) 1983, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+#ifndef lint
+#if 0
+static char sccsid[] = "@(#)get_names.c 8.1 (Berkeley) 6/6/93";
+#endif
+__RCSID("$NetBSD: get_names.c,v 1.16 2012/12/29 23:44:23 christos Exp $");
+#endif /* not lint */
+
+#include "talk.h"
+#include <err.h>
+#include <sys/param.h>
+#include <pwd.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <util.h>
+
+extern CTL_MSG msg;
+
+/*
+ * Determine the local and remote user, tty, and machines
+ */
+void
+get_names(int argc, char *argv[])
+{
+ char hostname[MAXHOSTNAMELEN + 1];
+ const char *his_name, *my_name;
+ const char *my_machine_name, *his_machine_name;
+ const char *his_tty;
+ char *cp;
+ char *names;
+
+ if (argc < 2 ) {
+ fprintf(stderr, "Usage: %s user [ttyname]\n", getprogname());
+ exit(1);
+ }
+ if (!isatty(0))
+ errx(EXIT_FAILURE, "Standard input must be a tty, "
+ "not a pipe or a file");
+ if ((my_name = getlogin()) == NULL) {
+ struct passwd *pw;
+
+ if ((pw = getpwuid(getuid())) == NULL)
+ errx(EXIT_FAILURE, "You don't exist. Go away.");
+ my_name = pw->pw_name;
+ }
+ if ((cp = getenv("TALKHOST")) != NULL)
+ (void)estrlcpy(hostname, cp, sizeof(hostname));
+ else {
+ if (gethostname(hostname, sizeof(hostname)) == -1)
+ err(EXIT_FAILURE, "gethostname");
+ hostname[sizeof(hostname) - 1] = '\0';
+ }
+ my_machine_name = hostname;
+ /* check for, and strip out, the machine name of the target */
+ names = strdup(argv[1]);
+ for (cp = names; *cp && !strchr("@:!.", *cp); cp++)
+ ;
+ if (*cp == '\0') {
+ /* this is a local to local talk */
+ his_name = names;
+ his_machine_name = my_machine_name;
+ } else {
+ if (*cp++ == '@') {
+ /* user@host */
+ his_name = names;
+ his_machine_name = cp;
+ } else {
+ /* host.user or host!user or host:user */
+ his_name = cp;
+ his_machine_name = names;
+ }
+ *--cp = '\0';
+ }
+ if (argc > 2)
+ his_tty = argv[2]; /* tty name is arg 2 */
+ else
+ his_tty = "";
+ get_addrs(my_machine_name, his_machine_name);
+ /*
+ * Initialize the message template.
+ */
+ msg.vers = TALK_VERSION;
+ msg.addr.sa_family = htons(AF_INET);
+ msg.ctl_addr.sa_family = htons(AF_INET);
+ msg.id_num = htonl(0);
+ strncpy(msg.l_name, my_name, NAME_SIZE);
+ msg.l_name[NAME_SIZE - 1] = '\0';
+ strncpy(msg.r_name, his_name, NAME_SIZE);
+ msg.r_name[NAME_SIZE - 1] = '\0';
+ strncpy(msg.r_tty, his_tty, TTY_SIZE);
+ msg.r_tty[TTY_SIZE - 1] = '\0';
+ free(names);
+}
diff --git a/usr.bin/talk/init_disp.c b/usr.bin/talk/init_disp.c
new file mode 100644
index 0000000..a006705
--- /dev/null
+++ b/usr.bin/talk/init_disp.c
@@ -0,0 +1,153 @@
+/* $NetBSD: init_disp.c,v 1.12 2011/09/06 18:32:03 joerg Exp $ */
+
+/*
+ * Copyright (c) 1983, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+#ifndef lint
+#if 0
+static char sccsid[] = "@(#)init_disp.c 8.2 (Berkeley) 2/16/94";
+#endif
+__RCSID("$NetBSD: init_disp.c,v 1.12 2011/09/06 18:32:03 joerg Exp $");
+#endif /* not lint */
+
+/*
+ * Initialization code for the display package,
+ * as well as the signal handling routines.
+ */
+
+#include "talk.h"
+#include <sys/ioctl.h>
+#include <sys/ioctl_compat.h>
+#include <err.h>
+#include <signal.h>
+#include <termios.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+/*
+ * Set up curses, catch the appropriate signals,
+ * and build the various windows.
+ */
+void
+init_display(void)
+{
+ struct sigaction sa;
+
+ if (initscr() == NULL)
+ errx(1, "Terminal type unset or lacking necessary features.");
+ (void)sigaction(SIGTSTP, NULL, &sa);
+ sigaddset(&sa.sa_mask, SIGALRM);
+ (void)sigaction(SIGTSTP, &sa, NULL);
+ curses_initialized = 1;
+ clear();
+ refresh();
+ noecho();
+ cbreak();
+ signal(SIGINT, sig_sent);
+ signal(SIGPIPE, sig_sent);
+ /* curses takes care of ^Z */
+ my_win.x_nlines = LINES / 2;
+ my_win.x_ncols = COLS;
+ my_win.x_win = newwin(my_win.x_nlines, my_win.x_ncols, 0, 0);
+ scrollok(my_win.x_win, FALSE);
+ wclear(my_win.x_win);
+
+ his_win.x_nlines = LINES / 2 - 1;
+ his_win.x_ncols = COLS;
+ his_win.x_win = newwin(his_win.x_nlines, his_win.x_ncols,
+ my_win.x_nlines+1, 0);
+ scrollok(his_win.x_win, FALSE);
+ wclear(his_win.x_win);
+
+ line_win = newwin(1, COLS, my_win.x_nlines, 0);
+ box(line_win, '-', '-');
+ wrefresh(line_win);
+ /* let them know we are working on it */
+ current_state = "No connection yet";
+}
+
+/*
+ * Trade edit characters with the other talk. By agreement
+ * the first three characters each talk transmits after
+ * connection are the three edit characters.
+ */
+void
+set_edit_chars(void)
+{
+ char buf[3];
+ int cc;
+ struct termios tty;
+
+ tcgetattr(0, &tty);
+ my_win.cerase = tty.c_cc[VERASE];
+ my_win.kill = tty.c_cc[VKILL];
+ if (tty.c_cc[VWERASE] == (unsigned char) -1)
+ my_win.werase = '\027'; /* control W */
+ else
+ my_win.werase = tty.c_cc[VWERASE];
+ buf[0] = my_win.cerase;
+ buf[1] = my_win.kill;
+ buf[2] = my_win.werase;
+ cc = write(sockt, buf, sizeof(buf));
+ if (cc != sizeof(buf) )
+ p_error("Lost the connection");
+ cc = read(sockt, buf, sizeof(buf));
+ if (cc != sizeof(buf) )
+ p_error("Lost the connection");
+ his_win.cerase = buf[0];
+ his_win.kill = buf[1];
+ his_win.werase = buf[2];
+}
+
+void
+sig_sent(int dummy)
+{
+
+ message("Connection closing. Exiting");
+ quit();
+}
+
+/*
+ * All done talking...hang up the phone and reset terminal thingy's
+ */
+void
+quit(void)
+{
+
+ if (curses_initialized) {
+ wmove(his_win.x_win, his_win.x_nlines-1, 0);
+ wclrtoeol(his_win.x_win);
+ wrefresh(his_win.x_win);
+ endwin();
+ }
+ if (invitation_waiting)
+ send_delete();
+ exit(0);
+}
diff --git a/usr.bin/talk/invite.c b/usr.bin/talk/invite.c
new file mode 100644
index 0000000..680744c
--- /dev/null
+++ b/usr.bin/talk/invite.c
@@ -0,0 +1,190 @@
+/* $NetBSD: invite.c,v 1.10 2012/12/29 23:44:23 christos Exp $ */
+
+/*
+ * Copyright (c) 1983, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+#ifndef lint
+#if 0
+static char sccsid[] = "@(#)invite.c 8.1 (Berkeley) 6/6/93";
+#endif
+__RCSID("$NetBSD: invite.c,v 1.10 2012/12/29 23:44:23 christos Exp $");
+#endif /* not lint */
+
+#include "talk.h"
+#include <sys/time.h>
+#include <signal.h>
+#include <errno.h>
+#include <setjmp.h>
+#include <unistd.h>
+#include <err.h>
+#include "talk_ctl.h"
+
+/*
+ * There wasn't an invitation waiting, so send a request containing
+ * our sockt address to the remote talk daemon so it can invite
+ * him
+ */
+
+/*
+ * The msg.id's for the invitations
+ * on the local and remote machines.
+ * These are used to delete the
+ * invitations.
+ */
+static int local_id, remote_id;
+static jmp_buf invitebuf;
+
+void
+invite_remote(void)
+{
+ int new_sockt;
+ struct itimerval itimer;
+ CTL_RESPONSE response;
+
+ itimer.it_value.tv_sec = RING_WAIT;
+ itimer.it_value.tv_usec = 0;
+ itimer.it_interval = itimer.it_value;
+ if (listen(sockt, 5) != 0)
+ p_error("Error on attempt to listen for caller");
+#ifdef MSG_EOR
+ /* copy new style sockaddr to old, swap family (short in old) */
+ msg.addr = *(struct talkd_sockaddr *)(void *)&my_addr;
+ msg.addr.sa_family = htons(my_addr.sin_family);
+#else
+ msg.addr = *(struct sockaddr *)&my_addr;
+#endif
+ msg.id_num = htonl(-1); /* an impossible id_num */
+ invitation_waiting = 1;
+ announce_invite();
+ /*
+ * Shut off the automatic messages for a while,
+ * so we can use the interrupt timer to resend the invitation
+ */
+ end_msgs();
+ setitimer(ITIMER_REAL, &itimer, (struct itimerval *)0);
+ message("Waiting for your party to respond");
+ signal(SIGALRM, re_invite);
+ (void) setjmp(invitebuf);
+ while ((new_sockt = accept(sockt, 0, 0)) < 0) {
+ if (errno == EINTR)
+ continue;
+ p_error("Unable to connect with your party");
+ }
+ close(sockt);
+ sockt = new_sockt;
+
+ /*
+ * Have the daemons delete the invitations now that we
+ * have connected.
+ */
+ current_state = "Waiting for your party to respond";
+ start_msgs();
+
+ msg.id_num = htonl(local_id);
+ ctl_transact(my_machine_addr, msg, DELETE, &response);
+ msg.id_num = htonl(remote_id);
+ ctl_transact(his_machine_addr, msg, DELETE, &response);
+ invitation_waiting = 0;
+}
+
+/*
+ * Routine called on interrupt to re-invite the callee
+ */
+void
+re_invite(int dummy)
+{
+
+ message("Ringing your party again");
+ current_line++;
+ /* force a re-announce */
+ msg.id_num = htonl(remote_id + 1);
+ announce_invite();
+ longjmp(invitebuf, 1);
+}
+
+static const char *answers[] = {
+ "answer #0", /* SUCCESS */
+ "Your party is not logged on", /* NOT_HERE */
+ "Target machine is too confused to talk to us", /* FAILED */
+ "Target machine does not recognize us", /* MACHINE_UNKNOWN */
+ "Your party is refusing messages", /* PERMISSION_REFUSED */
+ "Target machine can not handle remote talk", /* UNKNOWN_REQUEST */
+ "Target machine indicates protocol mismatch", /* BADVERSION */
+ "Target machine indicates protocol botch (addr)",/* BADADDR */
+ "Target machine indicates protocol botch (ctl_addr)",/* BADCTLADDR */
+};
+#define NANSWERS (sizeof (answers) / sizeof (answers[0]))
+
+/*
+ * Transmit the invitation and process the response
+ */
+void
+announce_invite(void)
+{
+ CTL_RESPONSE response;
+
+ current_state = "Trying to connect to your party's talk daemon";
+ ctl_transact(his_machine_addr, msg, ANNOUNCE, &response);
+ remote_id = response.id_num;
+ if (response.answer != SUCCESS) {
+ if (response.answer < NANSWERS)
+ message(answers[response.answer]);
+ quit();
+ }
+ /* leave the actual invitation on my talk daemon */
+ ctl_transact(my_machine_addr, msg, LEAVE_INVITE, &response);
+ local_id = response.id_num;
+}
+
+/*
+ * Tell the daemon to remove your invitation
+ */
+void
+send_delete(void)
+{
+
+ msg.type = DELETE;
+ /*
+ * This is just a extra clean up, so just send it
+ * and don't wait for an answer
+ */
+ msg.id_num = htonl(remote_id);
+ daemon_addr.sin_addr = his_machine_addr;
+ if (sendto(ctl_sockt, &msg, sizeof (msg), 0,
+ (struct sockaddr *)&daemon_addr,
+ sizeof (daemon_addr)) != sizeof(msg))
+ warn("send_delete (remote)");
+ msg.id_num = htonl(local_id);
+ daemon_addr.sin_addr = my_machine_addr;
+ if (sendto(ctl_sockt, &msg, sizeof (msg), 0,
+ (struct sockaddr *)&daemon_addr,
+ sizeof (daemon_addr)) != sizeof (msg))
+ warn("send_delete (local)");
+}
diff --git a/usr.bin/talk/io.c b/usr.bin/talk/io.c
new file mode 100644
index 0000000..f789f57
--- /dev/null
+++ b/usr.bin/talk/io.c
@@ -0,0 +1,139 @@
+/* $NetBSD: io.c,v 1.14 2011/09/06 18:32:03 joerg Exp $ */
+
+/*
+ * Copyright (c) 1983, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+#ifndef lint
+#if 0
+static char sccsid[] = "@(#)io.c 8.1 (Berkeley) 6/6/93";
+#endif
+__RCSID("$NetBSD: io.c,v 1.14 2011/09/06 18:32:03 joerg Exp $");
+#endif /* not lint */
+
+/*
+ * This file contains the I/O handling and the exchange of
+ * edit characters. This connection itself is established in
+ * ctl.c
+ */
+
+#include "talk.h"
+#include <sys/ioctl.h>
+#include <sys/time.h>
+#include <stdio.h>
+#include <errno.h>
+#include <string.h>
+#include <unistd.h>
+#include <poll.h>
+
+#define A_LONG_TIME 1000000
+
+/*
+ * The routine to do the actual talking
+ */
+void
+talk(void)
+{
+ struct pollfd set[2];
+ int nb;
+ char buf[BUFSIZ];
+
+ message("Connection established\007\007\007");
+ current_line = 0;
+
+ /*
+ * Wait on both the other process (sockt_mask) and
+ * standard input ( STDIN_MASK )
+ */
+ set[0].fd = sockt;
+ set[0].events = POLLIN;
+ set[1].fd = fileno(stdin);
+ set[1].events = POLLIN;
+ for (;;) {
+ nb = poll(set, 2, A_LONG_TIME * 1000);
+ if (nb <= 0) {
+ if (errno == EINTR)
+ continue;
+ /* panic, we don't know what happened */
+ p_error("Unexpected error from poll");
+ quit();
+ }
+ if (set[0].revents & POLLIN) {
+ /* There is data on sockt */
+ nb = read(sockt, buf, sizeof buf);
+ if (nb <= 0) {
+ message("Connection closed. Exiting");
+ quit();
+ }
+ display(&his_win, buf, nb);
+ }
+ if (set[1].revents & POLLIN) {
+ /*
+ * We can't make the tty non_blocking, because
+ * curses's output routines would screw up
+ */
+ ioctl(0, FIONREAD, (void *) &nb);
+ nb = read(0, buf, nb);
+ display(&my_win, buf, nb);
+ /* might lose data here because sockt is non-blocking */
+ if (nb > 0)
+ write(sockt, buf, nb);
+ }
+ }
+}
+
+/*
+ * p_error prints the system error message on the standard location
+ * on the screen and then exits. (i.e. a curses version of perror)
+ */
+void
+p_error(const char *string)
+{
+ wmove(my_win.x_win, current_line%my_win.x_nlines, 0);
+ wprintw(my_win.x_win, "[%s : %s (%d)]\n",
+ string, strerror(errno), errno);
+ wrefresh(my_win.x_win);
+ move(LINES-1, 0);
+ refresh();
+ quit();
+}
+
+/*
+ * Display string in the standard location
+ */
+void
+message(const char *string)
+{
+ wmove(my_win.x_win, current_line % my_win.x_nlines, 0);
+ wprintw(my_win.x_win, "[%s]", string);
+ wclrtoeol(my_win.x_win);
+ current_line++;
+ wmove(my_win.x_win, current_line % my_win.x_nlines, 0);
+ wrefresh(my_win.x_win);
+}
diff --git a/usr.bin/talk/look_up.c b/usr.bin/talk/look_up.c
new file mode 100644
index 0000000..1ff3d79
--- /dev/null
+++ b/usr.bin/talk/look_up.c
@@ -0,0 +1,119 @@
+/* $NetBSD: look_up.c,v 1.8 2012/03/20 20:34:59 matt Exp $ */
+
+/*
+ * Copyright (c) 1983, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+#ifndef lint
+#if 0
+static char sccsid[] = "@(#)look_up.c 8.1 (Berkeley) 6/6/93";
+#endif
+__RCSID("$NetBSD: look_up.c,v 1.8 2012/03/20 20:34:59 matt Exp $");
+#endif /* not lint */
+
+#include "talk.h"
+#include <errno.h>
+#include <unistd.h>
+#include "talk_ctl.h"
+
+/*
+ * See if the local daemon has an invitation for us.
+ */
+int
+check_local(void)
+{
+ CTL_RESPONSE response;
+ CTL_RESPONSE *rp = &response;
+ struct sockaddr addr;
+
+ /* the rest of msg was set up in get_names */
+#ifdef MSG_EOR
+ /* copy new style sockaddr to old, swap family (short in old) */
+ msg.ctl_addr = *(struct talkd_sockaddr *)(void *)&ctl_addr;
+ msg.ctl_addr.sa_family = htons(ctl_addr.sin_family);
+#else
+ msg.ctl_addr = *(struct sockaddr *)&ctl_addr;
+#endif
+ /* must be initiating a talk */
+ if (!look_for_invite(rp))
+ return (0);
+ /*
+ * There was an invitation waiting for us,
+ * so connect with the other (hopefully waiting) party
+ */
+ current_state = "Waiting to connect with caller";
+ do {
+ if (rp->addr.sa_family != AF_INET)
+ p_error("Response uses invalid network address");
+
+ (void)memcpy(&addr, &rp->addr.sa_family, sizeof(addr));
+ addr.sa_family = rp->addr.sa_family;
+ addr.sa_len = sizeof(addr);
+
+ errno = 0;
+ if (connect(sockt, &addr, sizeof(addr)) != -1)
+ return (1);
+ } while (errno == EINTR);
+ if (errno == ECONNREFUSED) {
+ /*
+ * The caller gave up, but his invitation somehow
+ * was not cleared. Clear it and initiate an
+ * invitation. (We know there are no newer invitations,
+ * the talkd works LIFO.)
+ */
+ ctl_transact(his_machine_addr, msg, DELETE, rp);
+ close(sockt);
+ open_sockt();
+ return (0);
+ }
+ p_error("Unable to connect with initiator");
+ /*NOTREACHED*/
+ return (0);
+}
+
+/*
+ * Look for an invitation on 'machine'
+ */
+int
+look_for_invite(CTL_RESPONSE *rp)
+{
+ current_state = "Checking for invitation on caller's machine";
+ ctl_transact(his_machine_addr, msg, LOOK_UP, rp);
+ /* the switch is for later options, such as multiple invitations */
+ switch (rp->answer) {
+
+ case SUCCESS:
+ msg.id_num = htonl(rp->id_num);
+ return (1);
+
+ default:
+ /* there wasn't an invitation waiting for us */
+ return (0);
+ }
+}
diff --git a/usr.bin/talk/msgs.c b/usr.bin/talk/msgs.c
new file mode 100644
index 0000000..bb84843
--- /dev/null
+++ b/usr.bin/talk/msgs.c
@@ -0,0 +1,82 @@
+/* $NetBSD: msgs.c,v 1.7 2011/09/06 18:32:03 joerg Exp $ */
+
+/*
+ * Copyright (c) 1983, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+#ifndef lint
+#if 0
+static char sccsid[] = "@(#)msgs.c 8.1 (Berkeley) 6/6/93";
+#endif
+__RCSID("$NetBSD: msgs.c,v 1.7 2011/09/06 18:32:03 joerg Exp $");
+#endif /* not lint */
+
+/*
+ * A package to display what is happening every MSG_INTERVAL seconds
+ * if we are slow connecting.
+ */
+
+#include <sys/time.h>
+#include <signal.h>
+#include <stdio.h>
+#include "talk.h"
+
+#define MSG_INTERVAL 4
+
+const char *current_state;
+int current_line = 0;
+
+void
+disp_msg(int dummy)
+{
+ message(current_state);
+}
+
+void
+start_msgs(void)
+{
+ struct itimerval itimer;
+
+ message(current_state);
+ signal(SIGALRM, disp_msg);
+ itimer.it_value.tv_sec = itimer.it_interval.tv_sec = MSG_INTERVAL;
+ itimer.it_value.tv_usec = itimer.it_interval.tv_usec = 0;
+ setitimer(ITIMER_REAL, &itimer, (struct itimerval *)0);
+}
+
+void
+end_msgs(void)
+{
+ struct itimerval itimer;
+
+ timerclear(&itimer.it_value);
+ timerclear(&itimer.it_interval);
+ setitimer(ITIMER_REAL, &itimer, (struct itimerval *)0);
+ signal(SIGALRM, SIG_DFL);
+}
diff --git a/usr.bin/talk/talk.1 b/usr.bin/talk/talk.1
new file mode 100644
index 0000000..20d3ee2
--- /dev/null
+++ b/usr.bin/talk/talk.1
@@ -0,0 +1,143 @@
+.\" $NetBSD: talk.1,v 1.9 2007/01/08 17:10:59 christos Exp $
+.\"
+.\" Copyright (c) 1983, 1990, 1993
+.\" The Regents of the University of California. All rights reserved.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\" notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must reproduce the above copyright
+.\" notice, this list of conditions and the following disclaimer in the
+.\" documentation and/or other materials provided with the distribution.
+.\" 3. Neither the name of the University nor the names of its contributors
+.\" may be used to endorse or promote products derived from this software
+.\" without specific prior written permission.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+.\" ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+.\" SUCH DAMAGE.
+.\"
+.\" @(#)talk.1 8.1 (Berkeley) 6/6/93
+.\"
+.Dd January 7, 2007
+.Dt TALK 1
+.Os
+.Sh NAME
+.Nm talk
+.Nd talk to another user
+.Sh SYNOPSIS
+.Nm
+.Ar person
+.Op Ar ttyname
+.Sh DESCRIPTION
+.Nm
+is a visual communication program which copies lines from your
+terminal to that of another user.
+.Pp
+Options available:
+.Bl -tag -width ttyname
+.It Ar person
+If you wish to talk to someone on your own machine, then
+.Ar person
+is just the person's login name.
+If you wish to talk to a user on another host, then
+.Ar person
+is of the form
+.Ql user@host .
+.It Ar ttyname
+If you wish to talk to a user who is logged in more than once, the
+.Ar ttyname
+argument may be used to indicate the appropriate terminal
+name, where
+.Ar ttyname
+is of the form
+.Ql ttyXX .
+.El
+.Pp
+When first called,
+.Nm
+sends the message
+.Bd -literal -offset indent -compact
+Message from TalkDaemon@his_machine...
+talk: connection requested by your_name@your_machine.
+talk: respond with: talk your_name@your_machine
+.Ed
+.Pp
+to the user you wish to talk to.
+At this point, the recipient
+of the message should reply by typing
+.Pp
+.Dl talk \ your_name@your_machine
+.Pp
+It doesn't matter from which machine the recipient replies, as
+long as his login-name is the same.
+Once communication is established,
+the two parties may type simultaneously, with their output appearing
+in separate windows.
+Typing control-L
+.Ql ^L
+will cause the screen to
+be reprinted, while your erase, kill, and word kill characters will
+behave normally.
+To exit, just type your interrupt character;
+.Nm
+then moves the cursor to the bottom of the screen and restores the
+terminal to its previous state.
+.Pp
+Permission to talk may be denied or granted by use of the
+.Xr mesg 1
+command.
+At the outset talking is allowed.
+Certain commands, in
+particular
+.Xr nroff 1
+and
+.Xr pr 1 ,
+disallow messages in order to
+prevent messy output.
+.Sh ENVIRONMENT
+If the
+.Ev TALKHOST
+environment variable is set, its value is used as the
+.Ar hostname
+the
+.Nm
+packets appear to be originating from.
+This is useful if you wish to talk to someone on another machine and
+your internal hostname does not resolve to the address of your
+external interface as seen from the other machine.
+.Sh FILES
+.Bl -tag -width /var/run/utmp -compact
+.It Pa /etc/hosts
+to find the recipient's machine
+.It Pa /var/run/utmp
+to find the recipient's tty
+.El
+.Sh SEE ALSO
+.Xr mail 1 ,
+.Xr mesg 1 ,
+.Xr who 1 ,
+.Xr write 1
+.Sh HISTORY
+The
+.Nm
+command appeared in
+.Bx 4.2 .
+.Sh BUGS
+The version of
+.Nm
+released with
+.Bx 4.3
+uses a protocol that
+is incompatible with the protocol used in the version released with
+.Bx 4.2 .
diff --git a/usr.bin/talk/talk.c b/usr.bin/talk/talk.c
new file mode 100644
index 0000000..2d71253
--- /dev/null
+++ b/usr.bin/talk/talk.c
@@ -0,0 +1,79 @@
+/* $NetBSD: talk.c,v 1.8 2011/09/06 18:32:03 joerg Exp $ */
+
+/*
+ * Copyright (c) 1983, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+#ifndef lint
+__COPYRIGHT("@(#) Copyright (c) 1983, 1993\
+ The Regents of the University of California. All rights reserved.");
+#endif /* not lint */
+
+#ifndef lint
+#if 0
+static char sccsid[] = "@(#)talk.c 8.1 (Berkeley) 6/6/93";
+#endif
+__RCSID("$NetBSD: talk.c,v 1.8 2011/09/06 18:32:03 joerg Exp $");
+#endif /* not lint */
+
+#include "talk.h"
+#include <locale.h>
+
+/*
+ * talk: A visual form of write. Using sockets, a two way
+ * connection is set up between the two people talking.
+ * With the aid of curses, the screen is split into two
+ * windows, and each users text is added to the window,
+ * one character at a time...
+ *
+ * Written by Kipp Hickman
+ *
+ * Modified to run under 4.1a by Clem Cole and Peter Moore
+ * Modified to run between hosts by Peter Moore, 8/19/82
+ * Modified to run under 4.1c by Peter Moore 3/17/83
+ */
+
+int
+main(int argc, char *argv[])
+{
+ setlocale(LC_ALL, "");
+
+ get_names(argc, argv);
+ init_display();
+ open_ctl();
+ open_sockt();
+ start_msgs();
+ if (!check_local())
+ invite_remote();
+ end_msgs();
+ set_edit_chars();
+ talk();
+ /* NOTREACHED */
+ return (0);
+}
diff --git a/usr.bin/talk/talk.h b/usr.bin/talk/talk.h
new file mode 100644
index 0000000..77cfac7
--- /dev/null
+++ b/usr.bin/talk/talk.h
@@ -0,0 +1,89 @@
+/* $NetBSD: talk.h,v 1.9 2011/09/06 18:32:03 joerg Exp $ */
+
+/*
+ * Copyright (c) 1983, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * @(#)talk.h 8.1 (Berkeley) 6/6/93
+ */
+
+#include <sys/param.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <protocols/talkd.h>
+#include <curses.h>
+#include <string.h>
+
+extern int sockt;
+extern int curses_initialized;
+extern int invitation_waiting;
+
+extern const char *current_state;
+extern int current_line;
+
+typedef struct xwin {
+ WINDOW *x_win;
+ int x_nlines;
+ int x_ncols;
+ int x_line;
+ int x_col;
+ char kill;
+ char cerase;
+ char werase;
+} xwin_t;
+
+extern xwin_t my_win;
+extern xwin_t his_win;
+extern WINDOW *line_win;
+
+void announce_invite(void);
+int check_local(void);
+void ctl_transact(struct in_addr, CTL_MSG, int, CTL_RESPONSE *);
+void display(xwin_t *, char *, int);
+void disp_msg(int);
+void end_msgs(void);
+void get_addrs(const char *, const char *);
+void get_names(int, char **);
+void init_display(void);
+void invite_remote(void);
+int look_for_invite(CTL_RESPONSE *);
+int max(int, int);
+void message(const char *);
+void open_ctl(void);
+void open_sockt(void);
+void print_addr(struct sockaddr_in);
+void p_error(const char *) __dead;
+void quit(void) __dead;
+int readwin(WINDOW *, int, int);
+void re_invite(int) __dead;
+void send_delete(void);
+void set_edit_chars(void);
+void sig_sent(int) __dead;
+void start_msgs(void);
+void talk(void) __dead;
+void xscroll(xwin_t *, int);
diff --git a/usr.bin/talk/talk_ctl.h b/usr.bin/talk/talk_ctl.h
new file mode 100644
index 0000000..e4bab52
--- /dev/null
+++ b/usr.bin/talk/talk_ctl.h
@@ -0,0 +1,41 @@
+/* $NetBSD: talk_ctl.h,v 1.4 2003/08/07 11:16:05 agc Exp $ */
+
+/*
+ * Copyright (c) 1983, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * @(#)talk_ctl.h 8.1 (Berkeley) 6/6/93
+ */
+
+extern struct sockaddr_in daemon_addr;
+extern struct sockaddr_in ctl_addr;
+extern struct sockaddr_in my_addr;
+extern struct in_addr my_machine_addr;
+extern struct in_addr his_machine_addr;
+extern u_short daemon_port;
+extern int ctl_sockt;
+extern CTL_MSG msg;
diff --git a/usr.bin/tee/tee.1 b/usr.bin/tee/tee.1
new file mode 100644
index 0000000..5ba9565
--- /dev/null
+++ b/usr.bin/tee/tee.1
@@ -0,0 +1,84 @@
+.\" $NetBSD: tee.1,v 1.9 2017/07/04 07:05:16 wiz Exp $
+.\"
+.\" Copyright (c) 1991, 1993
+.\" The Regents of the University of California. All rights reserved.
+.\"
+.\" This code is derived from software contributed to Berkeley by
+.\" the Institute of Electrical and Electronics Engineers, Inc.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\" notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must reproduce the above copyright
+.\" notice, this list of conditions and the following disclaimer in the
+.\" documentation and/or other materials provided with the distribution.
+.\" 3. Neither the name of the University nor the names of its contributors
+.\" may be used to endorse or promote products derived from this software
+.\" without specific prior written permission.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+.\" ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+.\" SUCH DAMAGE.
+.\"
+.\" @(#)tee.1 8.1 (Berkeley) 6/6/93
+.\"
+.Dd June 6, 1993
+.Dt TEE 1
+.Os
+.Sh NAME
+.Nm tee
+.Nd pipe fitting
+.Sh SYNOPSIS
+.Nm
+.Op Fl ai
+.Op Ar file ...
+.Sh DESCRIPTION
+The
+.Nm
+utility copies standard input to standard output,
+making a copy in zero or more files.
+The output is unbuffered.
+.Pp
+The following options are available:
+.Bl -tag -width Ds
+.It Fl a
+Append the output to the files rather than
+overwriting them.
+.It Fl i
+Ignore the
+.Dv SIGINT
+signal.
+.El
+.Pp
+The following operands are available:
+.Bl -tag -width file
+.It file
+A pathname of an output
+.Ar file .
+.El
+.Pp
+The
+.Nm
+utility takes the default action for all signals,
+except in the event of the
+.Fl i
+option.
+.Sh EXIT STATUS
+.Ex -std tee
+.Sh STANDARDS
+The
+.Nm
+function is expected to be
+.Tn POSIX
+.St -p1003.2
+compatible.
diff --git a/usr.bin/tee/tee.c b/usr.bin/tee/tee.c
new file mode 100644
index 0000000..f534e7e
--- /dev/null
+++ b/usr.bin/tee/tee.c
@@ -0,0 +1,149 @@
+/* $NetBSD: tee.c,v 1.12 2016/09/05 00:40:30 sevan Exp $ */
+
+/*
+ * Copyright (c) 1988, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+#ifndef lint
+__COPYRIGHT("@(#) Copyright (c) 1988, 1993\
+ The Regents of the University of California. All rights reserved.");
+#endif /* not lint */
+
+#ifndef lint
+#if 0
+static char sccsid[] = "@(#)tee.c 8.1 (Berkeley) 6/6/93";
+#endif
+__RCSID("$NetBSD: tee.c,v 1.12 2016/09/05 00:40:30 sevan Exp $");
+#endif
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <signal.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <locale.h>
+#include <err.h>
+
+typedef struct _list {
+ struct _list *next;
+ int fd;
+ const char *name;
+} LIST;
+LIST *head;
+
+void add(int, const char *);
+
+int
+main(int argc, char *argv[])
+{
+ LIST *p;
+ ssize_t rval;
+ int fd;
+ int append, ch, exitval;
+ char *buf;
+#define BSIZE (8 * 1024)
+
+ setlocale(LC_ALL, "");
+
+ append = 0;
+ while ((ch = getopt(argc, argv, "ai")) != -1)
+ switch((char)ch) {
+ case 'a':
+ append = 1;
+ break;
+ case 'i':
+ (void)signal(SIGINT, SIG_IGN);
+ break;
+ case '?':
+ default:
+ (void)fprintf(stderr, "usage: tee [-ai] [file ...]\n");
+ exit(1);
+ }
+ argv += optind;
+ argc -= optind;
+
+ if ((buf = malloc(BSIZE)) == NULL)
+ err(1, "malloc");
+
+ add(STDOUT_FILENO, "stdout");
+
+ for (exitval = 0; *argv; ++argv)
+ if ((fd = open(*argv, append ? O_WRONLY|O_CREAT|O_APPEND :
+ O_WRONLY|O_CREAT|O_TRUNC, DEFFILEMODE)) < 0) {
+ warn("%s", *argv);
+ exitval = 1;
+ } else
+ add(fd, *argv);
+
+ while ((rval = read(STDIN_FILENO, buf, BSIZE)) > 0)
+ for (p = head; p; p = p->next) {
+ const char *bp = buf;
+ size_t n = rval;
+ ssize_t wval;
+
+ do {
+ if ((wval = write(p->fd, bp, n)) == -1) {
+ warn("%s", p->name);
+ exitval = 1;
+ break;
+ }
+ bp += wval;
+ } while (n -= wval);
+ }
+ if (rval < 0) {
+ warn("read");
+ exitval = 1;
+ }
+
+ for (p = head; p; p = p->next) {
+ if (close(p->fd) == -1) {
+ warn("%s", p->name);
+ exitval = 1;
+ }
+ }
+
+ exit(exitval);
+}
+
+void
+add(int fd, const char *name)
+{
+ LIST *p;
+
+ if ((p = malloc(sizeof(LIST))) == NULL)
+ err(1, "malloc");
+ p->fd = fd;
+ p->name = name;
+ p->next = head;
+ head = p;
+}
diff --git a/usr.bin/time/ext.h b/usr.bin/time/ext.h
new file mode 100644
index 0000000..6626a29
--- /dev/null
+++ b/usr.bin/time/ext.h
@@ -0,0 +1,5 @@
+/* $NetBSD: ext.h,v 1.3 2017/07/15 14:34:08 christos Exp $ */
+
+/* borrowed from ../../bin/csh/extern.h */
+void prusage1(FILE *, const char *fmt, struct rusage *, struct rusage *,
+ struct timespec *, struct timespec *);
diff --git a/usr.bin/time/time.1 b/usr.bin/time/time.1
new file mode 100644
index 0000000..3f42310
--- /dev/null
+++ b/usr.bin/time/time.1
@@ -0,0 +1,220 @@
+.\" $NetBSD: time.1,v 1.28 2017/07/15 14:40:36 wiz Exp $
+.\"
+.\" Copyright (c) 1980, 1991, 1993
+.\" The Regents of the University of California. All rights reserved.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\" notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must reproduce the above copyright
+.\" notice, this list of conditions and the following disclaimer in the
+.\" documentation and/or other materials provided with the distribution.
+.\" 3. Neither the name of the University nor the names of its contributors
+.\" may be used to endorse or promote products derived from this software
+.\" without specific prior written permission.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+.\" ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+.\" SUCH DAMAGE.
+.\"
+.\" @(#)time.1 8.1 (Berkeley) 6/6/93
+.\"
+.Dd July 15, 2017
+.Dt TIME 1
+.Os
+.Sh NAME
+.Nm time
+.Nd time command execution
+.Sh SYNOPSIS
+.Nm
+.Op Fl clp
+.Op Fl f Ar fmt
+.Ar command
+.Op Ar argument ...
+.Sh DESCRIPTION
+The
+.Nm
+utility
+executes and
+times
+.Ar command .
+After the command finishes,
+.Nm
+writes the total elapsed time (wall clock time),
+.Pq Dq real ,
+the CPU time spent executing
+.Ar command
+at user level
+.Pq Dq user ,
+and the CPU time spent executing in the operating system kernel
+.Pq Dq sys ,
+to the standard error stream.
+Times are reported in seconds.
+.Pp
+Available options:
+.Bl -tag -width Ds
+.It Fl c
+Displays information in the format used by default the
+.Nm
+builtin of
+.Xr csh 1
+uses (%Uu %Ss %E %P %X+%Dk %I+%Oio %Fpf+%Ww).
+.It Fl f
+Specify a time format using the
+.Xr csh 1
+.Nm
+builtin syntax.
+The following sequences may be used in the format string:
+.Pp
+.Bl -tag -width 4n -compact
+.It \&%U
+The time the process spent in user mode in cpu seconds.
+.It \&%S
+The time the process spent in kernel mode in cpu seconds.
+.It \&%E
+The elapsed (wall clock) time in seconds.
+.It \&%P
+The CPU percentage computed as (%U + %S) / %E.
+.It \&%W
+Number of times the process was swapped.
+.It \&%X
+The average amount in (shared) text space used in Kbytes.
+.It \&%D
+The average amount in (unshared) data/stack space used in Kbytes.
+.It \&%K
+The total space used (%X + %D) in Kbytes.
+.It \&%M
+The maximum memory the process had in use at any time in Kbytes.
+.It \&%F
+The number of major page faults (page needed to be brought from disk).
+.It \&%R
+The number of minor page faults.
+.It \&%I
+The number of input operations.
+.It \&%O
+The number of output operations.
+.It \&%r
+The number of socket messages received.
+.It \&%s
+The number of socket messages sent.
+.It \&%k
+The number of signals received.
+.It \&%w
+The number of voluntary context switches (waits).
+.It \&%c
+The number of involuntary context switches.
+.El
+.It Fl l
+Lists resource utilization information.
+The contents of the
+.Ar command
+process's
+.Em rusage
+structure are printed; see below.
+.It Fl p
+The output is formatted as specified by
+.St -p1003.2-92 .
+.El
+.Pp
+Some shells, such as
+.Xr csh 1
+and
+.Xr ksh 1 ,
+have their own and syntactically different built-in version of
+.Nm .
+The utility described here
+is available as
+.Pa /usr/bin/time
+to users of these shells.
+.Ss Resource Utilization
+If the
+.Fl l
+option is given, the following resource usage
+information is displayed
+in addition to the timing information:
+.Bl -item -offset indent -compact
+.It
+maximum resident set size
+.It
+average shared memory size
+.It
+average unshared data size
+.It
+average unshared stack size
+.It
+page reclaims
+.It
+page faults
+.It
+swaps
+.It
+block input operations
+.It
+block output operations
+.It
+messages sent
+.It
+messages received
+.It
+signals received
+.It
+voluntary context switches
+.It
+involuntary context switches
+.El
+Resource usage is the total for the execution of
+.Ar command
+and any child processes it spawns, as per
+.Xr wait4 2 .
+.Sh FILES
+.Bl -tag -width Xsys/resource.hX -compact
+.It Aq sys/resource.h
+.El
+.Sh EXIT STATUS
+The
+.Nm
+utility exits with one of the following values:
+.Bl -tag -width indent
+.It 1-125
+An error occurred in the
+.Nm
+utility.
+.It 126
+The
+.Ar command
+was found but could not be invoked.
+.It 127
+The
+.Ar command
+could not be found.
+.El
+.Pp
+Otherwise, the exit status of
+.Nm
+will be that of
+.Ar command .
+.Sh SEE ALSO
+.Xr csh 1 ,
+.Xr ksh 1 ,
+.Xr clock_gettime 2 ,
+.Xr getrusage 2 ,
+.Xr wait4 2
+.Sh STANDARDS
+The
+.Nm
+utility conforms to
+.St -p1003.2-92 .
+.Sh BUGS
+The granularity of seconds on microprocessors is crude and
+can result in times being reported for CPU usage which are too large by
+a second.
diff --git a/usr.bin/time/time.c b/usr.bin/time/time.c
new file mode 100644
index 0000000..263db9a
--- /dev/null
+++ b/usr.bin/time/time.c
@@ -0,0 +1,215 @@
+/* $NetBSD: time.c,v 1.23 2017/07/15 14:34:08 christos Exp $ */
+
+/*
+ * Copyright (c) 1987, 1988, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+#ifndef lint
+__COPYRIGHT("@(#) Copyright (c) 1987, 1988, 1993\
+ The Regents of the University of California. All rights reserved.");
+#endif /* not lint */
+
+#ifndef lint
+#if 0
+static char sccsid[] = "@(#)time.c 8.1 (Berkeley) 6/6/93";
+#endif
+__RCSID("$NetBSD: time.c,v 1.23 2017/07/15 14:34:08 christos Exp $");
+#endif /* not lint */
+
+#include <sys/types.h>
+#include <sys/time.h>
+#include <sys/resource.h>
+#include <sys/wait.h>
+#include <errno.h>
+#include <err.h>
+#include <locale.h>
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+#include "ext.h"
+
+__dead static void usage(void);
+static void prl(long, const char *);
+static void prts(const char *, const char *, const struct timespec *,
+ const char *);
+static void prtv(const char *, const char *, const struct timeval *,
+ const char *);
+
+int
+main(int argc, char ** volatile argv)
+{
+ int pid;
+ int ch, status;
+ int volatile portableflag;
+ int volatile lflag;
+ const char *decpt;
+ const char *fmt;
+ const struct lconv *lconv;
+ struct timespec before, after;
+ struct rusage ru;
+
+ (void)setlocale(LC_ALL, "");
+
+ lflag = portableflag = 0;
+ fmt = NULL;
+ while ((ch = getopt(argc, argv, "cf:lp")) != -1) {
+ switch (ch) {
+ case 'f':
+ fmt = optarg;
+ portableflag = 0;
+ lflag = 0;
+ break;
+ case 'c':
+ fmt = "%Uu %Ss %E %P %X+%Dk %I+%Oio %Fpf+%Ww";
+ portableflag = 0;
+ lflag = 0;
+ break;
+ case 'p':
+ portableflag = 1;
+ fmt = NULL;
+ lflag = 0;
+ break;
+ case 'l':
+ lflag = 1;
+ portableflag = 0;
+ fmt = NULL;
+ break;
+ case '?':
+ default:
+ usage();
+ }
+ }
+ argc -= optind;
+ argv += optind;
+
+ if (argc < 1)
+ usage();
+
+ (void)clock_gettime(CLOCK_MONOTONIC, &before);
+ switch(pid = vfork()) {
+ case -1: /* error */
+ err(EXIT_FAILURE, "Vfork failed");
+ /* NOTREACHED */
+ case 0: /* child */
+ /* LINTED will return only on failure */
+ execvp(*argv, argv);
+ err((errno == ENOENT) ? 127 : 126, "Can't exec `%s'", *argv);
+ /* NOTREACHED */
+ }
+
+ /* parent */
+ (void)signal(SIGINT, SIG_IGN);
+ (void)signal(SIGQUIT, SIG_IGN);
+ if ((pid = wait4(pid, &status, 0, &ru)) == -1)
+ err(EXIT_FAILURE, "wait4 %d failed", pid);
+ (void)clock_gettime(CLOCK_MONOTONIC, &after);
+ if (!WIFEXITED(status))
+ warnx("Command terminated abnormally.");
+ timespecsub(&after, &before, &after);
+
+ if ((lconv = localeconv()) == NULL ||
+ (decpt = lconv->decimal_point) == NULL)
+ decpt = ".";
+
+ if (fmt) {
+ static struct rusage null_ru;
+ before.tv_sec = 0;
+ before.tv_nsec = 0;
+ prusage1(stderr, fmt, &null_ru, &ru, &after, &before);
+ } else if (portableflag) {
+ prts("real ", decpt, &after, "\n");
+ prtv("user ", decpt, &ru.ru_utime, "\n");
+ prtv("sys ", decpt, &ru.ru_stime, "\n");
+ } else {
+ prts("", decpt, &after, " real ");
+ prtv("", decpt, &ru.ru_utime, " user ");
+ prtv("", decpt, &ru.ru_stime, " sys\n");
+ }
+
+ if (lflag) {
+ int hz = (int)sysconf(_SC_CLK_TCK);
+ unsigned long long ticks;
+#define SCALE(x) (long)(ticks ? x / ticks : 0)
+
+ ticks = hz * (ru.ru_utime.tv_sec + ru.ru_stime.tv_sec) +
+ hz * (ru.ru_utime.tv_usec + ru.ru_stime.tv_usec) / 1000000;
+ prl(ru.ru_maxrss, "maximum resident set size");
+ prl(SCALE(ru.ru_ixrss), "average shared memory size");
+ prl(SCALE(ru.ru_idrss), "average unshared data size");
+ prl(SCALE(ru.ru_isrss), "average unshared stack size");
+ prl(ru.ru_minflt, "page reclaims");
+ prl(ru.ru_majflt, "page faults");
+ prl(ru.ru_nswap, "swaps");
+ prl(ru.ru_inblock, "block input operations");
+ prl(ru.ru_oublock, "block output operations");
+ prl(ru.ru_msgsnd, "messages sent");
+ prl(ru.ru_msgrcv, "messages received");
+ prl(ru.ru_nsignals, "signals received");
+ prl(ru.ru_nvcsw, "voluntary context switches");
+ prl(ru.ru_nivcsw, "involuntary context switches");
+ }
+
+ return (WIFEXITED(status) ? WEXITSTATUS(status) : EXIT_FAILURE);
+}
+
+static void
+usage(void)
+{
+
+ (void)fprintf(stderr, "Usage: %s [-clp] [-f <fmt>] utility [argument ...]\n",
+ getprogname());
+ exit(EXIT_FAILURE);
+}
+
+static void
+prl(long val, const char *expn)
+{
+
+ (void)fprintf(stderr, "%10ld %s\n", val, expn);
+}
+
+static void
+prts(const char *pre, const char *decpt, const struct timespec *ts,
+ const char *post)
+{
+
+ (void)fprintf(stderr, "%s%9lld%s%02ld%s", pre, (long long)ts->tv_sec,
+ decpt, (long)ts->tv_nsec / 10000000, post);
+}
+
+static void
+prtv(const char *pre, const char *decpt, const struct timeval *tv,
+ const char *post)
+{
+
+ (void)fprintf(stderr, "%s%9lld%s%02ld%s", pre, (long long)tv->tv_sec,
+ decpt, (long)tv->tv_usec / 10000, post);
+}
diff --git a/usr.bin/time/xtime.c b/usr.bin/time/xtime.c
new file mode 100644
index 0000000..b685d2d
--- /dev/null
+++ b/usr.bin/time/xtime.c
@@ -0,0 +1,9 @@
+/* $NetBSD: xtime.c,v 1.1 2007/02/24 21:30:27 matt Exp $ */
+
+
+
+#include <stdio.h>
+#include <sys/time.h>
+#include <sys/resource.h>
+#include "ext.h"
+#include "csh/time.c"
diff --git a/usr.bin/touch/touch.1 b/usr.bin/touch/touch.1
new file mode 100644
index 0000000..d1d1fb4
--- /dev/null
+++ b/usr.bin/touch/touch.1
@@ -0,0 +1,215 @@
+.\" $NetBSD: touch.1,v 1.26 2016/12/24 15:49:18 abhinav Exp $
+.\"
+.\" Copyright (c) 1991, 1993
+.\" The Regents of the University of California. All rights reserved.
+.\"
+.\" This code is derived from software contributed to Berkeley by
+.\" the Institute of Electrical and Electronics Engineers, Inc.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\" notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must reproduce the above copyright
+.\" notice, this list of conditions and the following disclaimer in the
+.\" documentation and/or other materials provided with the distribution.
+.\" 3. Neither the name of the University nor the names of its contributors
+.\" may be used to endorse or promote products derived from this software
+.\" without specific prior written permission.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+.\" ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+.\" SUCH DAMAGE.
+.\"
+.\" @(#)touch.1 8.3 (Berkeley) 4/28/95
+.\"
+.Dd December 24, 2016
+.Dt TOUCH 1
+.Os
+.Sh NAME
+.Nm touch
+.Nd change file access and modification times
+.Sh SYNOPSIS
+.Nm
+.Op Fl acfhm
+.Op Fl d Ar human-datetime
+.Op Fl Fl date Ar human-datetime
+.Op Fl r Ar file
+.Op Fl Fl reference Ar file
+.Op Fl t Ar datetime
+.Ar file ...
+.Sh DESCRIPTION
+The
+.Nm
+utility changes the access and modification times of files to the
+current time of day.
+If the file doesn't exist, it is created with default permissions.
+.Pp
+The following options are available:
+.Bl -tag -width "-d human-datetime"
+.It Fl a
+Change the access time of the file.
+The modification time of the file is not changed unless the
+.Fl m
+flag is also specified.
+.It Fl c
+Do not create the file if it does not exist.
+The
+.Nm
+utility does not treat this as an error.
+No error messages are displayed and the exit value is not affected.
+.It Fl d Ar human-datetime
+.It Fl Fl date Ar human-datetime
+Parse
+.Ar human-datetime
+using the human datetime parser
+.Xr parsedate 3 .
+.It Fl f
+This flag has no effect; it is accepted for compatibility reasons.
+.It Fl h
+If
+.Ar file
+is a symbolic link, access and/or modification time of the link is changed.
+This option implies
+.Fl c .
+.It Fl m
+Change the modification time of the file.
+The access time of the file is not changed unless the
+.Fl a
+flag is also specified.
+.It Fl r Ar file
+.It Fl Fl reference Ar file
+Use the access and modifications times from
+.Ar file
+instead of the current time of day.
+.It Fl t Ar datetime
+Change the access and modification times to the specified time.
+The argument
+.Ar datetime
+should be in the form
+.Dq [[CC]YY]MMDDhhmm[.SS]
+where each pair of letters represents the following:
+.Pp
+.Bl -tag -width Ds -compact -offset indent
+.It Ar CC
+The first two digits of the year (the century).
+.It Ar YY
+The second two digits of the year.
+If
+.Dq YY
+is specified, but
+.Dq CC
+is not, a value for
+.Dq YY
+between 69 and 99 results in a
+.Dq CC
+value of 19.
+Otherwise, a
+.Dq CC
+value of 20 is used.
+.It Ar MM
+The month of the year, from 1 to 12.
+.It Ar DD
+The day of the month, from 1 to 31.
+.It Ar hh
+The hour of the day, from 0 to 23.
+.It Ar mm
+The minute of the hour, from 0 to 59.
+.It Ar SS
+The second of the minute, from 0 to 60 (permitting leap seconds).
+If
+.Ar SS
+is 60 and the resulting time,
+as affected by the
+.Ev TZ
+environment variable,
+does not refer to a leap second,
+the resulting time is one second after a time where
+.Ar SS
+is 59.
+If
+.Ar SS
+is not given a value, it is assumed to be zero.
+.El
+.Pp
+If the
+.Dq CC
+and
+.Dq YY
+letter pairs are not specified, the values default to the current
+year.
+If the
+.Dq SS
+letter pair is not specified, the value defaults to 0.
+.El
+.Pp
+The
+.Fl d ,
+.Fl r ,
+and
+.Fl t
+options are mutually exclusive.
+If more than one of these options is present, the last one is used.
+.Sh ENVIRONMENT
+.Bl -tag -width -iTZ
+.It Ev TZ
+The timezone to be used for interpreting the
+.Ar datetime
+argument of the
+.Fl t
+option.
+.El
+.Sh EXIT STATUS
+.Ex -std
+.Sh COMPATIBILITY
+The obsolescent form of
+.Nm ,
+where a time format is specified as the first argument, is supported.
+When no
+.Fl d ,
+.Fl r ,
+or
+.Fl t
+option is specified, there are at least two arguments, and the first
+argument is a string of digits either eight or ten characters in length,
+the first argument is interpreted as a time specification of the form
+.Dq MMDDhhmm[YY] .
+.Pp
+The
+.Dq MM ,
+.Dq DD ,
+.Dq hh
+and
+.Dq mm
+letter pairs are treated as their counterparts specified to the
+.Fl t
+option.
+If the
+.Dq YY
+letter pair is in the range 69 to 99, the year is set to 1969 to 1999,
+otherwise, the year is set in the 21st century.
+.Sh SEE ALSO
+.Xr utimes 2 ,
+.Xr parsedate 3
+.Sh STANDARDS
+The
+.Nm
+utility is expected to be a superset of the
+.St -p1003.2
+specification.
+.Sh HISTORY
+A
+.Nm
+utility appeared in
+.At v7 .
+.Sh BUGS
+A symbolic link can't be a reference file of access and/or modification time.
diff --git a/usr.bin/touch/touch.c b/usr.bin/touch/touch.c
new file mode 100644
index 0000000..1963815
--- /dev/null
+++ b/usr.bin/touch/touch.c
@@ -0,0 +1,337 @@
+/* $NetBSD: touch.c,v 1.33 2015/03/02 03:17:24 enami Exp $ */
+
+/*
+ * Copyright (c) 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+#ifndef lint
+__COPYRIGHT("@(#) Copyright (c) 1993\
+ The Regents of the University of California. All rights reserved.");
+#endif /* not lint */
+
+#ifndef lint
+#if 0
+static char sccsid[] = "@(#)touch.c 8.2 (Berkeley) 4/28/95";
+#endif
+__RCSID("$NetBSD: touch.c,v 1.33 2015/03/02 03:17:24 enami Exp $");
+#endif /* not lint */
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/time.h>
+
+#include <err.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <locale.h>
+#include <time.h>
+#include <tzfile.h>
+#include <unistd.h>
+#include <util.h>
+#include <getopt.h>
+
+static void stime_arg0(char *, struct timespec *);
+static void stime_arg1(char *, struct timespec *);
+static void stime_arg2(char *, int, struct timespec *);
+static void stime_file(char *, struct timespec *);
+__dead static void usage(void);
+
+struct option touch_longopts[] = {
+ { "date", required_argument, 0,
+ 'd' },
+ { "reference", required_argument, 0,
+ 'r' },
+ { NULL, 0, 0,
+ 0 },
+};
+
+int
+main(int argc, char *argv[])
+{
+ struct stat sb;
+ struct timespec ts[2];
+ int aflag, cflag, hflag, mflag, ch, fd, len, rval, timeset;
+ char *p;
+ int (*change_file_times)(const char *, const struct timespec *);
+ int (*get_file_status)(const char *, struct stat *);
+
+ setlocale(LC_ALL, "");
+
+ aflag = cflag = hflag = mflag = timeset = 0;
+ if (clock_gettime(CLOCK_REALTIME, &ts[0]))
+ err(1, "clock_gettime");
+
+ while ((ch = getopt_long(argc, argv, "acd:fhmr:t:", touch_longopts,
+ NULL)) != -1)
+ switch(ch) {
+ case 'a':
+ aflag = 1;
+ break;
+ case 'c':
+ cflag = 1;
+ break;
+ case 'd':
+ timeset = 1;
+ stime_arg0(optarg, ts);
+ break;
+ case 'f':
+ break;
+ case 'h':
+ hflag = 1;
+ break;
+ case 'm':
+ mflag = 1;
+ break;
+ case 'r':
+ timeset = 1;
+ stime_file(optarg, ts);
+ break;
+ case 't':
+ timeset = 1;
+ stime_arg1(optarg, ts);
+ break;
+ case '?':
+ default:
+ usage();
+ }
+ argc -= optind;
+ argv += optind;
+
+ /* Default is both -a and -m. */
+ if (aflag == 0 && mflag == 0)
+ aflag = mflag = 1;
+
+ if (hflag) {
+ cflag = 1; /* Don't create new file */
+ change_file_times = lutimens;
+ get_file_status = lstat;
+ } else {
+ change_file_times = utimens;
+ get_file_status = stat;
+ }
+
+ /*
+ * If no -r or -t flag, at least two operands, the first of which
+ * is an 8 or 10 digit number, use the obsolete time specification.
+ */
+ if (!timeset && argc > 1) {
+ (void)strtol(argv[0], &p, 10);
+ len = p - argv[0];
+ if (*p == '\0' && (len == 8 || len == 10)) {
+ timeset = 1;
+ stime_arg2(*argv++, len == 10, ts);
+ }
+ }
+
+ /* Otherwise use the current time of day. */
+ if (!timeset)
+ ts[1] = ts[0];
+
+ if (*argv == NULL)
+ usage();
+
+ for (rval = EXIT_SUCCESS; *argv; ++argv) {
+ /* See if the file exists. */
+ if ((*get_file_status)(*argv, &sb)) {
+ if (!cflag) {
+ /* Create the file. */
+ fd = open(*argv,
+ O_WRONLY | O_CREAT, DEFFILEMODE);
+ if (fd == -1 || fstat(fd, &sb) || close(fd)) {
+ rval = EXIT_FAILURE;
+ warn("%s", *argv);
+ continue;
+ }
+
+ /* If using the current time, we're done. */
+ if (!timeset)
+ continue;
+ } else
+ continue;
+ }
+ if (!aflag)
+ ts[0] = sb.st_atimespec;
+ if (!mflag)
+ ts[1] = sb.st_mtimespec;
+
+ /* Try utimes(2). */
+ if (!(*change_file_times)(*argv, ts))
+ continue;
+
+ /* If the user specified a time, nothing else we can do. */
+ if (timeset) {
+ rval = EXIT_FAILURE;
+ warn("%s", *argv);
+ }
+
+ /*
+ * System V and POSIX 1003.1 require that a NULL argument
+ * set the access/modification times to the current time.
+ * The permission checks are different, too, in that the
+ * ability to write the file is sufficient. Take a shot.
+ */
+ if (!(*change_file_times)(*argv, NULL))
+ continue;
+
+ rval = EXIT_FAILURE;
+ warn("%s", *argv);
+ }
+ exit(rval);
+}
+
+#define ATOI2(s) ((s) += 2, ((s)[-2] - '0') * 10 + ((s)[-1] - '0'))
+
+static void
+stime_arg0(char *arg, struct timespec *tsp)
+{
+ tsp[1].tv_sec = tsp[0].tv_sec = parsedate(arg, NULL, NULL);
+ if (tsp[0].tv_sec == -1)
+ errx(EXIT_FAILURE, "Could not parse `%s'", arg);
+ tsp[0].tv_nsec = tsp[1].tv_nsec = 0;
+}
+
+static void
+stime_arg1(char *arg, struct timespec *tsp)
+{
+ struct tm *t;
+ time_t tmptime;
+ int yearset;
+ char *p;
+ /* Start with the current time. */
+ tmptime = tsp[0].tv_sec;
+ if ((t = localtime(&tmptime)) == NULL)
+ err(EXIT_FAILURE, "localtime");
+ /* [[CC]YY]MMDDhhmm[.SS] */
+ if ((p = strchr(arg, '.')) == NULL)
+ t->tm_sec = 0; /* Seconds defaults to 0. */
+ else {
+ if (strlen(p + 1) != 2)
+ goto terr;
+ *p++ = '\0';
+ t->tm_sec = ATOI2(p);
+ }
+
+ yearset = 0;
+ switch (strlen(arg)) {
+ case 12: /* CCYYMMDDhhmm */
+ t->tm_year = ATOI2(arg) * 100 - TM_YEAR_BASE;
+ yearset = 1;
+ /* FALLTHROUGH */
+ case 10: /* YYMMDDhhmm */
+ if (yearset) {
+ t->tm_year += ATOI2(arg);
+ } else {
+ yearset = ATOI2(arg);
+ if (yearset < 69)
+ t->tm_year = yearset + 2000 - TM_YEAR_BASE;
+ else
+ t->tm_year = yearset + 1900 - TM_YEAR_BASE;
+ }
+ /* FALLTHROUGH */
+ case 8: /* MMDDhhmm */
+ t->tm_mon = ATOI2(arg);
+ --t->tm_mon; /* Convert from 01-12 to 00-11 */
+ /* FALLTHROUGH */
+ case 6:
+ t->tm_mday = ATOI2(arg);
+ /* FALLTHROUGH */
+ case 4:
+ t->tm_hour = ATOI2(arg);
+ /* FALLTHROUGH */
+ case 2:
+ t->tm_min = ATOI2(arg);
+ break;
+ default:
+ goto terr;
+ }
+
+ t->tm_isdst = -1; /* Figure out DST. */
+ tsp[0].tv_sec = tsp[1].tv_sec = mktime(t);
+ if (tsp[0].tv_sec == -1)
+terr: errx(EXIT_FAILURE,
+ "out of range or illegal time specification: [[CC]YY]MMDDhhmm[.SS]");
+
+ tsp[0].tv_nsec = tsp[1].tv_nsec = 0;
+}
+
+static void
+stime_arg2(char *arg, int year, struct timespec *tsp)
+{
+ struct tm *t;
+ time_t tmptime;
+ /* Start with the current time. */
+ tmptime = tsp[0].tv_sec;
+ if ((t = localtime(&tmptime)) == NULL)
+ err(EXIT_FAILURE, "localtime");
+
+ t->tm_mon = ATOI2(arg); /* MMDDhhmm[yy] */
+ --t->tm_mon; /* Convert from 01-12 to 00-11 */
+ t->tm_mday = ATOI2(arg);
+ t->tm_hour = ATOI2(arg);
+ t->tm_min = ATOI2(arg);
+ if (year) {
+ year = ATOI2(arg);
+ if (year < 69)
+ t->tm_year = year + 2000 - TM_YEAR_BASE;
+ else
+ t->tm_year = year + 1900 - TM_YEAR_BASE;
+ }
+ t->tm_sec = 0;
+
+ t->tm_isdst = -1; /* Figure out DST. */
+ tsp[0].tv_sec = tsp[1].tv_sec = mktime(t);
+ if (tsp[0].tv_sec == -1)
+ errx(EXIT_FAILURE,
+ "out of range or illegal time specification: MMDDhhmm[yy]");
+
+ tsp[0].tv_nsec = tsp[1].tv_nsec = 0;
+}
+
+static void
+stime_file(char *fname, struct timespec *tsp)
+{
+ struct stat sb;
+
+ if (stat(fname, &sb))
+ err(1, "%s", fname);
+ tsp[0] = sb.st_atimespec;
+ tsp[1] = sb.st_mtimespec;
+}
+
+static void
+usage(void)
+{
+ (void)fprintf(stderr,
+ "Usage: %s [-acfhm] [-d|--date datetime] [-r|--reference file] [-t time] file ...\n",
+ getprogname());
+ exit(EXIT_FAILURE);
+}
diff --git a/usr.bin/tput/clear.sh b/usr.bin/tput/clear.sh
new file mode 100644
index 0000000..5fffaf0
--- /dev/null
+++ b/usr.bin/tput/clear.sh
@@ -0,0 +1,38 @@
+#!/bin/sh -
+# $NetBSD: clear.sh,v 1.2 1994/12/07 08:49:09 jtc Exp $
+#
+# Copyright (c) 1989, 1993
+# The Regents of the University of California. All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1. Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+# 3. All advertising materials mentioning features or use of this software
+# must display the following acknowledgement:
+# This product includes software developed by the University of
+# California, Berkeley and its contributors.
+# 4. Neither the name of the University nor the names of its contributors
+# may be used to endorse or promote products derived from this software
+# without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+# ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+# SUCH DAMAGE.
+#
+# @(#)clear.sh 8.1 (Berkeley) 6/6/93
+#
+
+exec tput clear
diff --git a/usr.bin/tput/tput.1 b/usr.bin/tput/tput.1
new file mode 100644
index 0000000..1ea0bb4
--- /dev/null
+++ b/usr.bin/tput/tput.1
@@ -0,0 +1,139 @@
+.\" $NetBSD: tput.1,v 1.16 2012/04/21 12:27:30 roy Exp $
+.\"
+.\" Copyright (c) 1989, 1990, 1993
+.\" The Regents of the University of California. All rights reserved.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\" notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must reproduce the above copyright
+.\" notice, this list of conditions and the following disclaimer in the
+.\" documentation and/or other materials provided with the distribution.
+.\" 3. Neither the name of the University nor the names of its contributors
+.\" may be used to endorse or promote products derived from this software
+.\" without specific prior written permission.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+.\" ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+.\" SUCH DAMAGE.
+.\"
+.\" @(#)tput.1 8.2 (Berkeley) 3/19/94
+.\"
+.Dd April 5, 2012
+.Dt TPUT 1
+.Os
+.Sh NAME
+.Nm tput ,
+.Nm clear
+.Nd terminal capability interface
+.Sh SYNOPSIS
+.Nm
+.Op Fl T Ar term
+.Ar attribute
+.Op Ar attribute-args
+.Ar ...
+.Sh DESCRIPTION
+.Nm
+makes terminal-dependent information available to users or shell
+applications.
+The options are as follows:
+.Bl -tag -width Ds
+.It Fl T
+The terminal name as specified in the
+.Xr terminfo 5
+database, for example,
+.Dq vt100
+or
+.Dq xterm .
+If not specified,
+.Nm
+retrieves the
+.Dq Ev TERM
+variable from the environment.
+.El
+.Pp
+.Nm
+outputs a string if the
+.Ar attribute
+is of type string; a number if it is of type integer.
+Otherwise,
+.Nm
+exits 0 if the terminal has the capability and 1 if it does not,
+without further action.
+.Pp
+If the
+.Ar attribute
+is of type string, and takes arguments (e.g. cursor movement,
+the terminfo
+.Dq cup
+sequence) the arguments are taken from the command line immediately
+following the attribute.
+.Pp
+The following special attributes are available:
+.Bl -tag -width Ar
+.It clear
+Clear the screen (the
+.Xr terminfo 5
+.Dq cl
+sequence).
+.It init
+Initialize the terminal (the
+.Xr terminfo 5
+.Dq is2
+sequence).
+.It longname
+Print the descriptive name of the user's terminal type.
+.It reset
+Reset the terminal (the
+.Xr terminfo 5
+.Dq rs1 , rs2 , rs3
+and
+.Dq rf
+sequence).
+.El
+.Sh EXIT STATUS
+The exit status of
+.Nm
+is based on the last attribute specified.
+If the attribute is of type string or of type integer,
+.Nm
+exits 0 if the attribute is defined for this terminal type and 1
+if it is not.
+If the attribute is of type boolean,
+.Nm
+exits 0 if the terminal has this attribute, and 1 if it does not.
+.Nm
+exits 2 if any error occurred.
+.Sh EXAMPLES
+.Bl -tag -width "tput cm 6 11 DC 6" -compact
+.It Li "tput cl cm 5 10"
+clear the screen and goto line 5 column 10
+.Pp
+.It Li "tput cm 6 11 DC 6"
+goto line 6 column 11 and delete 6 characters
+.El
+.Sh SEE ALSO
+.Xr termcap 3 ,
+.Xr terminfo 3 ,
+.Xr terminfo 5
+.Sh HISTORY
+The
+.Nm
+command appears in
+.Bx 4.4 .
+.Sh BUGS
+.Nm
+can't really distinguish between different types of attributes.
+.Pp
+Not all terminfo entries contain the reset sequence, so using the init
+sequence may be more useful.
diff --git a/usr.bin/tput/tput.c b/usr.bin/tput/tput.c
new file mode 100644
index 0000000..0e8da2f
--- /dev/null
+++ b/usr.bin/tput/tput.c
@@ -0,0 +1,195 @@
+/* $NetBSD: tput.c,v 1.26 2013/02/05 11:31:56 roy Exp $ */
+
+/*-
+ * Copyright (c) 1980, 1988, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+#ifndef lint
+__COPYRIGHT("@(#) Copyright (c) 1980, 1988, 1993\
+ The Regents of the University of California. All rights reserved.");
+#endif /* not lint */
+
+#ifndef lint
+#if 0
+static char sccsid[] = "@(#)tput.c 8.3 (Berkeley) 4/28/95";
+#endif
+__RCSID("$NetBSD: tput.c,v 1.26 2013/02/05 11:31:56 roy Exp $");
+#endif /* not lint */
+
+#include <termios.h>
+
+#include <err.h>
+#include <errno.h>
+#include <limits.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <term_private.h>
+#include <term.h>
+#include <unistd.h>
+
+static void usage(void) __dead;
+static char **process(const char *, const char *, char **);
+
+int
+main(int argc, char **argv)
+{
+ int ch, exitval, n;
+ char *term;
+ const char *p, *s;
+ size_t pl;
+
+ term = NULL;
+ while ((ch = getopt(argc, argv, "T:")) != -1)
+ switch(ch) {
+ case 'T':
+ term = optarg;
+ break;
+ case '?':
+ default:
+ usage();
+ }
+ argc -= optind;
+ argv += optind;
+
+ if (!term && !(term = getenv("TERM")))
+ errx(2, "No terminal type specified and no TERM "
+ "variable set in the environment.");
+ setupterm(term, 0, NULL);
+ for (exitval = 0; (p = *argv) != NULL; ++argv) {
+ switch (*p) {
+ case 'c':
+ if (!strcmp(p, "clear"))
+ p = "clear";
+ break;
+ case 'i':
+ if (!strcmp(p, "init")) {
+ s = tigetstr("is1");
+ if (s != NULL)
+ putp(s);
+ p = "is2";
+ }
+ break;
+ case 'l':
+ if (!strcmp(p, "longname")) {
+ (void)printf("%s\n", longname());
+ continue;
+ }
+ break;
+ case 'r':
+ if (!strcmp(p, "reset")) {
+ s = tigetstr("rs1");
+ if (s != NULL)
+ putp(s);
+ p = "rs2";
+ }
+ break;
+ }
+ pl = strlen(p);
+ if (((s = tigetstr(p)) != NULL && s != (char *)-1) ||
+ (pl <= 2 && (s = tgetstr(p, NULL)) != NULL))
+ argv = process(p, s, argv);
+ else if ((((n = tigetnum(p)) != -1 && n != -2 ) ||
+ (pl <= 2 && (n = tgetnum(p)) != -1)))
+ (void)printf("%d\n", n);
+ else {
+ exitval = tigetflag(p);
+ if (exitval == -1) {
+ if (pl <= 2)
+ exitval = !tgetflag(p);
+ else
+ exitval = 1;
+ } else
+ exitval = !exitval;
+ }
+
+ if (argv == NULL)
+ break;
+ }
+ return argv ? exitval : 2;
+}
+
+static char **
+process(const char *cap, const char *str, char **argv)
+{
+ static const char errfew[] =
+ "Not enough arguments (%d) for capability `%s'";
+ static const char erresc[] =
+ "Unknown %% escape (%s) for capability `%s'";
+ static const char errnum[] =
+ "Expected a numeric argument [%d] (%s) for capability `%s'";
+ static const char errcharlong[] =
+ "Platform does not fit a string into a long for capability '%s'";
+ int i, nparams, piss[TPARM_MAX];
+ long nums[TPARM_MAX];
+ char *strs[TPARM_MAX], *tmp;
+
+ /* Count how many values we need for this capability. */
+ errno = 0;
+ memset(&piss, 0, sizeof(piss));
+ nparams = _ti_parm_analyse(str, piss, TPARM_MAX);
+ if (errno == EINVAL)
+ errx(2, erresc, str, cap);
+
+ /* Create our arrays of integers and strings */
+ for (i = 0; i < nparams; i++) {
+ if (*++argv == NULL || *argv[0] == '\0')
+ errx(2, errfew, nparams, cap);
+ if (piss[i]) {
+ if (sizeof(char *) > sizeof(long) /* CONSTCOND */)
+ errx(2, errcharlong, cap);
+ strs[i] = *argv;
+ } else {
+ errno = 0;
+ nums[i] = strtol(*argv, &tmp, 0);
+ if ((errno == ERANGE &&
+ (nums[i] == LONG_MIN || nums[i] == LONG_MAX)) ||
+ (errno != 0 && nums[i] == 0) ||
+ tmp == str ||
+ *tmp != '\0')
+ errx(2, errnum, i + 1, *argv, cap);
+ }
+ }
+
+ /* And output */
+#define p(i) (i <= nparams ? \
+ (piss[i - 1] ? (long)strs[i - 1] : nums[i - 1]) : 0)
+ putp(tparm(str, p(1), p(2), p(3), p(4), p(5), p(6), p(7), p(8), p(9)));
+
+ return argv;
+}
+
+static void
+usage(void)
+{
+ (void)fprintf(stderr,
+ "Usage: %s [-T term] attribute [attribute-args] ...\n",
+ getprogname());
+ exit(2);
+}
diff --git a/usr.bin/tr/extern.h b/usr.bin/tr/extern.h
new file mode 100644
index 0000000..8cb2de9
--- /dev/null
+++ b/usr.bin/tr/extern.h
@@ -0,0 +1,43 @@
+/* $NetBSD: extern.h,v 1.11 2013/08/11 00:39:22 dholland Exp $ */
+
+/*-
+ * Copyright (c) 1991, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * @(#)extern.h 8.1 (Berkeley) 6/6/93
+ */
+
+struct str;
+typedef struct str STR;
+
+#include <limits.h>
+#define NCHARS (UCHAR_MAX + 1) /* Number of possible characters. */
+#define OOBCH (UCHAR_MAX + 1) /* Out of band character value. */
+
+STR *str_create(int, const char *);
+void str_destroy(STR *);
+int next(STR *, int *);
diff --git a/usr.bin/tr/str.c b/usr.bin/tr/str.c
new file mode 100644
index 0000000..a9dccde
--- /dev/null
+++ b/usr.bin/tr/str.c
@@ -0,0 +1,453 @@
+/* $NetBSD: str.c,v 1.30 2018/05/26 11:20:30 leot Exp $ */
+
+/*-
+ * Copyright (c) 1991, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+#ifndef lint
+#if 0
+static char sccsid[] = "@(#)str.c 8.2 (Berkeley) 4/28/95";
+#endif
+__RCSID("$NetBSD: str.c,v 1.30 2018/05/26 11:20:30 leot Exp $");
+#endif /* not lint */
+
+#include <sys/types.h>
+
+#include <err.h>
+#include <errno.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <ctype.h>
+#include <assert.h>
+
+#include "extern.h"
+
+struct str {
+ enum { STRING1, STRING2 } which;
+ enum { EOS, INFINITE, NORMAL, RANGE, SEQUENCE, SET } state;
+ int cnt; /* character count */
+ int lastch; /* last character */
+ int equiv[2]; /* equivalence set */
+ int *set; /* set of characters */
+ const char *str; /* user's string */
+};
+
+static int backslash(STR *);
+static int bracket(STR *);
+static int c_class(const void *, const void *);
+static int *genclass(const char *, size_t);
+static void genequiv(STR *);
+static int genrange(STR *);
+static void genseq(STR *);
+
+STR *
+str_create(int whichstring, const char *txt)
+{
+ STR *s;
+
+ s = malloc(sizeof(*s));
+ if (s == NULL) {
+ err(1, "Out of memory");
+ }
+
+ s->which = whichstring == 2 ? STRING2 : STRING1;
+ s->state = NORMAL;
+ s->cnt = 0;
+ s->lastch = OOBCH;
+ s->equiv[0] = 0;
+ s->equiv[1] = OOBCH;
+ s->set = NULL;
+ s->str = txt;
+
+ return s;
+}
+
+void
+str_destroy(STR *s)
+{
+ if (s->set != NULL && s->set != s->equiv) {
+ free(s->set);
+ }
+ free(s);
+}
+
+int
+next(STR *s, int *ret)
+{
+ int ch;
+
+ switch (s->state) {
+ case EOS:
+ *ret = s->lastch;
+ return 0;
+ case INFINITE:
+ *ret = s->lastch;
+ return 1;
+ case NORMAL:
+ ch = (unsigned char)s->str[0];
+ switch (ch) {
+ case '\0':
+ s->state = EOS;
+ *ret = s->lastch;
+ return 0;
+ case '\\':
+ s->lastch = backslash(s);
+ break;
+ case '[':
+ if (bracket(s)) {
+ return next(s, ret);
+ }
+ /* FALLTHROUGH */
+ default:
+ ++s->str;
+ s->lastch = ch;
+ break;
+ }
+
+ /* We can start a range at any time. */
+ if (s->str[0] == '-' && genrange(s)) {
+ return next(s, ret);
+ }
+ *ret = s->lastch;
+ return 1;
+ case RANGE:
+ if (s->cnt == 0) {
+ s->state = NORMAL;
+ return next(s, ret);
+ }
+ s->cnt--;
+ ++s->lastch;
+ *ret = s->lastch;
+ return 1;
+ case SEQUENCE:
+ if (s->cnt == 0) {
+ s->state = NORMAL;
+ return next(s, ret);
+ }
+ s->cnt--;
+ *ret = s->lastch;
+ return 1;
+ case SET:
+ s->lastch = s->set[s->cnt++];
+ if (s->lastch == OOBCH) {
+ s->state = NORMAL;
+ if (s->set != s->equiv) {
+ free(s->set);
+ }
+ s->set = NULL;
+ return next(s, ret);
+ }
+ *ret = s->lastch;
+ return 1;
+ }
+ /* NOTREACHED */
+ assert(0);
+ *ret = s->lastch;
+ return 0;
+}
+
+static int
+bracket(STR *s)
+{
+ const char *p;
+ int *q;
+
+ switch (s->str[1]) {
+ case ':': /* "[:class:]" */
+ if ((p = strstr(s->str + 2, ":]")) == NULL)
+ return 0;
+ s->str += 2;
+ q = genclass(s->str, p - s->str);
+ s->state = SET;
+ s->set = q;
+ s->cnt = 0;
+ s->str = p + 2;
+ return 1;
+ case '=': /* "[=equiv=]" */
+ if ((p = strstr(s->str + 2, "=]")) == NULL)
+ return 0;
+ s->str += 2;
+ genequiv(s);
+ s->str = p + 2;
+ return 1;
+ default: /* "[\###*n]" or "[#*n]" */
+ if ((p = strpbrk(s->str + 2, "*]")) == NULL)
+ return 0;
+ if (p[0] != '*' || strchr(p, ']') == NULL)
+ return 0;
+ s->str += 1;
+ genseq(s);
+ return 1;
+ }
+ /* NOTREACHED */
+}
+
+typedef struct {
+ const char *name;
+ int (*func)(int);
+} CLASS;
+
+static const CLASS classes[] = {
+ { "alnum", isalnum },
+ { "alpha", isalpha },
+ { "blank", isblank },
+ { "cntrl", iscntrl },
+ { "digit", isdigit },
+ { "graph", isgraph },
+ { "lower", islower },
+ { "print", isprint },
+ { "punct", ispunct },
+ { "space", isspace },
+ { "upper", isupper },
+ { "xdigit", isxdigit },
+};
+
+typedef struct {
+ const char *name;
+ size_t len;
+} CLASSKEY;
+
+static int *
+genclass(const char *class, size_t len)
+{
+ int ch;
+ const CLASS *cp;
+ CLASSKEY key;
+ int *p;
+ unsigned pos, num;
+
+ /* Find the class */
+ key.name = class;
+ key.len = len;
+ cp = bsearch(&key, classes, __arraycount(classes), sizeof(classes[0]),
+ c_class);
+ if (cp == NULL) {
+ errx(1, "unknown class %.*s", (int)len, class);
+ }
+
+ /*
+ * Figure out what characters are in the class
+ */
+
+ num = NCHARS + 1;
+ p = malloc(num * sizeof(*p));
+ if (p == NULL) {
+ err(1, "malloc");
+ }
+
+ pos = 0;
+ for (ch = 0; ch < NCHARS; ch++) {
+ if (cp->func(ch)) {
+ p[pos++] = ch;
+ }
+ }
+
+ p[pos++] = OOBCH;
+ for (; pos < num; pos++) {
+ p[pos] = 0;
+ }
+
+ return p;
+}
+
+static int
+c_class(const void *av, const void *bv)
+{
+ const CLASSKEY *a = av;
+ const CLASS *b = bv;
+ size_t blen;
+ int r;
+
+ blen = strlen(b->name);
+ r = strncmp(a->name, b->name, a->len);
+ if (r != 0) {
+ return r;
+ }
+ if (a->len < blen) {
+ /* someone gave us a prefix of the right name */
+ return -1;
+ }
+ assert(a-> len == blen);
+ return 0;
+}
+
+/*
+ * English doesn't have any equivalence classes, so for now
+ * we just syntax check and grab the character.
+ */
+static void
+genequiv(STR *s)
+{
+ int ch;
+
+ ch = (unsigned char)s->str[0];
+ if (ch == '\\') {
+ s->equiv[0] = backslash(s);
+ } else {
+ s->equiv[0] = ch;
+ s->str++;
+ }
+ if (s->str[0] != '=') {
+ errx(1, "Misplaced equivalence equals sign");
+ }
+ s->str++;
+ if (s->str[0] != ']') {
+ errx(1, "Misplaced equivalence right bracket");
+ }
+ s->str++;
+
+ s->cnt = 0;
+ s->state = SET;
+ s->set = s->equiv;
+}
+
+static int
+genrange(STR *s)
+{
+ int stopval;
+ const char *savestart;
+
+ savestart = s->str++;
+ stopval = s->str[0] == '\\' ? backslash(s) : (unsigned char)*s->str++;
+ if (stopval < (unsigned char)s->lastch) {
+ s->str = savestart;
+ return 0;
+ }
+ s->cnt = stopval - s->lastch + 1;
+ s->state = RANGE;
+ --s->lastch;
+ return 1;
+}
+
+static void
+genseq(STR *s)
+{
+ char *ep;
+
+ if (s->which == STRING1) {
+ errx(1, "Sequences only valid in string2");
+ }
+
+ if (*s->str == '\\') {
+ s->lastch = backslash(s);
+ } else {
+ s->lastch = (unsigned char)*s->str++;
+ }
+ if (*s->str != '*') {
+ errx(1, "Misplaced sequence asterisk");
+ }
+
+ s->str++;
+ switch (s->str[0]) {
+ case '\\':
+ s->cnt = backslash(s);
+ break;
+ case ']':
+ s->cnt = 0;
+ ++s->str;
+ break;
+ default:
+ if (isdigit((unsigned char)s->str[0])) {
+ s->cnt = strtol(s->str, &ep, 0);
+ if (*ep == ']') {
+ s->str = ep + 1;
+ break;
+ }
+ }
+ errx(1, "illegal sequence count");
+ /* NOTREACHED */
+ }
+
+ s->state = s->cnt ? SEQUENCE : INFINITE;
+}
+
+/*
+ * Translate \??? into a character. Up to 3 octal digits, if no digits either
+ * an escape code or a literal character.
+ */
+static int
+backslash(STR *s)
+{
+ int ch, cnt, val;
+
+ cnt = val = 0;
+ for (;;) {
+ /* Consume the character we're already on. */
+ s->str++;
+
+ /* Look at the next character. */
+ ch = (unsigned char)s->str[0];
+ if (!isascii(ch) || !isdigit(ch)) {
+ break;
+ }
+ val = val * 8 + ch - '0';
+ if (++cnt == 3) {
+ /* Enough digits; consume this one and stop */
+ ++s->str;
+ break;
+ }
+ }
+ if (cnt) {
+ /* We saw digits, so return their value */
+ if (val >= OOBCH)
+ errx(1, "Invalid octal character value");
+ return val;
+ }
+ if (ch == '\0') {
+ /* \<end> -> \ */
+ s->state = EOS;
+ return '\\';
+ }
+
+ /* Consume the escaped character */
+ s->str++;
+
+ switch (ch) {
+ case 'a': /* escape characters */
+ return '\7';
+ case 'b':
+ return '\b';
+ case 'e':
+ return '\033';
+ case 'f':
+ return '\f';
+ case 'n':
+ return '\n';
+ case 'r':
+ return '\r';
+ case 't':
+ return '\t';
+ case 'v':
+ return '\13';
+ default: /* \q -> q */
+ return ch;
+ }
+}
diff --git a/usr.bin/tr/tr.1 b/usr.bin/tr/tr.1
new file mode 100644
index 0000000..fa9d121
--- /dev/null
+++ b/usr.bin/tr/tr.1
@@ -0,0 +1,352 @@
+.\" $NetBSD: tr.1,v 1.22 2017/07/03 21:34:22 wiz Exp $
+.\"
+.\" Copyright (c) 1991, 1993
+.\" The Regents of the University of California. All rights reserved.
+.\"
+.\" This code is derived from software contributed to Berkeley by
+.\" the Institute of Electrical and Electronics Engineers, Inc.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\" notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must reproduce the above copyright
+.\" notice, this list of conditions and the following disclaimer in the
+.\" documentation and/or other materials provided with the distribution.
+.\" 3. Neither the name of the University nor the names of its contributors
+.\" may be used to endorse or promote products derived from this software
+.\" without specific prior written permission.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+.\" ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+.\" SUCH DAMAGE.
+.\"
+.\" @(#)tr.1 8.1 (Berkeley) 6/6/93
+.\"
+.Dd May 29, 2013
+.Dt TR 1
+.Os
+.Sh NAME
+.Nm tr
+.Nd translate characters
+.Sh SYNOPSIS
+.Nm
+.Op Fl cs
+.Ar string1 string2
+.Nm
+.Op Fl c
+.Fl d
+.Ar string1
+.Nm
+.Op Fl c
+.Fl s
+.Ar string1
+.Nm
+.Op Fl c
+.Fl ds
+.Ar string1 string2
+.Sh DESCRIPTION
+The
+.Nm
+utility copies the standard input to the standard output with substitution
+or deletion of selected characters.
+.Pp
+The following options are available:
+.Bl -tag -width Ds
+.It Fl c
+Complements the set of characters in
+.Ar string1 ;
+that is,
+.Fl c Ar \&ab
+includes every character except for
+.Sq a
+and
+.Sq b .
+.It Fl d
+The
+.Fl d
+option causes characters to be deleted from the input.
+.It Fl s
+The
+.Fl s
+option squeezes multiple occurrences of the characters listed in the last
+operand (either
+.Ar string1
+or
+.Ar string2 )
+in the input into a single instance of the character.
+This occurs after all deletion and translation is completed.
+.El
+.Pp
+In the first synopsis form, the characters in
+.Ar string1
+are translated into the characters in
+.Ar string2
+where the first character in
+.Ar string1
+is translated into the first character in
+.Ar string2
+and so on.
+If
+.Ar string1
+is longer than
+.Ar string2 ,
+the last character found in
+.Ar string2
+is duplicated until
+.Ar string1
+is exhausted.
+.Pp
+In the second synopsis form, the characters in
+.Ar string1
+are deleted from the input.
+.Pp
+In the third synopsis form, the characters in
+.Ar string1
+are compressed as described for the
+.Fl s
+option.
+.Pp
+In the fourth synopsis form, the characters in
+.Ar string1
+are deleted from the input, and the characters in
+.Ar string2
+are compressed as described for the
+.Fl s
+option.
+.Pp
+The following conventions can be used in
+.Ar string1
+and
+.Ar string2
+to specify sets of characters:
+.Bl -tag -width [:equiv:]
+.It character
+Any character not described by one of the following conventions
+represents itself.
+.It \eoctal
+A backslash followed by 1, 2 or 3 octal digits represents a character
+with that encoded value.
+To follow an octal sequence with a digit as a character, left zero-pad
+the octal sequence to the full 3 octal digits.
+.It \echaracter
+A backslash followed by certain special characters maps to special
+values.
+.sp
+.Bl -column cc
+.It \ea <alert character>
+.It \eb <backspace>
+.It \ef <form-feed>
+.It \en <newline>
+.It \er <carriage return>
+.It \et <tab>
+.It \ev <vertical tab>
+.El
+.sp
+A backslash followed by any other character maps to that character.
+.It c-c
+Represents the range of characters between the range endpoints, inclusively.
+.It [:class:]
+Represents all characters belonging to the defined character class.
+Class names are:
+.sp
+.Bl -column xdigit
+.It alnum <alphanumeric characters>
+.It alpha <alphabetic characters>
+.It blank <blank characters>
+.It cntrl <control characters>
+.It digit <numeric characters>
+.It graph <graphic characters>
+.It lower <lower-case alphabetic characters>
+.It print <printable characters>
+.It punct <punctuation characters>
+.It space <space characters>
+.It upper <upper-case characters>
+.It xdigit <hexadecimal characters>
+.El
+.Pp
+.\" All classes may be used in
+.\" .Ar string1 ,
+.\" and in
+.\" .Ar string2
+.\" when both the
+.\" .Fl d
+.\" and
+.\" .Fl s
+.\" options are specified.
+.\" Otherwise, only the classes ``upper'' and ``lower'' may be used in
+.\" .Ar string2
+.\" and then only when the corresponding class (``upper'' for ``lower''
+.\" and vice-versa) is specified in the same relative position in
+.\" .Ar string1 .
+.\" .Pp
+With the exception of the
+.Dq upper
+and
+.Dq lower
+classes, characters in the classes are in unspecified order.
+In the
+.Dq upper
+and
+.Dq lower
+classes, characters are entered in ascending order.
+.Pp
+For specific information as to which ASCII characters are included
+in these classes, see
+.Xr ctype 3
+and related manual pages.
+.It [=equiv=]
+Represents all characters or collating (sorting) elements belonging to
+the same equivalence class as
+.Ar equiv .
+If there is a secondary ordering within the equivalence class, the
+characters are ordered in ascending sequence.
+Otherwise, they are ordered after their encoded values.
+An example of an equivalence class might be
+.Dq \&c
+and
+.Dq \&ch
+in Spanish;
+English has no equivalence classes.
+.It [#*n]
+Represents
+.Ar n
+repeated occurrences of the character represented by
+.Ar # .
+This
+expression is only valid when it occurs in
+.Ar string2 .
+If
+.Ar n
+is omitted or is zero, it is interpreted as large enough to extend the
+.Ar string2
+sequence to the length of
+.Ar string1 .
+If
+.Ar n
+has a leading zero, it is interpreted as an octal value;
+otherwise, it is interpreted as a decimal value.
+.El
+.Sh EXIT STATUS
+.Ex -std
+.Sh EXAMPLES
+The following examples are shown as given to the shell:
+.Pp
+Create a list of the words in
+.Ar file1 ,
+one per line, where a word is taken to be a maximal string of letters:
+.sp
+.D1 Li "tr -cs \*q[:alpha:]\*q \*q\en\*q < file1"
+.sp
+Translate the contents of
+.Ar file1
+to upper-case:
+.sp
+.D1 Li "tr \*q[:lower:]\*q \*q[:upper:]\*q < file1"
+.sp
+Strip out non-printable characters from
+.Ar file1 :
+.sp
+.D1 Li "tr -cd \*q[:print:]\*q < file1"
+.Sh COMPATIBILITY
+.At V
+has historically implemented character ranges using the syntax
+.Dq [c-c]
+instead of the
+.Dq c-c
+used by historic
+.Bx
+implementations and standardized by POSIX.
+.At V
+shell scripts should work under this implementation as long as
+the range is intended to map in another range, i.e. the command
+.Pp
+.Ic "tr [a-z] [A-Z]"
+.Pp
+will work as it will map the
+.Sq \&[
+character in
+.Ar string1
+to the
+.Sq \&[
+character in
+.Ar string2 .
+However, if the shell script is deleting or squeezing characters as in
+the command
+.Pp
+.Ic "tr -d [a-z]"
+.Pp
+the characters
+.Sq \&[
+and
+.Sq \&]
+will be included in the deletion or compression list which would
+not have happened under an historic
+.At V
+implementation.
+Additionally, any scripts that depended on the sequence
+.Dq a-z
+to represent the three characters
+.Sq \&a ,
+.Sq \&- ,
+and
+.Sq \&z
+will have to be rewritten as
+.Dq a\e-z .
+.Pp
+The
+.Nm
+utility has historically not permitted the manipulation of NUL bytes in
+its input and, additionally, stripped NUL's from its input stream.
+This implementation has removed this behavior as a bug.
+.Pp
+The
+.Nm
+utility has historically been extremely forgiving of syntax errors,
+for example, the
+.Fl c
+and
+.Fl s
+options were ignored unless two strings were specified.
+This implementation will not permit illegal syntax.
+.Sh SEE ALSO
+.Xr dd 1 ,
+.Xr sed 1
+.Sh STANDARDS
+The
+.Nm
+utility is expected to be
+.St -p1003.2
+compatible.
+It should be noted that the feature wherein the last character of
+.Ar string2
+is duplicated if
+.Ar string2
+has less characters than
+.Ar string1
+is permitted by POSIX but is not required.
+Shell scripts attempting to be portable to other POSIX systems should use
+the
+.Dq [#*n]
+convention instead of relying on this behavior.
+.Sh BUGS
+.Nm
+was originally designed to work with
+.Tn US-ASCII .
+Its use with character sets that do not share all the properties of
+.Tn US-ASCII ,
+e.g., a symmetric set of upper and lower case characters
+that can be algorithmically converted one to the other,
+may yield unpredictable results.
+.Pp
+.Nm
+should be internationalized.
diff --git a/usr.bin/tr/tr.c b/usr.bin/tr/tr.c
new file mode 100644
index 0000000..8221880
--- /dev/null
+++ b/usr.bin/tr/tr.c
@@ -0,0 +1,283 @@
+/* $NetBSD: tr.c,v 1.20 2013/08/11 01:54:35 dholland Exp $ */
+
+/*
+ * Copyright (c) 1988, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+#ifndef lint
+__COPYRIGHT("@(#) Copyright (c) 1988, 1993\
+ The Regents of the University of California. All rights reserved.");
+#endif /* not lint */
+
+#ifndef lint
+#if 0
+static char sccsid[] = "@(#)tr.c 8.2 (Berkeley) 5/4/95";
+#endif
+__RCSID("$NetBSD: tr.c,v 1.20 2013/08/11 01:54:35 dholland Exp $");
+#endif /* not lint */
+
+#include <sys/types.h>
+
+#include <err.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "extern.h"
+
+static int string1[NCHARS], string2[NCHARS];
+
+static void setup(int *, const char *, int, int);
+__dead static void usage(void);
+
+int
+main(int argc, char **argv)
+{
+ int ch, ch2, lastch;
+ int cflag, dflag, sflag, isstring2;
+ STR *s1, *s2;
+
+ cflag = dflag = sflag = 0;
+ while ((ch = getopt(argc, argv, "cds")) != -1)
+ switch (ch) {
+ case 'c':
+ cflag = 1;
+ break;
+ case 'd':
+ dflag = 1;
+ break;
+ case 's':
+ sflag = 1;
+ break;
+ case '?':
+ default:
+ usage();
+ }
+ argc -= optind;
+ argv += optind;
+
+ switch(argc) {
+ case 0:
+ default:
+ usage();
+ /* NOTREACHED */
+ case 1:
+ isstring2 = 0;
+ break;
+ case 2:
+ isstring2 = 1;
+ break;
+ }
+
+ /*
+ * tr -ds [-c] string1 string2
+ * Delete all characters (or complemented characters) in string1.
+ * Squeeze all characters in string2.
+ */
+ if (dflag && sflag) {
+ if (!isstring2)
+ usage();
+
+ setup(string1, argv[0], 1, cflag);
+ setup(string2, argv[1], 2, 0);
+
+ for (lastch = OOBCH; (ch = getchar()) != EOF; )
+ if (!string1[ch] && (!string2[ch] || lastch != ch)) {
+ lastch = ch;
+ (void)putchar(ch);
+ }
+ exit(0);
+ }
+
+ /*
+ * tr -d [-c] string1
+ * Delete all characters (or complemented characters) in string1.
+ */
+ if (dflag) {
+ if (isstring2)
+ usage();
+
+ setup(string1, argv[0], 1, cflag);
+
+ while ((ch = getchar()) != EOF)
+ if (!string1[ch])
+ (void)putchar(ch);
+ exit(0);
+ }
+
+ /*
+ * tr -s [-c] string1
+ * Squeeze all characters (or complemented characters) in string1.
+ */
+ if (sflag && !isstring2) {
+ setup(string1, argv[0], 1, cflag);
+
+ for (lastch = OOBCH; (ch = getchar()) != EOF;)
+ if (!string1[ch] || lastch != ch) {
+ lastch = ch;
+ (void)putchar(ch);
+ }
+ exit(0);
+ }
+
+ /*
+ * tr [-cs] string1 string2
+ * Replace all characters (or complemented characters) in string1 with
+ * the character in the same position in string2. If the -s option is
+ * specified, squeeze all the characters in string2.
+ */
+ if (!isstring2)
+ usage();
+
+ /*
+ * The first and second strings need to be matched up. This
+ * means that if we are doing -c, we need to scan the first
+ * string in advance, complement it, and match *that* against
+ * the second string; otherwise we need to scan them together.
+ */
+
+ if (cflag) {
+ /*
+ * Scan string 1 and complement it. After this,
+ * string1[] contains 0 for chars to leave alone and 1
+ * for chars to translate.
+ */
+ setup(string1, argv[0], 1, cflag);
+ s1 = NULL; /* for safety */
+ /* we will use ch to iterate over string1, so start it */
+ ch = -1;
+ } else {
+ /* Create the scanner for string 1. */
+ s1 = str_create(1, argv[0]);
+ for (ch = 0; ch < NCHARS; ch++) {
+ string1[ch] = ch;
+ }
+ }
+ /* Create the scanner for string 2. */
+ s2 = str_create(2, argv[1]);
+
+ /* Read the first char of string 2 first to make sure there is one. */
+ if (!next(s2, &ch2))
+ errx(1, "empty string2");
+
+ /*
+ * Loop over the chars from string 1. After this loop string1[]
+ * is a mapping from input to output chars.
+ */
+ while (1) {
+ if (cflag) {
+ /*
+ * Try each character in order. For characters we
+ * skip over because we aren't translating them,
+ * set the translation to the identity.
+ */
+ ch++;
+ while (ch < NCHARS && string1[ch] == 0) {
+ if (string1[ch] == 0) {
+ string1[ch] = ch;
+ }
+ ch++;
+ }
+ if (ch == NCHARS) {
+ break;
+ }
+ }
+ else {
+ /* Get the next character from string 1. */
+ if (!next(s1, &ch)) {
+ break;
+ }
+ }
+
+ /* Set the translation to the character from string 2. */
+ string1[ch] = ch2;
+
+ /* Note the characters to squeeze in string2[]. */
+ if (sflag) {
+ string2[ch2] = 1;
+ }
+
+ /*
+ * Get the next character from string 2. If it runs
+ * out, this will keep returning the last character
+ * over and over again.
+ */
+ (void)next(s2, &ch2);
+ }
+
+ /*
+ * Now do it.
+ */
+
+ if (sflag)
+ for (lastch = OOBCH; (ch = getchar()) != EOF;) {
+ ch = string1[ch];
+ if (!string2[ch] || lastch != ch) {
+ lastch = ch;
+ (void)putchar(ch);
+ }
+ }
+ else
+ while ((ch = getchar()) != EOF)
+ (void)putchar(string1[ch]);
+
+ /* Clean up and exit. */
+ if (s1 != NULL) {
+ str_destroy(s1);
+ }
+ str_destroy(s2);
+ exit (0);
+}
+
+static void
+setup(int *string, const char *arg, int whichstring, int cflag)
+{
+ int cnt, *p;
+ int ch;
+ STR *str;
+
+ str = str_create(whichstring, arg);
+ while (next(str, &ch))
+ string[ch] = 1;
+ if (cflag)
+ for (p = string, cnt = NCHARS; cnt--; ++p)
+ *p = !*p;
+ str_destroy(str);
+}
+
+static void
+usage(void)
+{
+ (void)fprintf(stderr, "usage: tr [-cs] string1 string2\n");
+ (void)fprintf(stderr, " tr [-c] -d string1\n");
+ (void)fprintf(stderr, " tr [-c] -s string1\n");
+ (void)fprintf(stderr, " tr [-c] -ds string1 string2\n");
+ exit(1);
+}
diff --git a/usr.bin/true/true.1 b/usr.bin/true/true.1
new file mode 100644
index 0000000..5829560
--- /dev/null
+++ b/usr.bin/true/true.1
@@ -0,0 +1,56 @@
+.\" $NetBSD: true.1,v 1.7 2003/08/07 11:16:48 agc Exp $
+.\"
+.\" Copyright (c) 1983, 1985, 1990 The Regents of the University of California.
+.\" All rights reserved.
+.\"
+.\" This code is derived from software contributed to Berkeley by
+.\" the Institute of Electrical and Electronics Engineers, Inc.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\" notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must reproduce the above copyright
+.\" notice, this list of conditions and the following disclaimer in the
+.\" documentation and/or other materials provided with the distribution.
+.\" 3. Neither the name of the University nor the names of its contributors
+.\" may be used to endorse or promote products derived from this software
+.\" without specific prior written permission.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+.\" ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+.\" SUCH DAMAGE.
+.\"
+.\" from: @(#)true.1 6.4 (Berkeley) 6/27/91
+.\" $NetBSD: true.1,v 1.7 2003/08/07 11:16:48 agc Exp $
+.\"
+.Dd June 27, 1991
+.Dt TRUE 1
+.Os
+.Sh NAME
+.Nm true
+.Nd return true value
+.Sh SYNOPSIS
+.Nm
+.Sh DESCRIPTION
+The
+.Nm
+utility always returns with exit code zero.
+.Sh SEE ALSO
+.Xr csh 1 ,
+.Xr false 1 ,
+.Xr sh 1
+.Sh STANDARDS
+The
+.Nm
+utility conforms to
+.St -p1003.2-92 .
diff --git a/usr.bin/true/true.sh b/usr.bin/true/true.sh
new file mode 100644
index 0000000..20f87ef
--- /dev/null
+++ b/usr.bin/true/true.sh
@@ -0,0 +1,2 @@
+#! /bin/sh
+exit 0
diff --git a/usr.bin/tsort/tsort.1 b/usr.bin/tsort/tsort.1
new file mode 100644
index 0000000..061824b
--- /dev/null
+++ b/usr.bin/tsort/tsort.1
@@ -0,0 +1,87 @@
+.\" $NetBSD: tsort.1,v 1.10 2003/08/07 11:16:50 agc Exp $
+.\"
+.\" Copyright (c) 1990, 1993, 1994
+.\" The Regents of the University of California. All rights reserved.
+.\"
+.\" This manual is derived from one contributed to Berkeley by
+.\" Michael Rendell of Memorial University of Newfoundland.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\" notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must reproduce the above copyright
+.\" notice, this list of conditions and the following disclaimer in the
+.\" documentation and/or other materials provided with the distribution.
+.\" 3. Neither the name of the University nor the names of its contributors
+.\" may be used to endorse or promote products derived from this software
+.\" without specific prior written permission.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+.\" ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+.\" SUCH DAMAGE.
+.\"
+.\" @(#)tsort.1 8.3 (Berkeley) 4/1/94
+.\"
+.Dd April 1, 1994
+.Dt TSORT 1
+.Os
+.Sh NAME
+.Nm tsort
+.Nd topological sort of a directed graph
+.Sh SYNOPSIS
+.Nm
+.Op Fl l
+.Op Fl q
+.Op Ar file
+.Sh DESCRIPTION
+.Nm
+takes a list of pairs of node names representing directed arcs in
+a graph and prints the nodes in topological order on standard output.
+Input is taken from the named
+.Ar file ,
+or from standard input if no file
+is given.
+.Pp
+Node names in the input are separated by white space and there must
+be an even number of node names.
+.Pp
+Presence of a node in a graph can be represented by an arc from the node
+to itself.
+This is useful when a node is not connected to any other nodes.
+.Pp
+If the graph contains a cycle (and therefore cannot be properly sorted),
+one of the arcs in the cycle is ignored and the sort continues.
+Cycles are reported on standard error.
+.Pp
+The options are as follows:
+.Bl -tag -width Ds
+.It Fl l
+Search for and display the longest cycle.
+Can take a very long time.
+.It Fl q
+Do not display informational messages about cycles.
+This is primarily
+intended for building libraries, where optimal ordering is not critical,
+and cycles occur often.
+.El
+.Sh SEE ALSO
+.Xr ar 1
+.Sh HISTORY
+A
+.Nm
+command appeared in
+.At v7 .
+This
+.Nm
+command and manual page are derived from sources contributed to Berkeley by
+Michael Rendell of Memorial University of Newfoundland.
diff --git a/usr.bin/tsort/tsort.c b/usr.bin/tsort/tsort.c
new file mode 100644
index 0000000..3e8d569
--- /dev/null
+++ b/usr.bin/tsort/tsort.c
@@ -0,0 +1,433 @@
+/* $NetBSD: tsort.c,v 1.23 2011/09/06 18:34:37 joerg Exp $ */
+
+/*
+ * Copyright (c) 1989, 1993, 1994
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Michael Rendell of Memorial University of Newfoundland.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#if HAVE_NBTOOL_CONFIG_H
+#include "nbtool_config.h"
+#endif
+
+#include <sys/cdefs.h>
+#if !defined(lint)
+__COPYRIGHT("@(#) Copyright (c) 1989, 1993, 1994\
+ The Regents of the University of California. All rights reserved.");
+#if 0
+static char sccsid[] = "@(#)tsort.c 8.3 (Berkeley) 5/4/95";
+#endif
+__RCSID("$NetBSD: tsort.c,v 1.23 2011/09/06 18:34:37 joerg Exp $");
+#endif /* not lint */
+
+#include <sys/types.h>
+#include <ctype.h>
+#include <db.h>
+#include <err.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+/*
+ * Topological sort. Input is a list of pairs of strings separated by
+ * white space (spaces, tabs, and/or newlines); strings are written to
+ * standard output in sorted order, one per line.
+ *
+ * usage:
+ * tsort [-l] [inputfile]
+ * If no input file is specified, standard input is read.
+ *
+ * Should be compatible with AT&T tsort HOWEVER the output is not identical
+ * (i.e. for most graphs there is more than one sorted order, and this tsort
+ * usually generates a different one then the AT&T tsort). Also, cycle
+ * reporting seems to be more accurate in this version (the AT&T tsort
+ * sometimes says a node is in a cycle when it isn't).
+ *
+ * Michael Rendell, michael@stretch.cs.mun.ca - Feb 26, '90
+ */
+#define HASHSIZE 53 /* doesn't need to be big */
+#define NF_MARK 0x1 /* marker for cycle detection */
+#define NF_ACYCLIC 0x2 /* this node is cycle free */
+#define NF_NODEST 0x4 /* Unreachable */
+
+typedef struct node_str NODE;
+
+struct node_str {
+ NODE **n_prevp; /* pointer to previous node's n_next */
+ NODE *n_next; /* next node in graph */
+ NODE **n_arcs; /* array of arcs to other nodes */
+ int n_narcs; /* number of arcs in n_arcs[] */
+ int n_arcsize; /* size of n_arcs[] array */
+ int n_refcnt; /* # of arcs pointing to this node */
+ int n_flags; /* NF_* */
+ char n_name[1]; /* name of this node */
+};
+
+typedef struct _buf {
+ char *b_buf;
+ int b_bsize;
+} BUF;
+
+static DB *db;
+static NODE *graph, **cycle_buf, **longest_cycle;
+static int debug, longest, quiet;
+
+static void add_arc(char *, char *);
+static void clear_cycle(void);
+static int find_cycle(NODE *, NODE *, int, int);
+static NODE *get_node(char *);
+static void *grow_buf(void *, int);
+static void remove_node(NODE *);
+static void tsort(void);
+__dead static void usage(void);
+
+int
+main(int argc, char *argv[])
+{
+ BUF *b;
+ int c, n;
+ FILE *fp;
+ int bsize, ch, nused;
+ BUF bufs[2];
+
+ setprogname(argv[0]);
+
+ fp = NULL;
+ while ((ch = getopt(argc, argv, "dlq")) != -1)
+ switch (ch) {
+ case 'd':
+ debug = 1;
+ break;
+ case 'l':
+ longest = 1;
+ break;
+ case 'q':
+ quiet = 1;
+ break;
+ case '?':
+ default:
+ usage();
+ }
+ argc -= optind;
+ argv += optind;
+
+ switch (argc) {
+ case 0:
+ fp = stdin;
+ break;
+ case 1:
+ if ((fp = fopen(*argv, "r")) == NULL)
+ err(1, "%s", *argv);
+ break;
+ default:
+ usage();
+ }
+
+ for (b = bufs, n = 2; --n >= 0; b++)
+ b->b_buf = grow_buf(NULL, b->b_bsize = 1024);
+
+ /* parse input and build the graph */
+ for (n = 0, c = getc(fp);;) {
+ while (c != EOF && isspace(c))
+ c = getc(fp);
+ if (c == EOF)
+ break;
+
+ nused = 0;
+ b = &bufs[n];
+ bsize = b->b_bsize;
+ do {
+ b->b_buf[nused++] = c;
+ if (nused == bsize)
+ b->b_buf = grow_buf(b->b_buf, bsize *= 2);
+ c = getc(fp);
+ } while (c != EOF && !isspace(c));
+
+ b->b_buf[nused] = '\0';
+ b->b_bsize = bsize;
+ if (n)
+ add_arc(bufs[0].b_buf, bufs[1].b_buf);
+ n = !n;
+ }
+ (void)fclose(fp);
+ if (n)
+ errx(1, "odd data count");
+
+ /* do the sort */
+ tsort();
+ return(0);
+}
+
+/* double the size of oldbuf and return a pointer to the new buffer. */
+static void *
+grow_buf(void *bp, int size)
+{
+ void *n;
+
+ if ((n = realloc(bp, (u_int)size)) == NULL)
+ err(1, "realloc");
+ bp = n;
+ return (bp);
+}
+
+/*
+ * add an arc from node s1 to node s2 in the graph. If s1 or s2 are not in
+ * the graph, then add them.
+ */
+static void
+add_arc(char *s1, char *s2)
+{
+ NODE *n1;
+ NODE *n2;
+ int bsize, i;
+
+ n1 = get_node(s1);
+
+ if (!strcmp(s1, s2))
+ return;
+
+ n2 = get_node(s2);
+
+ /*
+ * Check if this arc is already here.
+ */
+ for (i = 0; i < n1->n_narcs; i++)
+ if (n1->n_arcs[i] == n2)
+ return;
+ /*
+ * Add it.
+ */
+ if (n1->n_narcs == n1->n_arcsize) {
+ if (!n1->n_arcsize)
+ n1->n_arcsize = 10;
+ bsize = n1->n_arcsize * sizeof(*n1->n_arcs) * 2;
+ n1->n_arcs = grow_buf(n1->n_arcs, bsize);
+ n1->n_arcsize = bsize / sizeof(*n1->n_arcs);
+ }
+ n1->n_arcs[n1->n_narcs++] = n2;
+ ++n2->n_refcnt;
+}
+
+/* Find a node in the graph (insert if not found) and return a pointer to it. */
+static NODE *
+get_node(char *name)
+{
+ DBT data, key;
+ NODE *n;
+
+ if (db == NULL &&
+ (db = dbopen(NULL, O_RDWR, 0, DB_HASH, NULL)) == NULL)
+ err(1, "db: %s", name);
+
+ key.data = name;
+ key.size = strlen(name) + 1;
+
+ switch ((*db->get)(db, &key, &data, 0)) {
+ case 0:
+ (void)memmove(&n, data.data, sizeof(n));
+ return (n);
+ case 1:
+ break;
+ default:
+ case -1:
+ err(1, "db: %s", name);
+ }
+
+ if ((n = malloc(sizeof(NODE) + key.size)) == NULL)
+ err(1, "malloc");
+
+ n->n_narcs = 0;
+ n->n_arcsize = 0;
+ n->n_arcs = NULL;
+ n->n_refcnt = 0;
+ n->n_flags = 0;
+ (void)memmove(n->n_name, name, key.size);
+
+ /* Add to linked list. */
+ if ((n->n_next = graph) != NULL)
+ graph->n_prevp = &n->n_next;
+ n->n_prevp = &graph;
+ graph = n;
+
+ /* Add to hash table. */
+ data.data = &n;
+ data.size = sizeof(n);
+ if ((*db->put)(db, &key, &data, 0))
+ err(1, "db: %s", name);
+ return (n);
+}
+
+
+/*
+ * Clear the NODEST flag from all nodes.
+ */
+static void
+clear_cycle(void)
+{
+ NODE *n;
+
+ for (n = graph; n != NULL; n = n->n_next)
+ n->n_flags &= ~NF_NODEST;
+}
+
+/* do topological sort on graph */
+static void
+tsort(void)
+{
+ NODE *n, *next;
+ int cnt, i;
+
+ while (graph != NULL) {
+ /*
+ * Keep getting rid of simple cases until there are none left,
+ * if there are any nodes still in the graph, then there is
+ * a cycle in it.
+ */
+ do {
+ for (cnt = 0, n = graph; n != NULL; n = next) {
+ next = n->n_next;
+ if (n->n_refcnt == 0) {
+ remove_node(n);
+ ++cnt;
+ }
+ }
+ } while (graph != NULL && cnt);
+
+ if (graph == NULL)
+ break;
+
+ if (!cycle_buf) {
+ /*
+ * Allocate space for two cycle logs - one to be used
+ * as scratch space, the other to save the longest
+ * cycle.
+ */
+ for (cnt = 0, n = graph; n != NULL; n = n->n_next)
+ ++cnt;
+ cycle_buf = malloc((u_int)sizeof(NODE *) * cnt);
+ longest_cycle = malloc((u_int)sizeof(NODE *) * cnt);
+ if (cycle_buf == NULL || longest_cycle == NULL)
+ err(1, "malloc");
+ }
+ for (n = graph; n != NULL; n = n->n_next) {
+ if (!(n->n_flags & NF_ACYCLIC)) {
+ if ((cnt = find_cycle(n, n, 0, 0)) != 0) {
+ if (!quiet) {
+ warnx("cycle in data");
+ for (i = 0; i < cnt; i++)
+ warnx("%s",
+ longest_cycle[i]->n_name);
+ }
+ remove_node(n);
+ clear_cycle();
+ break;
+ } else {
+ /* to avoid further checks */
+ n->n_flags |= NF_ACYCLIC;
+ clear_cycle();
+ }
+ }
+ }
+ if (n == NULL)
+ errx(1, "internal error -- could not find cycle");
+ }
+}
+
+/* print node and remove from graph (does not actually free node) */
+static void
+remove_node(NODE *n)
+{
+ NODE **np;
+ int i;
+
+ (void)printf("%s\n", n->n_name);
+ for (np = n->n_arcs, i = n->n_narcs; --i >= 0; np++)
+ --(*np)->n_refcnt;
+ n->n_narcs = 0;
+ *n->n_prevp = n->n_next;
+ if (n->n_next)
+ n->n_next->n_prevp = n->n_prevp;
+}
+
+
+/* look for the longest? cycle from node from to node to. */
+static int
+find_cycle(NODE *from, NODE *to, int longest_len, int depth)
+{
+ NODE **np;
+ int i, len;
+
+ /*
+ * avoid infinite loops and ignore portions of the graph known
+ * to be acyclic
+ */
+ if (from->n_flags & (NF_NODEST|NF_MARK|NF_ACYCLIC))
+ return (0);
+ from->n_flags |= NF_MARK;
+
+ for (np = from->n_arcs, i = from->n_narcs; --i >= 0; np++) {
+ cycle_buf[depth] = *np;
+ if (*np == to) {
+ if (depth + 1 > longest_len) {
+ longest_len = depth + 1;
+ (void)memcpy(longest_cycle, cycle_buf,
+ longest_len * sizeof(NODE *));
+ }
+ } else {
+ if ((*np)->n_flags & (NF_MARK|NF_ACYCLIC|NF_NODEST))
+ continue;
+ len = find_cycle(*np, to, longest_len, depth + 1);
+
+ if (debug)
+ (void)printf("%*s %s->%s %d\n", depth, "",
+ from->n_name, to->n_name, len);
+
+ if (len == 0)
+ (*np)->n_flags |= NF_NODEST;
+
+ if (len > longest_len)
+ longest_len = len;
+
+ if (len > 0 && !longest)
+ break;
+ }
+ }
+ from->n_flags &= ~NF_MARK;
+ return (longest_len);
+}
+
+static void
+usage(void)
+{
+ (void)fprintf(stderr, "usage: tsort [-lq] [file]\n");
+ exit(1);
+}
diff --git a/usr.bin/tty/tty.1 b/usr.bin/tty/tty.1
new file mode 100644
index 0000000..5b65d66
--- /dev/null
+++ b/usr.bin/tty/tty.1
@@ -0,0 +1,75 @@
+.\" $NetBSD: tty.1,v 1.9 2017/07/03 21:34:22 wiz Exp $
+.\"
+.\" Copyright (c) 1990, 1993
+.\" The Regents of the University of California. All rights reserved.
+.\"
+.\" This code is derived from software contributed to Berkeley by
+.\" the Institute of Electrical and Electronics Engineers, Inc.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\" notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must reproduce the above copyright
+.\" notice, this list of conditions and the following disclaimer in the
+.\" documentation and/or other materials provided with the distribution.
+.\" 3. Neither the name of the University nor the names of its contributors
+.\" may be used to endorse or promote products derived from this software
+.\" without specific prior written permission.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+.\" ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+.\" SUCH DAMAGE.
+.\"
+.\" @(#)tty.1 8.1 (Berkeley) 6/6/93
+.\"
+.Dd June 6, 1993
+.Dt TTY 1
+.Os
+.Sh NAME
+.Nm tty
+.Nd return user's terminal name
+.Sh SYNOPSIS
+.Nm
+.Op Fl s
+.Sh DESCRIPTION
+The
+.Nm
+utility writes the name of the terminal attached to standard input
+to standard output.
+The name that is written is the string returned by
+.Xr ttyname 3 .
+If the standard input is not a terminal, the message ``not a tty''
+is written.
+The options are as follows:
+.Bl -tag -width Ds
+.It Fl s
+Don't write the terminal name; only the exit status is affected
+when this option is specified.
+The
+.Fl s
+option is deprecated in favor of the
+.Dq Li "test -t 0"
+command.
+.El
+.Pp
+.Nm
+exits 0 if the standard input is a terminal, 1 if the standard input is
+not a terminal, and >1 if an error occurs.
+.Sh SEE ALSO
+.Xr test 1 ,
+.Xr ttyname 3
+.Sh STANDARDS
+The
+.Nm
+utility conforms to
+.St -p1003.2-92 .
diff --git a/usr.bin/tty/tty.c b/usr.bin/tty/tty.c
new file mode 100644
index 0000000..c5e5e80
--- /dev/null
+++ b/usr.bin/tty/tty.c
@@ -0,0 +1,81 @@
+/* $NetBSD: tty.c,v 1.8 2011/09/06 18:34:57 joerg Exp $ */
+
+/*
+ * Copyright (c) 1988, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+#ifndef lint
+__COPYRIGHT("@(#) Copyright (c) 1988, 1993\
+ The Regents of the University of California. All rights reserved.");
+#endif /* not lint */
+
+#ifndef lint
+#if 0
+static char sccsid[] = "@(#)tty.c 8.1 (Berkeley) 6/6/93";
+#endif
+__RCSID("$NetBSD: tty.c,v 1.8 2011/09/06 18:34:57 joerg Exp $");
+#endif /* not lint */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+__dead static void usage(void);
+
+int
+main(int argc, char **argv)
+{
+ int ch, sflag;
+ char *t;
+
+ sflag = 0;
+ while ((ch = getopt(argc, argv, "s")) != -1) {
+ switch((char)ch) {
+ case 's':
+ sflag = 1;
+ break;
+ case '?':
+ default:
+ usage();
+ /* NOTREACHED */
+ }
+ }
+
+ t = ttyname(STDIN_FILENO);
+ if (!sflag)
+ puts(t ? t : "not a tty");
+ exit(t ? 0 : 1);
+}
+
+static void
+usage(void)
+{
+ fprintf(stderr, "usage: tty [-s]\n");
+ exit(2);
+}
diff --git a/usr.bin/uname/uname.1 b/usr.bin/uname/uname.1
new file mode 100644
index 0000000..8253ea9
--- /dev/null
+++ b/usr.bin/uname/uname.1
@@ -0,0 +1,86 @@
+.\" $NetBSD: uname.1,v 1.12 2005/03/27 18:41:22 peter Exp $
+.\"
+.\" Copyright (c) 1990 The Regents of the University of California.
+.\" All rights reserved.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\" notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must reproduce the above copyright
+.\" notice, this list of conditions and the following disclaimer in the
+.\" documentation and/or other materials provided with the distribution.
+.\" 3. Neither the name of the University nor the names of its contributors
+.\" may be used to endorse or promote products derived from this software
+.\" without specific prior written permission.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+.\" ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+.\" SUCH DAMAGE.
+.\"
+.\" from: @(#)du.1 6.13 (Berkeley) 6/20/91
+.\" $NetBSD: uname.1,v 1.12 2005/03/27 18:41:22 peter Exp $
+.\"
+.Dd November 9, 1998
+.Dt UNAME 1
+.Os
+.Sh NAME
+.Nm uname
+.Nd Print operating system name
+.Sh SYNOPSIS
+.Nm
+.Op Fl amnprsv
+.Sh DESCRIPTION
+The
+.Nm
+utility writes symbols representing one or more system characteristics
+to the standard output.
+.Pp
+The following options are available:
+.Bl -tag -width indent
+.It Fl a
+Behave as though all of the options
+.Fl mnrsv
+were specified.
+.It Fl m
+print the machine hardware name.
+.It Fl n
+print the nodename (the nodename may be a name
+that the system is known by to a communications
+network).
+.It Fl p
+print the machine processor architecture name.
+.It Fl r
+print the operating system release.
+.It Fl s
+print the operating system name.
+.It Fl v
+print the operating system version.
+.El
+.Pp
+If no options are specified,
+.Nm
+prints the operating system name as if the
+.Fl s
+option had been specified.
+.Sh SEE ALSO
+.Xr hostname 1 ,
+.Xr machine 1 ,
+.Xr uname 3
+.Sh STANDARDS
+The
+.Nm
+utility conforms to
+.St -p1003.2-92 .
+The
+.Fl p
+option is an extension to the standard.
diff --git a/usr.bin/uname/uname.c b/usr.bin/uname/uname.c
new file mode 100644
index 0000000..de5e08e
--- /dev/null
+++ b/usr.bin/uname/uname.c
@@ -0,0 +1,159 @@
+/* $NetBSD: uname.c,v 1.11 2011/09/06 18:35:13 joerg Exp $ */
+
+/*
+ * Copyright (c) 1994 Winning Strategies, Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. All advertising materials mentioning features or use of this software
+ * must display the following acknowledgement:
+ * This product includes software developed by Winning Strategies, Inc.
+ * 4. The name of Winning Strategies, Inc. may not be used to endorse or
+ * promote products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+#ifndef lint
+__RCSID("$NetBSD: uname.c,v 1.11 2011/09/06 18:35:13 joerg Exp $");
+#endif /* not lint */
+
+#include <sys/param.h>
+#include <limits.h>
+#include <locale.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <err.h>
+
+#include <sys/sysctl.h>
+#include <sys/utsname.h>
+
+__dead static void usage(void);
+
+/* Note that PRINT_MACHINE_ARCH is excluded from PRINT_ALL! */
+#define PRINT_SYSNAME 0x01
+#define PRINT_NODENAME 0x02
+#define PRINT_RELEASE 0x04
+#define PRINT_VERSION 0x08
+#define PRINT_MACHINE 0x10
+#define PRINT_MACHINE_ARCH 0x20
+#define PRINT_ALL \
+ (PRINT_SYSNAME|PRINT_NODENAME|PRINT_RELEASE|PRINT_VERSION|PRINT_MACHINE)
+
+int
+main(int argc, char **argv)
+{
+ struct utsname u;
+ char machine_arch[SYS_NMLN];
+ int c;
+ int space = 0;
+ int print_mask = 0;
+
+ (void)setlocale(LC_ALL, "");
+
+ while ((c = getopt(argc,argv,"amnprsv")) != -1) {
+ switch (c) {
+ case 'a':
+ print_mask |= PRINT_ALL;
+ break;
+ case 'm':
+ print_mask |= PRINT_MACHINE;
+ break;
+ case 'n':
+ print_mask |= PRINT_NODENAME;
+ break;
+ case 'p':
+ print_mask |= PRINT_MACHINE_ARCH;
+ break;
+ case 'r':
+ print_mask |= PRINT_RELEASE;
+ break;
+ case 's':
+ print_mask |= PRINT_SYSNAME;
+ break;
+ case 'v':
+ print_mask |= PRINT_VERSION;
+ break;
+ default:
+ usage();
+ /* NOTREACHED */
+ }
+ }
+
+ if (optind != argc) {
+ usage();
+ /* NOTREACHED */
+ }
+
+ if (!print_mask) {
+ print_mask = PRINT_SYSNAME;
+ }
+
+ if (uname(&u) != 0) {
+ err(EXIT_FAILURE, "uname");
+ /* NOTREACHED */
+ }
+ if (print_mask & PRINT_MACHINE_ARCH) {
+ int mib[2] = { CTL_HW, HW_MACHINE_ARCH };
+ size_t len = sizeof (machine_arch);
+
+ if (sysctl(mib, sizeof (mib) / sizeof (mib[0]), machine_arch,
+ &len, NULL, 0) < 0)
+ err(EXIT_FAILURE, "sysctl");
+ }
+
+ if (print_mask & PRINT_SYSNAME) {
+ space++;
+ fputs(u.sysname, stdout);
+ }
+ if (print_mask & PRINT_NODENAME) {
+ if (space++) putchar(' ');
+ fputs(u.nodename, stdout);
+ }
+ if (print_mask & PRINT_RELEASE) {
+ if (space++) putchar(' ');
+ fputs(u.release, stdout);
+ }
+ if (print_mask & PRINT_VERSION) {
+ if (space++) putchar(' ');
+ fputs(u.version, stdout);
+ }
+ if (print_mask & PRINT_MACHINE) {
+ if (space++) putchar(' ');
+ fputs(u.machine, stdout);
+ }
+ if (print_mask & PRINT_MACHINE_ARCH) {
+ if (space++) putchar(' ');
+ fputs(machine_arch, stdout);
+ }
+ putchar('\n');
+
+ exit(EXIT_SUCCESS);
+ /* NOTREACHED */
+}
+
+static void
+usage(void)
+{
+ fprintf(stderr, "usage: uname [-amnprsv]\n");
+ exit(EXIT_FAILURE);
+}
diff --git a/usr.bin/unexpand/unexpand.c b/usr.bin/unexpand/unexpand.c
new file mode 100644
index 0000000..a9d7279
--- /dev/null
+++ b/usr.bin/unexpand/unexpand.c
@@ -0,0 +1,218 @@
+/* $NetBSD: unexpand.c,v 1.15 2016/02/03 05:32:14 christos Exp $ */
+
+/*-
+ * Copyright (c) 1980, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+#ifndef lint
+__COPYRIGHT("@(#) Copyright (c) 1980, 1993\
+ The Regents of the University of California. All rights reserved.");
+#endif /* not lint */
+
+#ifndef lint
+#if 0
+static char sccsid[] = "@(#)unexpand.c 8.1 (Berkeley) 6/6/93";
+#endif
+__RCSID("$NetBSD: unexpand.c,v 1.15 2016/02/03 05:32:14 christos Exp $");
+#endif /* not lint */
+
+/*
+ * unexpand - put tabs into a file replacing blanks
+ */
+#include <limits.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <errno.h>
+#include <err.h>
+#include <util.h>
+
+
+#define DSTOP 8
+static int all;
+static size_t nstops;
+static size_t maxstops;
+static size_t *tabstops;
+
+static void tabify(const char *, size_t);
+static void usage(void) __attribute__((__noreturn__));
+
+static void
+usage(void)
+{
+ (void)fprintf(stderr, "Usage: %s [-a] [-t tabstop] [file ...]\n",
+ getprogname());
+ exit(EXIT_FAILURE);
+}
+
+int
+main(int argc, char **argv)
+{
+ int c;
+ char *ep, *tab;
+ char *line;
+ size_t len;
+ unsigned long i;
+
+ setprogname(argv[0]);
+
+ while ((c = getopt(argc, argv, "at:")) != -1) {
+ switch (c) {
+ case 'a':
+ if (nstops)
+ usage();
+ all++;
+ break;
+ case 't':
+ if (all)
+ usage();
+ while ((tab = strsep(&optarg, ", \t")) != NULL) {
+ if (*tab == '\0')
+ continue;
+ errno = 0;
+ i = strtoul(tab, &ep, 0);
+ if (*ep || (errno == ERANGE && i == ULONG_MAX))
+ errx(EXIT_FAILURE,
+ "Invalid tabstop `%s'", tab);
+ if (nstops >= maxstops) {
+ maxstops += 20;
+ tabstops = erealloc(tabstops, maxstops);
+ }
+ if (nstops && tabstops[nstops - 1] >= (size_t)i)
+ errx(EXIT_FAILURE,
+ "Bad tabstop spec `%s', must be "
+ "greater than the previous `%zu'",
+ tab, tabstops[nstops - 1]);
+ tabstops[nstops++] = i;
+ }
+ break;
+ case '?':
+ default:
+ usage();
+ }
+ }
+ argc -= optind;
+ argv += optind;
+
+ for (i = 0; i < nstops; i++)
+ fprintf(stderr, "%lu %zu\n", i, tabstops[i]);
+
+ do {
+ if (argc > 0) {
+ if (freopen(argv[0], "r", stdin) == NULL)
+ err(EXIT_FAILURE, "Cannot open `%s'", argv[0]);
+ argc--, argv++;
+ }
+ while ((line = fgetln(stdin, &len)) != NULL)
+ tabify(line, len);
+ } while (argc > 0);
+ return EXIT_SUCCESS;
+}
+
+static void
+tabify(const char *line, size_t len)
+{
+ const char *e, *p;
+ size_t dcol, ocol, limit, n;
+
+ dcol = ocol = 0;
+ limit = nstops == 0 ? UINT_MAX : tabstops[nstops - 1] - 1;
+ e = line + len;
+ for (p = line; p < e; p++) {
+ if (*p == ' ') {
+ dcol++;
+ continue;
+ } else if (*p == '\t') {
+ if (nstops == 0) {
+ dcol = (1 + dcol / DSTOP) * DSTOP;
+ continue;
+ } else {
+ for (n = 0; n < nstops &&
+ tabstops[n] - 1 < dcol; n++)
+ continue;
+ if (n < nstops - 1 && tabstops[n] - 1 < limit) {
+ dcol = tabstops[n];
+ continue;
+ }
+ }
+ }
+
+ /* Output our tabs */
+ if (nstops == 0) {
+ while (((ocol + DSTOP) / DSTOP) <= (dcol / DSTOP)) {
+ if (dcol - ocol < 2)
+ break;
+ if (putchar('\t') == EOF)
+ goto out;
+ ocol = (1 + ocol / DSTOP) * DSTOP;
+ }
+ } else {
+ for (n = 0; n < nstops && tabstops[n] <= ocol; n++)
+ continue;
+ while (n < nstops && tabstops[n] <= dcol && ocol < dcol
+ && ocol < limit) {
+ if (putchar('\t') == EOF)
+ goto out;
+ ocol = tabstops[n++];
+ }
+ }
+
+ /* Output remaining spaces */
+ while (ocol < dcol && ocol < limit) {
+ if (putchar(' ') == EOF)
+ goto out;
+ ocol++;
+ }
+
+ /* Output our char */
+ if (putchar(*p) == EOF)
+ goto out;
+ if (*p == '\b') {
+ if (ocol > 0) {
+ ocol--;
+ dcol--;
+ }
+ } else {
+ ocol++;
+ dcol++;
+ }
+
+ /* Output remainder of line */
+ if (!all || dcol >= limit) {
+ for (p++; p < e; p++)
+ if (putchar(*p) == EOF)
+ goto out;
+ return;
+ }
+ }
+ return;
+out:
+ err(EXIT_FAILURE, "write failed");
+}
diff --git a/usr.bin/unifdef/unifdef.1 b/usr.bin/unifdef/unifdef.1
new file mode 100644
index 0000000..7319245
--- /dev/null
+++ b/usr.bin/unifdef/unifdef.1
@@ -0,0 +1,347 @@
+.\" $NetBSD: unifdef.1,v 1.13 2017/07/03 21:34:22 wiz Exp $
+.\"
+.\" Copyright (c) 1985, 1991, 1993
+.\" The Regents of the University of California. All rights reserved.
+.\"
+.\" This code is derived from software contributed to Berkeley by
+.\" Dave Yost.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\" notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must reproduce the above copyright
+.\" notice, this list of conditions and the following disclaimer in the
+.\" documentation and/or other materials provided with the distribution.
+.\" 3. Neither the name of the University nor the names of its contributors
+.\" may be used to endorse or promote products derived from this software
+.\" without specific prior written permission.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+.\" ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+.\" SUCH DAMAGE.
+.\"
+.\" Portions of this code (support for #if and #elif) are Copyright (c)
+.\" 2002, 2003 Tony Finch.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\" notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must reproduce the above copyright
+.\" notice, this list of conditions and the following disclaimer in the
+.\" documentation and/or other materials provided with the distribution.
+.\" 3. All advertising materials mentioning features or use of this software
+.\" must display the following acknowledgement:
+.\" This product includes software developed by the University of
+.\" California, Berkeley and its contributors.
+.\" 4. Neither the name of the University nor the names of its contributors
+.\" may be used to endorse or promote products derived from this software
+.\" without specific prior written permission.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+.\" ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+.\" SUCH DAMAGE.
+.\"
+.\" @(#)unifdef.1 8.2 (Berkeley) 4/1/94
+.\" $dotat: things/unifdef.1,v 1.45 2003/01/20 14:37:08 fanf2 Exp $
+.\" $FreeBSD: src/usr.bin/unifdef/unifdef.1,v 1.19 2003/01/20 12:41:41 fanf Exp $
+.\"
+.Dd June 5, 2009
+.Dt UNIFDEF 1
+.Os
+.Sh NAME
+.Nm unifdef ,
+.Nm unifdefall
+.Nd remove preprocessor conditionals from code
+.Sh SYNOPSIS
+.Nm
+.Op Fl ceklst
+.Op Fl I Ns Ar path
+.Op Fl D Ns Ar sym Ns Op = Ns Ar val
+.Op Fl U Ns Ar sym
+.Op Fl iD Ns Ar sym Ns Op = Ns Ar val
+.Op Fl iU Ns Ar sym
+.Ar ...
+.Op Fl o Ar output
+.Op Ar file
+.Nm unifdefall
+.Op Fl I Ns Ar path
+.Ar ...
+.Ar file
+.Sh DESCRIPTION
+The
+.Nm
+utility selectively processes conditional
+.Xr cpp 1
+directives.
+It removes from a file both the directives and any additional text
+that they specify should be removed, while otherwise leaving the
+file alone.
+.Pp
+The
+.Nm
+utility acts on
+.Ic #if , #ifdef , #ifndef , #elif , #else ,
+and
+.Ic #endif
+lines,
+and it understands only the commonly-used subset
+of the expression syntax for
+.Ic #if
+and
+.Ic #elif
+lines.
+It handles
+integer values of symbols defined on the command line,
+the
+.Fn defined
+operator applied to symbols defined or undefined on the command line,
+the operators
+.Ic \&! , < , > , <= , >= , == , != , && , || ,
+and parenthesized expressions.
+Anything that it does not understand is passed through unharmed.
+It only processes
+.Ic #ifdef
+and
+.Ic #ifndef
+directives if the symbol is specified on the command line,
+otherwise they are also passed through unchanged.
+By default, it ignores
+.Ic #if
+and
+.Ic #elif
+lines with constant expressions,
+or they may be processed by specifying the
+.Fl k
+flag on the command line.
+.Pp
+The
+.Nm
+utility also understands just enough about C
+to know when one of the directives is inactive
+because it is inside
+a comment,
+or affected by a backslash-continued line.
+It spots unusually-formatted preprocessor directives
+and knows when the layout is too odd to handle.
+.Pp
+A script called
+.Nm unifdefall
+can be used to remove all conditional
+.Xr cpp 1
+directives from a file.
+It uses
+.Nm Fl s
+and
+.Nm cpp Fl dM
+to get lists of all the controlling symbols
+and their definitions (or lack thereof),
+then invokes
+.Nm
+with appropriate arguments to process the file.
+.Pp
+Available options:
+.Bl -tag -width indent -compact
+.It Fl D Ns Ar sym Ns Op = Ns Ar val
+Specify that a symbol is defined,
+and optionally specify what value to give it
+for the purpose of handling
+.Ic #if
+and
+.Ic #elif
+directives.
+.It Fl U Ns Ar sym
+Specify that a symbol is undefined.
+If the same symbol appears in more than one argument,
+the last occurrence dominates.
+.It Fl c
+If the
+.Fl c
+flag is specified,
+then the operation of
+.Nm
+is complemented,
+i.e., the lines that would have been removed or blanked
+are retained and vice versa.
+.It Fl e
+Because
+.Nm
+processes its input one line at a time,
+it cannot remove preprocessor directives that span more than one line.
+The most common example of this is a directive with a multi-line
+comment hanging off its right hand end.
+By default,
+if
+.Nm
+has to process such a directive,
+it will complain that the line is too obfuscated.
+The
+.Fl e
+option changes the behaviour so that,
+where possible,
+such lines are left unprocessed instead of reporting an error.
+.It Fl k
+Process
+.Ic #if
+and
+.Ic #elif
+lines with constant expressions.
+By default, sections controlled by such lines are passed through unchanged
+because they typically start
+.Dq Li "#if 0"
+and are used as a kind of comment to sketch out future or past development.
+It would be rude to strip them out, just as it would be for normal comments.
+.It Fl l
+Replace removed lines with blank lines
+instead of deleting them.
+.It Fl o Ar output
+The argument given is the name of an
+.Ar output
+file to be used instead of the standard output.
+This file can be the same as the input file.
+.It Fl s
+Instead of processing the input file as usual,
+this option causes
+.Nm
+to produce a list of symbols that appear in expressions
+that
+.Nm
+understands.
+It is useful in conjunction with the
+.Fl dM
+option of
+.Xr cpp 1
+for creating
+.Nm
+command lines.
+.It Fl t
+Disables parsing for C comments
+and line continuations,
+which is useful
+for plain text.
+.It Fl iD Ns Ar sym Ns Op = Ns Ar val
+.It Fl iU Ns Ar sym
+Ignore
+.Ic #ifdef Ns s .
+If your C code uses
+.Ic #ifdef Ns s
+to delimit non-C lines,
+such as comments
+or code which is under construction,
+then you must tell
+.Nm
+which symbols are used for that purpose so that it will not try to parse
+comments
+and line continuations
+inside those
+.Ic #ifdef Ns s .
+One specifies ignored symbols with
+.Fl iD Ns Ar sym Ns Oo = Ns Ar val Oc
+and
+.Fl iU Ns Ar sym
+similar to
+.Fl D Ns Ar sym Ns Op = Ns Ar val
+and
+.Fl U Ns Ar sym
+above.
+.It Fl I Ns Ar path
+Specifies to
+.Nm unifdefall
+an additional place to look for
+.Ic #include
+files.
+This option is ignored by
+.Nm
+for compatibility with
+.Xr cpp 1
+and to simplify the implementation of
+.Nm unifdefall .
+.El
+.Pp
+The
+.Nm
+utility copies its output to
+.Em stdout
+and will take its input from
+.Em stdin
+if no
+.Ar file
+argument is given.
+.Pp
+The
+.Nm
+utility works nicely with the
+.Fl D Ns Ar sym
+option of
+.Xr diff 1 .
+.Sh DIAGNOSTICS
+.Bl -item
+.It
+Too many levels of nesting.
+.It
+Inappropriate
+.Ic #elif ,
+.Ic #else
+or
+.Ic #endif .
+.It
+Obfuscated preprocessor control line.
+.It
+Premature
+.Dv EOF
+(with the line number of the most recent unterminated
+.Ic #if ) .
+.It
+.Dv EOF
+in comment.
+.El
+.Pp
+The
+.Nm
+utility exits 0 if the output is an exact copy of the input,
+1 if not, and 2 if in trouble.
+.Sh SEE ALSO
+.Xr cpp 1 ,
+.Xr diff 1
+.Sh HISTORY
+The
+.Nm
+command appeared in
+.Bx 4.3 .
+.Tn ANSI\~C
+support was added in
+.Fx 4.7 .
+.Sh BUGS
+Expression evaluation is very limited.
+.Pp
+Preprocessor control lines split across more than one physical line
+(because of comments or backslash-newline)
+cannot be handled in every situation.
+.Pp
+Trigraphs are not recognized.
+.Pp
+There is no support for symbols with different definitions at
+different points in the source file.
+.Pp
+The text-mode and ignore functionality does not correspond to modern
+.Xr cpp 1
+behaviour.
diff --git a/usr.bin/unifdef/unifdef.c b/usr.bin/unifdef/unifdef.c
new file mode 100644
index 0000000..702e3ec
--- /dev/null
+++ b/usr.bin/unifdef/unifdef.c
@@ -0,0 +1,1037 @@
+/* $NetBSD: unifdef.c,v 1.22 2012/10/13 18:26:03 christos Exp $ */
+
+/*
+ * Copyright (c) 1985, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Dave Yost. It was rewritten to support ANSI C by Tony Finch.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+/*
+ * Copyright (c) 2002, 2003 Tony Finch <dot@dotat.at>
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Dave Yost. It was rewritten to support ANSI C by Tony Finch.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. All advertising materials mentioning features or use of this software
+ * must display the following acknowledgement:
+ * This product includes software developed by the University of
+ * California, Berkeley and its contributors.
+ * 4. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+
+#ifndef lint
+#if 0
+static const char copyright[] =
+"@(#) Copyright (c) 1985, 1993\n\
+ The Regents of the University of California. All rights reserved.\n";
+#endif
+#ifdef __IDSTRING
+__IDSTRING(Berkeley, "@(#)unifdef.c 8.1 (Berkeley) 6/6/93");
+__IDSTRING(NetBSD, "$NetBSD: unifdef.c,v 1.22 2012/10/13 18:26:03 christos Exp $");
+__IDSTRING(dotat, "$dotat: things/unifdef.c,v 1.161 2003/07/01 15:32:48 fanf2 Exp $");
+#endif
+#endif /* not lint */
+#ifdef __FBSDID
+__FBSDID("$FreeBSD: src/usr.bin/unifdef/unifdef.c,v 1.18 2003/07/01 15:30:43 fanf Exp $");
+#endif
+
+/*
+ * unifdef - remove ifdef'ed lines
+ *
+ * Wishlist:
+ * provide an option which will append the name of the
+ * appropriate symbol after #else's and #endif's
+ * provide an option which will check symbols after
+ * #else's and #endif's to see that they match their
+ * corresponding #ifdef or #ifndef
+ * generate #line directives in place of deleted code
+ *
+ * The first two items above require better buffer handling, which would
+ * also make it possible to handle all "dodgy" directives correctly.
+ */
+
+#include <ctype.h>
+#include <err.h>
+#include <libgen.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include <sys/param.h>
+#include <sys/stat.h>
+
+#include "stdbool.h"
+
+/* types of input lines: */
+typedef enum {
+ LT_TRUEI, /* a true #if with ignore flag */
+ LT_FALSEI, /* a false #if with ignore flag */
+ LT_IF, /* an unknown #if */
+ LT_TRUE, /* a true #if */
+ LT_FALSE, /* a false #if */
+ LT_ELIF, /* an unknown #elif */
+ LT_ELTRUE, /* a true #elif */
+ LT_ELFALSE, /* a false #elif */
+ LT_ELSE, /* #else */
+ LT_ENDIF, /* #endif */
+ LT_DODGY, /* flag: directive is not on one line */
+ LT_DODGY_LAST = LT_DODGY + LT_ENDIF,
+ LT_PLAIN, /* ordinary line */
+ LT_EOF, /* end of file */
+ LT_COUNT
+} Linetype;
+
+static char const * const linetype_name[] = {
+ "TRUEI", "FALSEI", "IF", "TRUE", "FALSE",
+ "ELIF", "ELTRUE", "ELFALSE", "ELSE", "ENDIF",
+ "DODGY TRUEI", "DODGY FALSEI",
+ "DODGY IF", "DODGY TRUE", "DODGY FALSE",
+ "DODGY ELIF", "DODGY ELTRUE", "DODGY ELFALSE",
+ "DODGY ELSE", "DODGY ENDIF",
+ "PLAIN", "EOF"
+};
+
+/* state of #if processing */
+typedef enum {
+ IS_OUTSIDE,
+ IS_FALSE_PREFIX, /* false #if followed by false #elifs */
+ IS_TRUE_PREFIX, /* first non-false #(el)if is true */
+ IS_PASS_MIDDLE, /* first non-false #(el)if is unknown */
+ IS_FALSE_MIDDLE, /* a false #elif after a pass state */
+ IS_TRUE_MIDDLE, /* a true #elif after a pass state */
+ IS_PASS_ELSE, /* an else after a pass state */
+ IS_FALSE_ELSE, /* an else after a true state */
+ IS_TRUE_ELSE, /* an else after only false states */
+ IS_FALSE_TRAILER, /* #elifs after a true are false */
+ IS_COUNT
+} Ifstate;
+
+static char const * const ifstate_name[] = {
+ "OUTSIDE", "FALSE_PREFIX", "TRUE_PREFIX",
+ "PASS_MIDDLE", "FALSE_MIDDLE", "TRUE_MIDDLE",
+ "PASS_ELSE", "FALSE_ELSE", "TRUE_ELSE",
+ "FALSE_TRAILER"
+};
+
+/* state of comment parser */
+typedef enum {
+ NO_COMMENT = false, /* outside a comment */
+ C_COMMENT, /* in a comment like this one */
+ CXX_COMMENT, /* between // and end of line */
+ STARTING_COMMENT, /* just after slash-backslash-newline */
+ FINISHING_COMMENT /* star-backslash-newline in a C comment */
+} Comment_state;
+
+static char const * const comment_name[] = {
+ "NO", "C", "CXX", "STARTING", "FINISHING"
+};
+
+/* state of preprocessor line parser */
+typedef enum {
+ LS_START, /* only space and comments on this line */
+ LS_HASH, /* only space, comments, and a hash */
+ LS_DIRTY /* this line can't be a preprocessor line */
+} Line_state;
+
+static char const * const linestate_name[] = {
+ "START", "HASH", "DIRTY"
+};
+
+/*
+ * Minimum translation limits from ISO/IEC 9899:1999 5.2.4.1
+ */
+#define MAXDEPTH 64 /* maximum #if nesting */
+#define MAXLINE 4096 /* maximum length of line */
+#define MAXSYMS 4096 /* maximum number of symbols */
+
+/*
+ * Sometimes when editing a keyword the replacement text is longer, so
+ * we leave some space at the end of the tline buffer to accommodate this.
+ */
+#define EDITSLOP 10
+
+/*
+ * Globals.
+ */
+
+static bool complement; /* -c: do the complement */
+static bool debugging; /* -d: debugging reports */
+static bool iocccok; /* -e: fewer IOCCC errors */
+static bool killconsts; /* -k: eval constant #ifs */
+static bool lnblank; /* -l: blank deleted lines */
+static bool symlist; /* -s: output symbol list */
+static bool text; /* -t: this is a text file */
+
+static const char *symname[MAXSYMS]; /* symbol name */
+static const char *value[MAXSYMS]; /* -Dsym=value */
+static bool ignore[MAXSYMS]; /* -iDsym or -iUsym */
+static int nsyms; /* number of symbols */
+
+static FILE *input; /* input file pointer */
+static FILE *output; /* output file pointer */
+static const char *filename; /* input file name */
+static char *ofilename; /* output file name */
+static char tmpname[MAXPATHLEN]; /* used when overwriting */
+static int linenum; /* current line number */
+static int overwriting; /* output overwrites input */
+
+static char tline[MAXLINE+EDITSLOP];/* input buffer plus space */
+static char *keyword; /* used for editing #elif's */
+
+static Comment_state incomment; /* comment parser state */
+static Line_state linestate; /* #if line parser state */
+static Ifstate ifstate[MAXDEPTH]; /* #if processor state */
+static bool ignoring[MAXDEPTH]; /* ignore comments state */
+static int stifline[MAXDEPTH]; /* start of current #if */
+static int depth; /* current #if nesting */
+static bool keepthis; /* don't delete constant #if */
+
+static int exitstat; /* program exit status */
+
+static void addsym(bool, bool, char *);
+static void debug(const char *, ...) __printflike(1, 2);
+__dead static void done(void);
+__dead static void error(const char *);
+static int findsym(const char *);
+static void flushline(bool);
+static Linetype get_line(void);
+static Linetype ifeval(const char **);
+static void ignoreoff(void);
+static void ignoreon(void);
+static void keywordedit(const char *);
+static void nest(void);
+__dead static void process(void);
+static const char *skipcomment(const char *);
+static const char *skipsym(const char *);
+static void state(Ifstate);
+static int strlcmp(const char *, const char *, size_t);
+__dead static void usage(void);
+
+#define endsym(c) (!isalpha((unsigned char)c) && !isdigit((unsigned char)c) && c != '_')
+
+/*
+ * The main program.
+ */
+int
+main(int argc, char *argv[])
+{
+ int opt;
+ struct stat isb, osb;
+
+ while ((opt = getopt(argc, argv, "i:D:U:I:o:cdeklst")) != -1)
+ switch (opt) {
+ case 'i': /* treat stuff controlled by these symbols as text */
+ /*
+ * For strict backwards-compatibility the U or D
+ * should be immediately after the -i but it doesn't
+ * matter much if we relax that requirement.
+ */
+ opt = *optarg++;
+ if (opt == 'D')
+ addsym(true, true, optarg);
+ else if (opt == 'U')
+ addsym(true, false, optarg);
+ else
+ usage();
+ break;
+ case 'D': /* define a symbol */
+ addsym(false, true, optarg);
+ break;
+ case 'U': /* undef a symbol */
+ addsym(false, false, optarg);
+ break;
+ case 'I':
+ /* no-op for compatibility with cpp */
+ break;
+ case 'c': /* treat -D as -U and vice versa */
+ complement = true;
+ break;
+ case 'd':
+ debugging = true;
+ break;
+ case 'e': /* fewer errors from dodgy lines */
+ iocccok = true;
+ break;
+ case 'k': /* process constant #ifs */
+ killconsts = true;
+ break;
+ case 'l': /* blank deleted lines instead of omitting them */
+ lnblank = true;
+ break;
+ case 'o': /* output to a file */
+ ofilename = optarg;
+ break;
+ case 's': /* only output list of symbols that control #ifs */
+ symlist = true;
+ break;
+ case 't': /* don't parse C comments */
+ text = true;
+ break;
+ default:
+ usage();
+ }
+ argc -= optind;
+ argv += optind;
+ if (nsyms == 0 && !symlist) {
+ warnx("must -D or -U at least one symbol");
+ usage();
+ }
+ if (argc > 1) {
+ errx(2, "can only do one file");
+ } else if (argc == 1 && strcmp(*argv, "-") != 0) {
+ filename = *argv;
+ input = fopen(filename, "r");
+ if (input == NULL)
+ err(2, "can't open %s", filename);
+ } else {
+ filename = "[stdin]";
+ input = stdin;
+ }
+ if (ofilename == NULL) {
+ output = stdout;
+ } else {
+ if (stat(ofilename, &osb) == 0) {
+ if (fstat(fileno(input), &isb) != 0)
+ err(2, "can't fstat %s", filename);
+
+ overwriting = (osb.st_dev == isb.st_dev &&
+ osb.st_ino == isb.st_ino);
+ }
+ if (overwriting) {
+ int ofd;
+
+ snprintf(tmpname, sizeof(tmpname), "%s/unifdef.XXXXXX",
+ dirname(ofilename));
+ if ((ofd = mkstemp(tmpname)) != -1)
+ output = fdopen(ofd, "w+");
+ if (output == NULL)
+ err(2, "can't create temporary file");
+ fchmod(ofd, isb.st_mode & ACCESSPERMS);
+ } else {
+ output = fopen(ofilename, "w");
+ if (output == NULL)
+ err(2, "can't open %s", ofilename);
+ }
+ }
+ process();
+ abort(); /* bug */
+}
+
+static void
+usage(void)
+{
+ fprintf(stderr, "usage: unifdef [-cdeklst] [-o output]"
+ " [-Dsym[=val]] [-Usym] [-iDsym[=val]] [-iUsym] ... [file]\n");
+ exit(2);
+}
+
+/*
+ * A state transition function alters the global #if processing state
+ * in a particular way. The table below is indexed by the current
+ * processing state and the type of the current line.
+ *
+ * Nesting is handled by keeping a stack of states; some transition
+ * functions increase or decrease the depth. They also maintain the
+ * ignore state on a stack. In some complicated cases they have to
+ * alter the preprocessor directive, as follows.
+ *
+ * When we have processed a group that starts off with a known-false
+ * #if/#elif sequence (which has therefore been deleted) followed by a
+ * #elif that we don't understand and therefore must keep, we edit the
+ * latter into a #if to keep the nesting correct.
+ *
+ * When we find a true #elif in a group, the following block will
+ * always be kept and the rest of the sequence after the next #elif or
+ * #else will be discarded. We edit the #elif into a #else and the
+ * following directive to #endif since this has the desired behaviour.
+ *
+ * "Dodgy" directives are split across multiple lines, the most common
+ * example being a multi-line comment hanging off the right of the
+ * directive. We can handle them correctly only if there is no change
+ * from printing to dropping (or vice versa) caused by that directive.
+ * If the directive is the first of a group we have a choice between
+ * failing with an error, or passing it through unchanged instead of
+ * evaluating it. The latter is not the default to avoid questions from
+ * users about unifdef unexpectedly leaving behind preprocessor directives.
+ */
+typedef void state_fn(void);
+
+/* report an error */
+__dead static void Eelif (void) { error("Inappropriate #elif"); }
+__dead static void Eelse (void) { error("Inappropriate #else"); }
+__dead static void Eendif(void) { error("Inappropriate #endif"); }
+__dead static void Eeof (void) { error("Premature EOF"); }
+__dead static void Eioccc(void) { error("Obfuscated preprocessor control line"); }
+/* plain line handling */
+static void print (void) { flushline(true); }
+static void drop (void) { flushline(false); }
+/* output lacks group's start line */
+static void Strue (void) { drop(); ignoreoff(); state(IS_TRUE_PREFIX); }
+static void Sfalse(void) { drop(); ignoreoff(); state(IS_FALSE_PREFIX); }
+static void Selse (void) { drop(); state(IS_TRUE_ELSE); }
+/* print/pass this block */
+static void Pelif (void) { print(); ignoreoff(); state(IS_PASS_MIDDLE); }
+static void Pelse (void) { print(); state(IS_PASS_ELSE); }
+static void Pendif(void) { print(); --depth; }
+/* discard this block */
+static void Dfalse(void) { drop(); ignoreoff(); state(IS_FALSE_TRAILER); }
+static void Delif (void) { drop(); ignoreoff(); state(IS_FALSE_MIDDLE); }
+static void Delse (void) { drop(); state(IS_FALSE_ELSE); }
+static void Dendif(void) { drop(); --depth; }
+/* first line of group */
+static void Fdrop (void) { nest(); Dfalse(); }
+static void Fpass (void) { nest(); Pelif(); }
+static void Ftrue (void) { nest(); Strue(); }
+static void Ffalse(void) { nest(); Sfalse(); }
+/* variable pedantry for obfuscated lines */
+static void Oiffy (void) { if (iocccok) Fpass(); else Eioccc(); ignoreon(); }
+static void Oif (void) { if (iocccok) Fpass(); else Eioccc(); }
+static void Oelif (void) { if (iocccok) Pelif(); else Eioccc(); }
+/* ignore comments in this block */
+static void Idrop (void) { Fdrop(); ignoreon(); }
+static void Itrue (void) { Ftrue(); ignoreon(); }
+static void Ifalse(void) { Ffalse(); ignoreon(); }
+/* edit this line */
+static void Mpass (void) { strncpy(keyword, "if ", 4); Pelif(); }
+static void Mtrue (void) { keywordedit("else\n"); state(IS_TRUE_MIDDLE); }
+static void Melif (void) { keywordedit("endif\n"); state(IS_FALSE_TRAILER); }
+static void Melse (void) { keywordedit("endif\n"); state(IS_FALSE_ELSE); }
+
+static state_fn * const trans_table[IS_COUNT][LT_COUNT] = {
+/* IS_OUTSIDE */
+{ Itrue, Ifalse,Fpass, Ftrue, Ffalse,Eelif, Eelif, Eelif, Eelse, Eendif,
+ Oiffy, Oiffy, Fpass, Oif, Oif, Eelif, Eelif, Eelif, Eelse, Eendif,
+ print, done },
+/* IS_FALSE_PREFIX */
+{ Idrop, Idrop, Fdrop, Fdrop, Fdrop, Mpass, Strue, Sfalse,Selse, Dendif,
+ Idrop, Idrop, Fdrop, Fdrop, Fdrop, Mpass, Eioccc,Eioccc,Eioccc,Eioccc,
+ drop, Eeof },
+/* IS_TRUE_PREFIX */
+{ Itrue, Ifalse,Fpass, Ftrue, Ffalse,Dfalse,Dfalse,Dfalse,Delse, Dendif,
+ Oiffy, Oiffy, Fpass, Oif, Oif, Eioccc,Eioccc,Eioccc,Eioccc,Eioccc,
+ print, Eeof },
+/* IS_PASS_MIDDLE */
+{ Itrue, Ifalse,Fpass, Ftrue, Ffalse,Pelif, Mtrue, Delif, Pelse, Pendif,
+ Oiffy, Oiffy, Fpass, Oif, Oif, Pelif, Oelif, Oelif, Pelse, Pendif,
+ print, Eeof },
+/* IS_FALSE_MIDDLE */
+{ Idrop, Idrop, Fdrop, Fdrop, Fdrop, Pelif, Mtrue, Delif, Pelse, Pendif,
+ Idrop, Idrop, Fdrop, Fdrop, Fdrop, Eioccc,Eioccc,Eioccc,Eioccc,Eioccc,
+ drop, Eeof },
+/* IS_TRUE_MIDDLE */
+{ Itrue, Ifalse,Fpass, Ftrue, Ffalse,Melif, Melif, Melif, Melse, Pendif,
+ Oiffy, Oiffy, Fpass, Oif, Oif, Eioccc,Eioccc,Eioccc,Eioccc,Pendif,
+ print, Eeof },
+/* IS_PASS_ELSE */
+{ Itrue, Ifalse,Fpass, Ftrue, Ffalse,Eelif, Eelif, Eelif, Eelse, Pendif,
+ Oiffy, Oiffy, Fpass, Oif, Oif, Eelif, Eelif, Eelif, Eelse, Pendif,
+ print, Eeof },
+/* IS_FALSE_ELSE */
+{ Idrop, Idrop, Fdrop, Fdrop, Fdrop, Eelif, Eelif, Eelif, Eelse, Dendif,
+ Idrop, Idrop, Fdrop, Fdrop, Fdrop, Eelif, Eelif, Eelif, Eelse, Eioccc,
+ drop, Eeof },
+/* IS_TRUE_ELSE */
+{ Itrue, Ifalse,Fpass, Ftrue, Ffalse,Eelif, Eelif, Eelif, Eelse, Dendif,
+ Oiffy, Oiffy, Fpass, Oif, Oif, Eelif, Eelif, Eelif, Eelse, Eioccc,
+ print, Eeof },
+/* IS_FALSE_TRAILER */
+{ Idrop, Idrop, Fdrop, Fdrop, Fdrop, Dfalse,Dfalse,Dfalse,Delse, Dendif,
+ Idrop, Idrop, Fdrop, Fdrop, Fdrop, Dfalse,Dfalse,Dfalse,Delse, Eioccc,
+ drop, Eeof }
+/*TRUEI FALSEI IF TRUE FALSE ELIF ELTRUE ELFALSE ELSE ENDIF
+ TRUEI FALSEI IF TRUE FALSE ELIF ELTRUE ELFALSE ELSE ENDIF (DODGY)
+ PLAIN EOF */
+};
+
+/*
+ * State machine utility functions
+ */
+static void
+done(void)
+{
+ if (incomment)
+ error("EOF in comment");
+ if (fclose(output)) {
+ if (overwriting) {
+ unlink(tmpname);
+ errx(2, "%s unchanged", ofilename);
+ }
+ }
+ if (overwriting && rename(tmpname, ofilename)) {
+ unlink(tmpname);
+ errx(2, "%s unchanged", ofilename);
+ }
+ exit(exitstat);
+}
+static void
+ignoreoff(void)
+{
+ ignoring[depth] = ignoring[depth-1];
+}
+static void
+ignoreon(void)
+{
+ ignoring[depth] = true;
+}
+static void
+keywordedit(const char *replacement)
+{
+ strlcpy(keyword, replacement, tline + sizeof(tline) - keyword);
+ print();
+}
+static void
+nest(void)
+{
+ depth += 1;
+ if (depth >= MAXDEPTH)
+ error("Too many levels of nesting");
+ stifline[depth] = linenum;
+}
+static void
+state(Ifstate is)
+{
+ ifstate[depth] = is;
+}
+
+/*
+ * Write a line to the output or not, according to command line options.
+ */
+static void
+flushline(bool keep)
+{
+ if (symlist)
+ return;
+ if (keep ^ complement)
+ fputs(tline, output);
+ else {
+ if (lnblank)
+ putc('\n', output);
+ exitstat = 1;
+ }
+}
+
+/*
+ * The driver for the state machine.
+ */
+static void
+process(void)
+{
+ Linetype lineval;
+
+ for (;;) {
+ linenum++;
+ lineval = get_line();
+ trans_table[ifstate[depth]][lineval]();
+ debug("process %s -> %s depth %d",
+ linetype_name[lineval],
+ ifstate_name[ifstate[depth]], depth);
+ }
+}
+
+/*
+ * Parse a line and determine its type. We keep the preprocessor line
+ * parser state between calls in the global variable linestate, with
+ * help from skipcomment().
+ */
+static Linetype
+get_line(void)
+{
+ const char *cp;
+ int cursym;
+ int kwlen;
+ Linetype retval;
+ Comment_state wascomment;
+
+ if (fgets(tline, MAXLINE, input) == NULL)
+ return (LT_EOF);
+ retval = LT_PLAIN;
+ wascomment = incomment;
+ cp = skipcomment(tline);
+ if (linestate == LS_START) {
+ if (*cp == '#') {
+ linestate = LS_HASH;
+ cp = skipcomment(cp + 1);
+ } else if (*cp != '\0')
+ linestate = LS_DIRTY;
+ }
+ if (!incomment && linestate == LS_HASH) {
+ keyword = tline + (cp - tline);
+ cp = skipsym(cp);
+ kwlen = cp - keyword;
+ /* no way can we deal with a continuation inside a keyword */
+ if (strncmp(cp, "\\\n", 2) == 0)
+ Eioccc();
+ if (strlcmp("ifdef", keyword, kwlen) == 0 ||
+ strlcmp("ifndef", keyword, kwlen) == 0) {
+ cp = skipcomment(cp);
+ if ((cursym = findsym(cp)) < 0)
+ retval = LT_IF;
+ else {
+ retval = (keyword[2] == 'n')
+ ? LT_FALSE : LT_TRUE;
+ if (value[cursym] == NULL)
+ retval = (retval == LT_TRUE)
+ ? LT_FALSE : LT_TRUE;
+ if (ignore[cursym])
+ retval = (retval == LT_TRUE)
+ ? LT_TRUEI : LT_FALSEI;
+ }
+ cp = skipsym(cp);
+ } else if (strlcmp("if", keyword, kwlen) == 0)
+ retval = ifeval(&cp);
+ else if (strlcmp("elif", keyword, kwlen) == 0)
+ retval = ifeval(&cp) - LT_IF + LT_ELIF;
+ else if (strlcmp("else", keyword, kwlen) == 0)
+ retval = LT_ELSE;
+ else if (strlcmp("endif", keyword, kwlen) == 0)
+ retval = LT_ENDIF;
+ else {
+ linestate = LS_DIRTY;
+ retval = LT_PLAIN;
+ }
+ cp = skipcomment(cp);
+ if (*cp != '\0') {
+ linestate = LS_DIRTY;
+ if (retval == LT_TRUE || retval == LT_FALSE ||
+ retval == LT_TRUEI || retval == LT_FALSEI)
+ retval = LT_IF;
+ if (retval == LT_ELTRUE || retval == LT_ELFALSE)
+ retval = LT_ELIF;
+ }
+ if (retval != LT_PLAIN && (wascomment || incomment)) {
+ retval += LT_DODGY;
+ if (incomment)
+ linestate = LS_DIRTY;
+ }
+ }
+ if (linestate == LS_DIRTY) {
+ while (*cp != '\0')
+ cp = skipcomment(cp + 1);
+ }
+ debug("parser %s comment %s line",
+ comment_name[incomment], linestate_name[linestate]);
+ return (retval);
+}
+
+/*
+ * These are the binary operators that are supported by the expression
+ * evaluator. Note that if support for division is added then we also
+ * need short-circuiting booleans because of divide-by-zero.
+ */
+static int op_lt(int a, int b) { return (a < b); }
+static int op_gt(int a, int b) { return (a > b); }
+static int op_le(int a, int b) { return (a <= b); }
+static int op_ge(int a, int b) { return (a >= b); }
+static int op_eq(int a, int b) { return (a == b); }
+static int op_ne(int a, int b) { return (a != b); }
+static int op_or(int a, int b) { return (a || b); }
+static int op_and(int a, int b) { return (a && b); }
+
+/*
+ * An evaluation function takes three arguments, as follows: (1) a pointer to
+ * an element of the precedence table which lists the operators at the current
+ * level of precedence; (2) a pointer to an integer which will receive the
+ * value of the expression; and (3) a pointer to a char* that points to the
+ * expression to be evaluated and that is updated to the end of the expression
+ * when evaluation is complete. The function returns LT_FALSE if the value of
+ * the expression is zero, LT_TRUE if it is non-zero, or LT_IF if the
+ * expression could not be evaluated.
+ */
+struct ops;
+
+typedef Linetype eval_fn(const struct ops *, int *, const char **);
+
+static eval_fn eval_table, eval_unary;
+
+/*
+ * The precedence table. Expressions involving binary operators are evaluated
+ * in a table-driven way by eval_table. When it evaluates a subexpression it
+ * calls the inner function with its first argument pointing to the next
+ * element of the table. Innermost expressions have special non-table-driven
+ * handling.
+ */
+static const struct ops {
+ eval_fn *inner;
+ struct op {
+ const char *str;
+ int (*fn)(int, int);
+ } op[5];
+} eval_ops[] = {
+ { eval_table, { { "||", op_or } } },
+ { eval_table, { { "&&", op_and } } },
+ { eval_table, { { "==", op_eq },
+ { "!=", op_ne } } },
+ { eval_unary, { { "<=", op_le },
+ { ">=", op_ge },
+ { "<", op_lt },
+ { ">", op_gt } } }
+};
+
+/*
+ * Function for evaluating the innermost parts of expressions,
+ * viz. !expr (expr) defined(symbol) symbol number
+ * We reset the keepthis flag when we find a non-constant subexpression.
+ */
+static Linetype
+eval_unary(const struct ops *ops, int *valp, const char **cpp)
+{
+ const char *cp;
+ char *ep;
+ int sym;
+
+ cp = skipcomment(*cpp);
+ if (*cp == '!') {
+ debug("eval%td !", ops - eval_ops);
+ cp++;
+ if (eval_unary(ops, valp, &cp) == LT_IF)
+ return (LT_IF);
+ *valp = !*valp;
+ } else if (*cp == '(') {
+ cp++;
+ debug("eval%td (", ops - eval_ops);
+ if (eval_table(eval_ops, valp, &cp) == LT_IF)
+ return (LT_IF);
+ cp = skipcomment(cp);
+ if (*cp++ != ')')
+ return (LT_IF);
+ } else if (isdigit((unsigned char)*cp)) {
+ debug("eval%td number", ops - eval_ops);
+ *valp = strtol(cp, &ep, 0);
+ cp = skipsym(cp);
+ } else if (strncmp(cp, "defined", 7) == 0 && endsym(cp[7])) {
+ cp = skipcomment(cp+7);
+ debug("eval%td defined", ops - eval_ops);
+ if (*cp++ != '(')
+ return (LT_IF);
+ cp = skipcomment(cp);
+ sym = findsym(cp);
+ if (sym < 0 || symlist)
+ return (LT_IF);
+ *valp = (value[sym] != NULL);
+ cp = skipsym(cp);
+ cp = skipcomment(cp);
+ if (*cp++ != ')')
+ return (LT_IF);
+ keepthis = false;
+ } else if (!endsym(*cp)) {
+ debug("eval%td symbol", ops - eval_ops);
+ sym = findsym(cp);
+ if (sym < 0 || symlist)
+ return (LT_IF);
+ if (value[sym] == NULL)
+ *valp = 0;
+ else {
+ *valp = strtol(value[sym], &ep, 0);
+ if (*ep != '\0' || ep == value[sym])
+ return (LT_IF);
+ }
+ cp = skipsym(cp);
+ keepthis = false;
+ } else {
+ debug("eval%td bad expr", ops - eval_ops);
+ return (LT_IF);
+ }
+
+ *cpp = cp;
+ debug("eval%td = %d", ops - eval_ops, *valp);
+ return (*valp ? LT_TRUE : LT_FALSE);
+}
+
+/*
+ * Table-driven evaluation of binary operators.
+ */
+static Linetype
+eval_table(const struct ops *ops, int *valp, const char **cpp)
+{
+ const struct op *op;
+ const char *cp;
+ int val;
+
+ debug("eval%td", ops - eval_ops);
+ cp = *cpp;
+ if (ops->inner(ops+1, valp, &cp) == LT_IF)
+ return (LT_IF);
+ for (;;) {
+ cp = skipcomment(cp);
+ for (op = ops->op; op->str != NULL; op++)
+ if (strncmp(cp, op->str, strlen(op->str)) == 0)
+ break;
+ if (op->str == NULL)
+ break;
+ cp += strlen(op->str);
+ debug("eval%td %s", ops - eval_ops, op->str);
+ if (ops->inner(ops+1, &val, &cp) == LT_IF)
+ return (LT_IF);
+ *valp = op->fn(*valp, val);
+ }
+
+ *cpp = cp;
+ debug("eval%td = %d", ops - eval_ops, *valp);
+ return (*valp ? LT_TRUE : LT_FALSE);
+}
+
+/*
+ * Evaluate the expression on a #if or #elif line. If we can work out
+ * the result we return LT_TRUE or LT_FALSE accordingly, otherwise we
+ * return just a generic LT_IF.
+ */
+static Linetype
+ifeval(const char **cpp)
+{
+ int ret;
+ int val;
+
+ debug("eval %s", *cpp);
+ keepthis = killconsts ? false : true;
+ ret = eval_table(eval_ops, &val, cpp);
+ debug("eval = %d", val);
+ return (keepthis ? LT_IF : ret);
+}
+
+/*
+ * Skip over comments and stop at the next character position that is
+ * not whitespace. Between calls we keep the comment state in the
+ * global variable incomment, and we also adjust the global variable
+ * linestate when we see a newline.
+ * XXX: doesn't cope with the buffer splitting inside a state transition.
+ */
+static const char *
+skipcomment(const char *cp)
+{
+ if (text || ignoring[depth]) {
+ for (; isspace((unsigned char)*cp); cp++)
+ if (*cp == '\n')
+ linestate = LS_START;
+ return (cp);
+ }
+ while (*cp != '\0')
+ /* don't reset to LS_START after a line continuation */
+ if (strncmp(cp, "\\\n", 2) == 0)
+ cp += 2;
+ else switch (incomment) {
+ case NO_COMMENT:
+ if (strncmp(cp, "/\\\n", 3) == 0) {
+ incomment = STARTING_COMMENT;
+ cp += 3;
+ } else if (strncmp(cp, "/*", 2) == 0) {
+ incomment = C_COMMENT;
+ cp += 2;
+ } else if (strncmp(cp, "//", 2) == 0) {
+ incomment = CXX_COMMENT;
+ cp += 2;
+ } else if (strncmp(cp, "\n", 1) == 0) {
+ linestate = LS_START;
+ cp += 1;
+ } else if (strchr(" \t", *cp) != NULL) {
+ cp += 1;
+ } else
+ return (cp);
+ continue;
+ case CXX_COMMENT:
+ if (strncmp(cp, "\n", 1) == 0) {
+ incomment = NO_COMMENT;
+ linestate = LS_START;
+ }
+ cp += 1;
+ continue;
+ case C_COMMENT:
+ if (strncmp(cp, "*\\\n", 3) == 0) {
+ incomment = FINISHING_COMMENT;
+ cp += 3;
+ } else if (strncmp(cp, "*/", 2) == 0) {
+ incomment = NO_COMMENT;
+ cp += 2;
+ } else
+ cp += 1;
+ continue;
+ case STARTING_COMMENT:
+ if (*cp == '*') {
+ incomment = C_COMMENT;
+ cp += 1;
+ } else if (*cp == '/') {
+ incomment = CXX_COMMENT;
+ cp += 1;
+ } else {
+ incomment = NO_COMMENT;
+ linestate = LS_DIRTY;
+ }
+ continue;
+ case FINISHING_COMMENT:
+ if (*cp == '/') {
+ incomment = NO_COMMENT;
+ cp += 1;
+ } else
+ incomment = C_COMMENT;
+ continue;
+ default:
+ abort(); /* bug */
+ }
+ return (cp);
+}
+
+/*
+ * Skip over an identifier.
+ */
+static const char *
+skipsym(const char *cp)
+{
+ while (!endsym(*cp))
+ ++cp;
+ return (cp);
+}
+
+/*
+ * Look for the symbol in the symbol table. If it is found, we return
+ * the symbol table index, else we return -1.
+ */
+static int
+findsym(const char *str)
+{
+ const char *cp;
+ int symind;
+
+ cp = skipsym(str);
+ if (cp == str)
+ return (-1);
+ if (symlist)
+ printf("%.*s\n", (int)(cp-str), str);
+ for (symind = 0; symind < nsyms; ++symind) {
+ if (strlcmp(symname[symind], str, cp-str) == 0) {
+ debug("findsym %s %s", symname[symind],
+ value[symind] ? value[symind] : "");
+ return (symind);
+ }
+ }
+ return (-1);
+}
+
+/*
+ * Add a symbol to the symbol table.
+ */
+static void
+addsym(bool ignorethis, bool definethis, char *sym)
+{
+ int symind;
+ char *val;
+
+ symind = findsym(sym);
+ if (symind < 0) {
+ if (nsyms >= MAXSYMS)
+ errx(2, "too many symbols");
+ symind = nsyms++;
+ }
+ symname[symind] = sym;
+ ignore[symind] = ignorethis;
+ val = sym + (skipsym(sym) - sym);
+ if (definethis) {
+ if (*val == '=') {
+ value[symind] = val+1;
+ *val = '\0';
+ } else if (*val == '\0')
+ value[symind] = "";
+ else
+ usage();
+ } else {
+ if (*val != '\0')
+ usage();
+ value[symind] = NULL;
+ }
+}
+
+/*
+ * Compare s with n characters of t.
+ * The same as strncmp() except that it checks that s[n] == '\0'.
+ */
+static int
+strlcmp(const char *s, const char *t, size_t n)
+{
+ while (n-- && *t != '\0')
+ if (*s != *t)
+ return ((unsigned char)*s - (unsigned char)*t);
+ else
+ ++s, ++t;
+ return ((unsigned char)*s);
+}
+
+/*
+ * Diagnostics.
+ */
+static void
+debug(const char *msg, ...)
+{
+ va_list ap;
+
+ if (debugging) {
+ va_start(ap, msg);
+ vwarnx(msg, ap);
+ va_end(ap);
+ }
+}
+
+static void
+error(const char *msg)
+{
+ if (depth == 0)
+ warnx("%s: %d: %s", filename, linenum, msg);
+ else
+ warnx("%s: %d: %s (#if line %d depth %d)",
+ filename, linenum, msg, stifline[depth], depth);
+ fclose(output);
+ if (overwriting) {
+ unlink(tmpname);
+ errx(2, "%s unchanged", ofilename);
+ }
+ errx(2, "output may be truncated");
+}
diff --git a/usr.bin/unifdef/unifdefall.sh b/usr.bin/unifdef/unifdefall.sh
new file mode 100644
index 0000000..7c1d643
--- /dev/null
+++ b/usr.bin/unifdef/unifdefall.sh
@@ -0,0 +1,29 @@
+#!/bin/sh
+#
+# remove all the #if's from a source file
+#
+# $dotat: things/unifdefall.sh,v 1.10 2002/09/24 19:52:11 fanf2 Exp $
+# $FreeBSD: src/usr.bin/unifdef/unifdefall.sh,v 1.2 2002/09/24 19:50:03 fanf Exp $
+
+set -e
+
+basename=`basename $0`
+tmp=`mktemp -d -t $basename` || exit 2
+
+unifdef -s "$@" | sort | uniq > $tmp/ctrl
+cpp -dM "$@" | sort |
+ sed -Ee 's/^#define[ ]+(.*[^ ])[ ]*$/\1/' > $tmp/hashdefs
+sed -Ee 's/^([A-Za-z0-9_]+).*$/\1/' $tmp/hashdefs > $tmp/alldef
+comm -23 $tmp/ctrl $tmp/alldef > $tmp/undef
+comm -12 $tmp/ctrl $tmp/alldef > $tmp/def
+
+echo unifdef -k \\ > $tmp/cmd
+sed -Ee 's/^(.*)$/-U\1 \\/' $tmp/undef >> $tmp/cmd
+while read sym
+do sed -Ee '/^('"$sym"')([(][^)]*[)])?([ ]+(.*))?$/!d;s//-D\1=\4/' $tmp/hashdefs
+done < $tmp/def |
+ sed -Ee 's/\\/\\\\/g;s/"/\\"/g;s/^/"/;s/$/" \\/' >> $tmp/cmd
+echo '"$@"' >> $tmp/cmd
+sh $tmp/cmd "$@"
+
+rm -r $tmp
diff --git a/usr.bin/uniq/uniq.1 b/usr.bin/uniq/uniq.1
new file mode 100644
index 0000000..bf1a78c
--- /dev/null
+++ b/usr.bin/uniq/uniq.1
@@ -0,0 +1,130 @@
+.\" $NetBSD: uniq.1,v 1.13 2012/08/26 14:13:50 wiz Exp $
+.\"
+.\" Copyright (c) 1991, 1993
+.\" The Regents of the University of California. All rights reserved.
+.\"
+.\" This code is derived from software contributed to Berkeley by
+.\" the Institute of Electrical and Electronics Engineers, Inc.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\" notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must reproduce the above copyright
+.\" notice, this list of conditions and the following disclaimer in the
+.\" documentation and/or other materials provided with the distribution.
+.\" 3. Neither the name of the University nor the names of its contributors
+.\" may be used to endorse or promote products derived from this software
+.\" without specific prior written permission.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+.\" ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+.\" SUCH DAMAGE.
+.\"
+.\" @(#)uniq.1 8.1 (Berkeley) 6/6/93
+.\"
+.Dd August 26, 2012
+.Dt UNIQ 1
+.Os
+.Sh NAME
+.Nm uniq
+.Nd report or filter out repeated lines in a file
+.Sh SYNOPSIS
+.Nm
+.Op Fl cdu
+.Op Fl f Ar fields
+.Op Fl s Ar chars
+.Oo
+.Ar input_file
+.Op Ar output_file
+.Oc
+.Sh DESCRIPTION
+The
+.Nm
+utility reads the standard input comparing adjacent lines, and writes
+a copy of each unique input line to the standard output.
+The second and succeeding copies of identical adjacent input lines are
+not written.
+Repeated lines in the input will not be detected if they are not adjacent,
+so it may be necessary to sort the files first.
+.Pp
+The following options are available:
+.Bl -tag -width Ds
+.It Fl c
+Precede each output line with the count of the number of times the line
+occurred in the input, followed by a single space.
+.It Fl d
+Don't output lines that are not repeated in the input.
+.It Fl f Ar fields
+Ignore the first
+.Ar fields
+in each input line when doing comparisons.
+A field is a string of non-blank characters separated from adjacent fields
+by blanks.
+Field numbers are one based, i.e., the first field is field one.
+.It Fl s Ar chars
+Ignore the first
+.Ar chars
+characters in each input line when doing comparisons.
+If specified in conjunction with the
+.Fl f
+option, the first
+.Ar chars
+characters after the first
+.Ar fields
+fields will be ignored.
+Character numbers are one based, i.e., the first character is character one.
+.It Fl u
+Don't output lines that are repeated in the input.
+.\".It Fl Ns Ar n
+.\"(Deprecated; replaced by
+.\".Fl f ) .
+.\"Ignore the first n
+.\"fields on each input line when doing comparisons,
+.\"where n is a number.
+.\"A field is a string of non-blank
+.\"characters separated from adjacent fields
+.\"by blanks.
+.\".It Cm \&\(pl Ns Ar n
+.\"(Deprecated; replaced by
+.\".Fl s ) .
+.\"Ignore the first
+.\".Ar m
+.\"characters when doing comparisons, where
+.\".Ar m
+.\"is a
+.\"number.
+.El
+.Pp
+Additional arguments
+.Ar input_file
+and
+.Ar output_file
+may be specified on the command line,
+where the former is then used as the name of an input file,
+and the latter as the name of an output file.
+.Sh EXIT STATUS
+.Ex -std
+.Sh COMPATIBILITY
+The historic
+.Cm \&\(pl Ns Ar number
+and
+.Fl Ns Ar number
+options have been deprecated but are still supported in this implementation.
+.Sh SEE ALSO
+.Xr sort 1
+.Sh STANDARDS
+The
+.Nm
+utility is expected to be
+.St -p1003.2
+compatible.
diff --git a/usr.bin/uniq/uniq.c b/usr.bin/uniq/uniq.c
new file mode 100644
index 0000000..ded7037
--- /dev/null
+++ b/usr.bin/uniq/uniq.c
@@ -0,0 +1,266 @@
+/* $NetBSD: uniq.c,v 1.20 2016/10/16 06:17:51 abhinav Exp $ */
+
+/*
+ * Copyright (c) 1989, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Case Larsen.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+#ifndef lint
+__COPYRIGHT("@(#) Copyright (c) 1989, 1993\
+ The Regents of the University of California. All rights reserved.");
+#endif /* not lint */
+
+#ifndef lint
+#if 0
+static char sccsid[] = "@(#)uniq.c 8.3 (Berkeley) 5/4/95";
+#endif
+__RCSID("$NetBSD: uniq.c,v 1.20 2016/10/16 06:17:51 abhinav Exp $");
+#endif /* not lint */
+
+#include <err.h>
+#include <errno.h>
+#include <stdio.h>
+#include <ctype.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+static int cflag, dflag, uflag;
+static int numchars, numfields, repeats;
+
+static FILE *file(const char *, const char *);
+static void show(FILE *, const char *);
+static const char *skip(const char *, size_t *);
+static void obsolete(char *[]);
+static void usage(void) __dead;
+
+int
+main (int argc, char *argv[])
+{
+ const char *prevp, *thisp;
+ FILE *ifp, *ofp;
+ int ch;
+ char *prevline, *thisline, *p;
+ size_t prevlinesize, thislinesize, psize;
+ size_t prevlen, thislen;
+
+ setprogname(argv[0]);
+ ifp = ofp = NULL;
+ obsolete(argv);
+ while ((ch = getopt(argc, argv, "-cdf:s:u")) != -1)
+ switch (ch) {
+ case '-':
+ --optind;
+ goto done;
+ case 'c':
+ cflag = 1;
+ break;
+ case 'd':
+ dflag = 1;
+ break;
+ case 'f':
+ numfields = strtol(optarg, &p, 10);
+ if (numfields < 0 || *p)
+ errx(1, "illegal field skip value: %s", optarg);
+ break;
+ case 's':
+ numchars = strtol(optarg, &p, 10);
+ if (numchars < 0 || *p)
+ errx(1, "illegal character skip value: %s",
+ optarg);
+ break;
+ case 'u':
+ uflag = 1;
+ break;
+ case '?':
+ default:
+ usage();
+ }
+
+done: argc -= optind;
+ argv +=optind;
+
+ switch(argc) {
+ case 0:
+ ifp = stdin;
+ ofp = stdout;
+ break;
+ case 1:
+ ifp = file(argv[0], "r");
+ ofp = stdout;
+ break;
+ case 2:
+ ifp = file(argv[0], "r");
+ ofp = file(argv[1], "w");
+ break;
+ default:
+ usage();
+ }
+
+ if ((p = fgetln(ifp, &psize)) == NULL)
+ return 0;
+ prevlinesize = prevlen = psize;
+ if ((prevline = malloc(prevlinesize + 1)) == NULL)
+ err(1, "malloc");
+ (void)memcpy(prevline, p, prevlinesize);
+ prevline[prevlinesize] = '\0';
+
+ if (numfields || numchars)
+ prevp = skip(prevline, &prevlen);
+ else
+ prevp = prevline;
+
+ thislinesize = psize;
+ if ((thisline = malloc(thislinesize + 1)) == NULL)
+ err(1, "malloc");
+
+ while ((p = fgetln(ifp, &psize)) != NULL) {
+ if (psize > thislinesize) {
+ if ((thisline = realloc(thisline, psize + 1)) == NULL)
+ err(1, "realloc");
+ thislinesize = psize;
+ }
+ thislen = psize;
+ (void)memcpy(thisline, p, psize);
+ thisline[psize] = '\0';
+
+ /* If requested get the chosen fields + character offsets. */
+ if (numfields || numchars) {
+ thisp = skip(thisline, &thislen);
+ } else {
+ thisp = thisline;
+ }
+
+ /* If different, print; set previous to new value. */
+ if (thislen != prevlen || strcmp(thisp, prevp)) {
+ char *t;
+ size_t ts;
+
+ show(ofp, prevline);
+ t = prevline;
+ prevline = thisline;
+ thisline = t;
+ ts = prevlinesize;
+ prevlinesize = thislinesize;
+ thislinesize = ts;
+ prevp = thisp;
+ prevlen = thislen;
+ repeats = 0;
+ } else
+ ++repeats;
+ }
+ show(ofp, prevline);
+ free(prevline);
+ free(thisline);
+ return 0;
+}
+
+/*
+ * show --
+ * Output a line depending on the flags and number of repetitions
+ * of the line.
+ */
+static void
+show(FILE *ofp, const char *str)
+{
+
+ if ((dflag && repeats == 0) || (uflag && repeats > 0))
+ return;
+ if (cflag) {
+ (void)fprintf(ofp, "%4d %s", repeats + 1, str);
+ } else {
+ (void)fprintf(ofp, "%s", str);
+ }
+}
+
+static const char *
+skip(const char *str, size_t *linesize)
+{
+ int infield, nchars, nfields;
+ size_t ls = *linesize;
+
+ for (nfields = numfields, infield = 0; nfields && *str; ++str, --ls)
+ if (isspace((unsigned char)*str)) {
+ if (infield) {
+ infield = 0;
+ --nfields;
+ }
+ } else if (!infield)
+ infield = 1;
+ for (nchars = numchars; nchars-- && *str; ++str, --ls)
+ continue;
+ *linesize = ls;
+ return str;
+}
+
+static FILE *
+file(const char *name, const char *mode)
+{
+ FILE *fp;
+
+ if ((fp = fopen(name, mode)) == NULL)
+ err(1, "%s", name);
+ return(fp);
+}
+
+static void
+obsolete(char *argv[])
+{
+ char *ap, *p, *start;
+
+ while ((ap = *++argv) != NULL) {
+ /* Return if "--" or not an option of any form. */
+ if (ap[0] != '-') {
+ if (ap[0] != '+')
+ return;
+ } else if (ap[1] == '-')
+ return;
+ if (!isdigit((unsigned char)ap[1]))
+ continue;
+ /*
+ * Digit signifies an old-style option. Malloc space for dash,
+ * new option and argument.
+ */
+ (void)asprintf(&p, "-%c%s", ap[0] == '+' ? 's' : 'f', ap + 1);
+ if (!p)
+ err(1, "malloc");
+ start = p;
+ *argv = start;
+ }
+}
+
+static void
+usage(void)
+{
+ (void)fprintf(stderr, "usage: %s [-cdu] [-f fields] [-s chars] "
+ "[input_file [output_file]]\n", getprogname());
+ exit(1);
+}
diff --git a/usr.bin/uudecode/uudecode.c b/usr.bin/uudecode/uudecode.c
new file mode 100644
index 0000000..977ace2
--- /dev/null
+++ b/usr.bin/uudecode/uudecode.c
@@ -0,0 +1,323 @@
+/* $NetBSD: uudecode.c,v 1.28 2013/01/28 19:50:30 apb Exp $ */
+
+/*-
+ * Copyright (c) 1983, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#if HAVE_NBTOOL_CONFIG_H
+#include "nbtool_config.h"
+#endif
+
+#include <sys/cdefs.h>
+#if !defined(lint)
+__COPYRIGHT("@(#) Copyright (c) 1983, 1993\
+ The Regents of the University of California. All rights reserved.");
+#if 0
+static char sccsid[] = "@(#)uudecode.c 8.2 (Berkeley) 4/2/94";
+#endif
+__RCSID("$NetBSD: uudecode.c,v 1.28 2013/01/28 19:50:30 apb Exp $");
+#endif /* not lint */
+
+/*
+ * uudecode [file ...]
+ *
+ * create the specified file, decoding as you go.
+ * used with uuencode.
+ */
+#include <sys/param.h>
+#include <sys/stat.h>
+#include <ctype.h>
+#include <err.h>
+#include <errno.h>
+#include <limits.h>
+#include <locale.h>
+#include <pwd.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#ifndef NO_BASE64
+#include <netinet/in.h>
+#include <resolv.h>
+#endif
+
+static int decode(char *);
+__dead static void usage(void);
+static int checkend(const char *, const char *, const char *);
+static int base64_decode(void);
+
+static int base64;
+static const char *inputname;
+
+int
+main(int argc, char *argv[])
+{
+ int ch, rval;
+ char *outputname = NULL;
+
+ setlocale(LC_ALL, "");
+ setprogname(argv[0]);
+
+ while ((ch = getopt(argc, argv, "mo:p")) != -1)
+ switch (ch) {
+ case 'm':
+ base64 = 1;
+ break;
+ case 'o':
+ outputname = optarg;
+ break;
+ case 'p':
+ outputname = __UNCONST("/dev/stdout");
+ break;
+ default:
+ usage();
+ }
+ argc -= optind;
+ argv += optind;
+
+ if (*argv) {
+ rval = 0;
+ do {
+ if (!freopen(inputname = *argv, "r", stdin)) {
+ warn("%s", *argv);
+ rval = 1;
+ continue;
+ }
+ rval |= decode(outputname);
+ } while (*++argv);
+ } else {
+ inputname = "stdin";
+ rval = decode(outputname);
+ }
+ exit(rval);
+}
+
+/*
+ * Decode one file from stdin. If outputname is not NULL
+ * then it overrides the file name embedded in the input data.
+ */
+static int
+decode(char *outputname)
+{
+ struct passwd *pw;
+ int n;
+ char ch, *p;
+ int n1;
+ long mode;
+ char *fn;
+ char buf[MAXPATHLEN];
+
+ /* search for header line */
+ for (;;) {
+ if (!fgets(buf, sizeof(buf), stdin)) {
+ warnx("%s: no \"%s\" line", inputname, base64 ?
+ "begin-base64" : "begin");
+ return(1);
+ }
+ p = buf;
+ if (strncmp(p, "begin-base64", 12) == 0) {
+ base64 = 1;
+ p += 13;
+ break;
+ } else if (strncmp(p, "begin", 5) == 0) {
+ p += 6;
+ break;
+ } else
+ continue;
+ }
+
+ /* must be followed by an octal mode and a space */
+ mode = strtol(p, &fn, 8);
+ if (fn == (p) || !isspace((unsigned char)*fn) || mode==LONG_MIN || mode==LONG_MAX)
+ {
+ warnx("%s: invalid mode on \"%s\" line", inputname,
+ base64 ? "begin-base64" : "begin");
+ return(1);
+ }
+ /* skip whitespace for file name */
+ while (*fn && isspace((unsigned char)*fn)) fn++;
+ if (*fn == 0) {
+ warnx("%s: no filename on \"%s\" line", inputname,
+ base64 ? "begin-base64" : "begin");
+ return(1);
+ }
+ /* zap newline */
+ for (p = fn; *p && *p != '\n'; p++)
+ ;
+ if (*p) *p = 0;
+
+ /* outputname overrides fn */
+ if (outputname)
+ fn = outputname;
+
+ /* handle ~user/file format */
+ if (*fn == '~') {
+ if (!(p = strchr(fn, '/'))) {
+ warnx("%s: illegal ~user.", inputname);
+ return(1);
+ }
+ *p++ = '\0';
+ if (!(pw = getpwnam(fn + 1))) {
+ warnx("%s: no user %s.", inputname, buf);
+ return(1);
+ }
+ n = strlen(pw->pw_dir);
+ n1 = strlen(p);
+ if (n + n1 + 2 > MAXPATHLEN) {
+ warnx("%s: path too long.", inputname);
+ return(1);
+ }
+ /* make space at beginning of buf by moving end of pathname */
+ memmove(buf + n + 1, p, n1 + 1);
+ memmove(buf, pw->pw_dir, n);
+ buf[n] = '/';
+ fn = buf;
+ }
+
+ if (strcmp(fn, "/dev/stdout") == 0 || strcmp(fn, "-") == 0) {
+ /*
+ * POSIX.1-2008 says that both "-" and "/dev/stdout"
+ * refer to standard output when they appear in the file
+ * header, but only "/dev/stdout" refers to standard
+ * output when it appears as the argument to the "-o"
+ * command line option.
+ *
+ * We handle both special names, regardless of whether
+ * they came from the "-o" option or from the header of
+ * the input stream.
+ */
+ } else {
+ /*
+ * Create output file, and set its mode. POSIX.1-2008
+ * requires the mode to be used exactly, ignoring the
+ * umask and anything else, but we mask it with 0666.
+ */
+ if (freopen(fn, "w", stdout) == NULL ||
+ fchmod(fileno(stdout), mode & 0666) != 0) {
+ warn("%s: %s", fn, inputname);
+ return(1);
+ }
+ }
+
+ if (base64)
+ return base64_decode();
+ else {
+ /* for each input line */
+ for (;;) {
+ if (!fgets(p = buf, sizeof(buf), stdin)) {
+ warnx("%s: short file.", inputname);
+ return(1);
+ }
+#define DEC(c) (((c) - ' ') & 077) /* single character decode */
+ /*
+ * `n' is used to avoid writing out all the characters
+ * at the end of the file.
+ */
+ if ((n = DEC(*p)) <= 0)
+ break;
+ for (++p; n > 0; p += 4, n -= 3)
+ if (n >= 3) {
+ ch = DEC(p[0]) << 2 | DEC(p[1]) >> 4;
+ putchar(ch);
+ ch = DEC(p[1]) << 4 | DEC(p[2]) >> 2;
+ putchar(ch);
+ ch = DEC(p[2]) << 6 | DEC(p[3]);
+ putchar(ch);
+ }
+ else {
+ if (n >= 1) {
+ ch = DEC(p[0]) << 2 | DEC(p[1]) >> 4;
+ putchar(ch);
+ }
+ if (n >= 2) {
+ ch = DEC(p[1]) << 4 | DEC(p[2]) >> 2;
+ putchar(ch);
+ }
+ if (n >= 3) {
+ ch = DEC(p[2]) << 6 | DEC(p[3]);
+ putchar(ch);
+ }
+ }
+ }
+ if (!fgets(buf, sizeof(buf), stdin) || strcmp(buf, "end\n")) {
+ warnx("%s: no \"end\" line.", inputname);
+ return(1);
+ }
+ return(0);
+ }
+}
+
+static int
+checkend(const char *ptr, const char *end, const char *msg)
+{
+ size_t n;
+
+ n = strlen(end);
+ if (strncmp(ptr, end, n) != 0 ||
+ strspn(ptr + n, "\t\r\n") != strlen(ptr + n)) {
+ warnx("%s", msg);
+ return (1);
+ }
+ return (0);
+}
+
+static int
+base64_decode(void)
+{
+ int n;
+ char inbuf[MAXPATHLEN];
+ unsigned char outbuf[MAXPATHLEN * 4];
+
+ for (;;) {
+ if (!fgets(inbuf, sizeof(inbuf), stdin)) {
+ warnx("%s: short file.", inputname);
+ return (1);
+ }
+#ifdef NO_BASE64
+ warnx("%s: base64 decoding is not supported", inputname);
+ return (1);
+#else
+ n = b64_pton(inbuf, outbuf, sizeof(outbuf));
+#endif
+ if (n < 0)
+ break;
+ fwrite(outbuf, 1, n, stdout);
+ }
+ return (checkend(inbuf, "====",
+ "error decoding base64 input stream"));
+}
+
+static void
+usage(void)
+{
+ (void)fprintf(stderr,
+ "usage: %s [-m] [-p | -o outfile] [encoded-file ...]\n",
+ getprogname());
+ exit(1);
+}
diff --git a/usr.bin/uuencode/uuencode.1 b/usr.bin/uuencode/uuencode.1
new file mode 100644
index 0000000..fabd310
--- /dev/null
+++ b/usr.bin/uuencode/uuencode.1
@@ -0,0 +1,175 @@
+.\" $NetBSD: uuencode.1,v 1.27 2017/07/03 21:34:22 wiz Exp $
+.\"
+.\" Copyright (c) 1980, 1990, 1993
+.\" The Regents of the University of California. All rights reserved.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\" notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must reproduce the above copyright
+.\" notice, this list of conditions and the following disclaimer in the
+.\" documentation and/or other materials provided with the distribution.
+.\" 3. Neither the name of the University nor the names of its contributors
+.\" may be used to endorse or promote products derived from this software
+.\" without specific prior written permission.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+.\" ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+.\" SUCH DAMAGE.
+.\"
+.\" @(#)uuencode.1 8.1 (Berkeley) 6/6/93
+.\"
+.Dd September 6, 2014
+.Dt UUENCODE 1
+.Os
+.Sh NAME
+.Nm uuencode ,
+.Nm uudecode
+.Nd encode/decode a binary file
+.Sh SYNOPSIS
+.Nm
+.Op Fl m
+.Op Ar inputfile
+.Ar headername
+.Nm uudecode
+.Op Fl m
+.Op Fl p | Fl o Ar outputfile
+.Op Ar encoded-file ...
+.Sh DESCRIPTION
+.Nm
+and
+.Nm uudecode
+are used to transmit binary files over transmission mediums
+that do not support other than simple
+.Tn ASCII
+data.
+.Pp
+The following options are available:
+.Bl -tag -width ".Fl m"
+.It Fl m
+Use base64 encoding.
+For
+.Nm ,
+the historical uuencode algorithm is the default.
+For
+.Nm uudecode ,
+by default the encoding is automatically detected.
+.It Fl o Ar outputfile
+.Po Nm uudecode No only . Pc
+Send the decoded output data to
+.Ar outputfile .
+By default,
+.Nm uudecode
+uses the
+.Ar headername
+recorded in the header of the encoded data stream.
+.It Fl p
+.Po Nm uudecode No only . Pc
+Write the decoded file to standard output instead of to a file.
+.El
+.Pp
+.Nm
+reads
+.Ar inputfile
+(or by default the standard input) and writes an encoded version
+to (always) the standard output.
+The encoding uses only printing
+.Tn ASCII
+characters suitable for text-only transport media.
+The string
+.Ar headername
+is inserted into the output header as the
+.Ar outputfile
+to use at
+.Nm uudecode
+time.
+The header also includes the mode (permissions) of the file.
+.Pp
+.Nm uudecode
+transforms
+.Em uuencoded
+files (or by default, the standard input) into the original form.
+The resulting file is named
+.Ar headername
+as recorded in the encoded file,
+or as specified by the
+.Fl o
+option,
+and will have the mode of the original file except that setuid
+and execute bits are not retained.
+If the
+.Fl p
+option is specified, or if the output file name is given as
+.Pa /dev/stdout ,
+then the data will be written to the standard output
+instead of to a named file.
+.Nm uudecode
+ignores any leading and trailing lines.
+.Pp
+The encoded form of the file is expanded by 35%.
+Every 3 bytes become 4 plus control information.
+.Sh EXIT STATUS
+The
+.Nm uudecode
+and
+.Nm
+utilities exits 0 on success, and >0 if an error occurs.
+.Sh EXAMPLES
+The following example packages up a source tree, compresses it,
+uuencodes it and mails it to a user on another system.
+.Pp
+.Bd -literal -offset indent -compact
+tar czf \- src_tree \&| uuencode src_tree.tgz \&| mail user@example.com
+.Ed
+.Pp
+On the other system, if the user saves the mail to the file
+.Pa temp ,
+the following example creates the file
+.Pa src_tree.tgz
+and extracts it to make a copy of the original tree.
+.Pp
+.Bd -literal -offset indent -compact
+uudecode temp
+tar xzf src_tree.tgz
+.Ed
+.Sh SEE ALSO
+.Xr gzip 1 ,
+.Xr mail 1 ,
+.Xr tar 1 ,
+.\".Xr uucp 1 ,
+.Xr uuencode 5
+.Sh STANDARDS
+The
+.Nm uudecode
+and
+.Nm
+utilities conform to
+.St -p1003.1-2008 .
+.Sh HISTORY
+The
+.Nm uudecode
+and
+.Nm
+utilities appeared in
+.Bx 4.0 .
+.Sh SECURITY CONSIDERATIONS
+When using
+.Nm uudecode
+with files coming from dubious sources,
+always either explicitly pass the
+.Fl o
+option or check the header (the first line) of the encoded file for
+safety.
+Blindly using a
+.Ar headername
+from a hostile source can overwrite important files.
diff --git a/usr.bin/uuencode/uuencode.5 b/usr.bin/uuencode/uuencode.5
new file mode 100644
index 0000000..4c8950a
--- /dev/null
+++ b/usr.bin/uuencode/uuencode.5
@@ -0,0 +1,145 @@
+.\" $NetBSD: uuencode.5,v 1.12 2016/06/06 15:09:33 abhinav Exp $
+.\"
+.\" Copyright (c) 1989, 1991, 1993
+.\" The Regents of the University of California. All rights reserved.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\" notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must reproduce the above copyright
+.\" notice, this list of conditions and the following disclaimer in the
+.\" documentation and/or other materials provided with the distribution.
+.\" 3. Neither the name of the University nor the names of its contributors
+.\" may be used to endorse or promote products derived from this software
+.\" without specific prior written permission.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+.\" ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+.\" SUCH DAMAGE.
+.\"
+.\" @(#)uuencode.format.5 8.2 (Berkeley) 1/12/94
+.\"
+.Dd June 2, 2016
+.Dt UUENCODE 5
+.Os
+.Sh NAME
+.Nm uuencode
+.Nd format of an encoded uuencode file
+.Sh DESCRIPTION
+Files output by
+.Xr uuencode 1
+consist of a header line,
+followed by a number of body lines,
+and a trailer line.
+The
+.Xr uudecode 1
+command
+will ignore any lines preceding the header or
+following the trailer.
+Lines preceding a header must not, of course,
+look like a header.
+.Pp
+The header line starts with the word
+.Dq begin ,
+a space,
+a file mode (in octal),
+a space,
+and finally a string which names the file being encoded.
+.Pp
+The central engine of
+.Xr uuencode 1
+is a six-bit encoding function which outputs an
+.Tn ASCII
+character.
+The six bits to be encoded are treated as a small integer and added
+with the
+.Tn ASCII
+value for the space character (octal 40).
+The result is a printable
+.Tn ASCII
+character.
+In the case where all six bits to be encoded are zero,
+the
+.Tn ASCII
+backquote character \` (octal 140) is emitted instead of what
+would normally be a space.
+.Pp
+The body of an encoded file consists of one or more lines,
+each of which may be a maximum of 86 characters long (including the trailing
+newline).
+Each line represents an encoded chunk of data from the input file and begins
+with a byte count,
+followed by encoded bytes,
+followed by a newline.
+.Pp
+The byte count is a six-bit integer encoded with the above function,
+representing the number of bytes encoded in the rest of the line.
+The method used to encode the data expands its size by
+133% (described below).
+Therefore it is important to note that the byte count describes the size of
+the chunk of data before it is encoded, not afterwards.
+The six bit size of this number effectively limits the number of bytes
+that can be encoded in each line to a maximum of 63.
+While
+.Xr uuencode 1
+will not encode more than 45 bytes per line,
+.Xr uudecode 1
+will tolerate the maximum line size.
+.Pp
+The remaining characters in the line represent the data of the input
+file encoded as follows.
+Input data are broken into groups of three eight-bit bytes,
+which are then interpreted together as a 24-bit block.
+The first bit of the block is the highest order bit of the first character,
+and the last is the lowest order bit of the third character.
+This block is then broken into four six-bit integers which are encoded one by
+one starting from the first bit of the block.
+The result is a four character
+.Tn ASCII
+string for every three bytes of input data.
+.Pp
+Encoded lines of data continue in this manner until the input file is
+exhausted.
+The end of the body is signaled by an encoded line with a byte count
+of zero (the
+.Tn ASCII
+backquote character \`).
+.Pp
+Obviously, not every input file will be a multiple of three bytes in size.
+In these cases,
+.Xr uuencode 1
+will pad the remaining one or two bytes of data with garbage bytes until
+a three byte group is created.
+The byte count in a line containing
+garbage padding will reflect the actual number of bytes encoded, making
+it possible to convey how many bytes are garbage.
+.Pp
+The trailer line consists of
+.Dq end
+on a line by itself.
+.Sh SEE ALSO
+.Xr mail 1 ,
+.Xr uudecode 1 ,
+.Xr uuencode 1 ,
+.Xr ascii 7
+.Sh HISTORY
+The
+.Nm
+file format appeared in
+.Bx 4.0 .
+.Sh BUGS
+The interpretation of the
+.Nm
+format relies on properties of the
+.Tn ASCII
+character set and may not work correctly on non-ASCII systems.
diff --git a/usr.bin/uuencode/uuencode.c b/usr.bin/uuencode/uuencode.c
new file mode 100644
index 0000000..50b7051
--- /dev/null
+++ b/usr.bin/uuencode/uuencode.c
@@ -0,0 +1,202 @@
+/* $NetBSD: uuencode.c,v 1.16 2014/09/06 18:58:35 dholland Exp $ */
+
+/*-
+ * Copyright (c) 1983, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+#ifndef lint
+__COPYRIGHT("@(#) Copyright (c) 1983, 1993\
+ The Regents of the University of California. All rights reserved.");
+#endif /* not lint */
+
+#ifndef lint
+#if 0
+static char sccsid[] = "@(#)uuencode.c 8.2 (Berkeley) 4/2/94";
+#else
+__RCSID("$NetBSD: uuencode.c,v 1.16 2014/09/06 18:58:35 dholland Exp $");
+#endif
+#endif /* not lint */
+
+/*
+ * uuencode [input] output
+ *
+ * Encode a file so it can be mailed to a remote system.
+ */
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <netinet/in.h>
+#include <err.h>
+#include <errno.h>
+#include <locale.h>
+#include <resolv.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+static void encode(void);
+static void base64_encode(void);
+__dead static void usage(void);
+
+int
+main(int argc, char *argv[])
+{
+ struct stat sb;
+ int base64, ch, mode;
+
+ mode = 0;
+ base64 = 0;
+ setlocale(LC_ALL, "");
+ setprogname(argv[0]);
+
+ while ((ch = getopt(argc, argv, "m")) != -1) {
+ switch(ch) {
+ case 'm':
+ base64 = 1;
+ break;
+ default:
+ usage();
+ }
+ }
+ argv += optind;
+ argc -= optind;
+
+ switch(argc) {
+ case 2: /* optional first argument is input file */
+ if (!freopen(*argv, "r", stdin) || fstat(fileno(stdin), &sb))
+ err(1, "%s", *argv);
+#define RWX (S_IRWXU|S_IRWXG|S_IRWXO)
+ mode = sb.st_mode & RWX;
+ ++argv;
+ break;
+ case 1:
+#define RW (S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP|S_IROTH|S_IWOTH)
+ mode = RW & ~umask(RW);
+ break;
+ case 0:
+ default:
+ usage();
+ }
+
+ if (base64) {
+ (void)printf("begin-base64 %o %s\n", mode, *argv);
+ base64_encode();
+ (void)printf("====\n");
+ } else {
+ (void)printf("begin %o %s\n", mode, *argv);
+ encode();
+ (void)printf("end\n");
+ }
+
+ if (ferror(stdout))
+ err(1, "write error");
+ exit(0);
+}
+
+/* ENC is the basic 1 character encoding function to make a char printing */
+#define ENC(c) ((c) ? ((c) & 077) + ' ': '`')
+
+/*
+ * copy from in to out, encoding in base64 as you go along.
+ */
+static void
+base64_encode(void)
+{
+ /*
+ * Output must fit into 80 columns, chunks come in 4, leave 1.
+ */
+#define GROUPS ((70 / 4) - 1)
+ unsigned char buf[3];
+ char buf2[sizeof(buf) * 2 + 1];
+ size_t n;
+ int rv, sequence;
+
+ sequence = 0;
+
+ while ((n = fread(buf, 1, sizeof(buf), stdin))) {
+ ++sequence;
+ rv = b64_ntop(buf, n, buf2, (sizeof(buf2) / sizeof(buf2[0])));
+ if (rv == -1)
+ errx(1, "b64_ntop: error encoding base64");
+ printf("%s%s", buf2, (sequence % GROUPS) ? "" : "\n");
+ }
+ if (sequence % GROUPS)
+ printf("\n");
+}
+
+/*
+ * copy from in to out, encoding as you go along.
+ */
+static void
+encode(void)
+{
+ int ch, n;
+ char *p;
+ char buf[80];
+
+ while ((n = fread(buf, 1, 45, stdin)) > 0) {
+ ch = ENC(n);
+ if (putchar(ch) == EOF)
+ break;
+ for (p = buf; n > 0; n -= 3, p += 3) {
+ ch = *p >> 2;
+ ch = ENC(ch);
+ if (putchar(ch) == EOF)
+ break;
+ ch = ((*p << 4) & 060) | ((p[1] >> 4) & 017);
+ ch = ENC(ch);
+ if (putchar(ch) == EOF)
+ break;
+ ch = ((p[1] << 2) & 074) | ((p[2] >> 6) & 03);
+ ch = ENC(ch);
+ if (putchar(ch) == EOF)
+ break;
+ ch = p[2] & 077;
+ ch = ENC(ch);
+ if (putchar(ch) == EOF)
+ break;
+ }
+ if (putchar('\n') == EOF)
+ break;
+ }
+ if (ferror(stdin))
+ err(1, "read error.");
+ ch = ENC('\0');
+ (void)putchar(ch);
+ (void)putchar('\n');
+}
+
+static void
+usage(void)
+{
+ (void)fprintf(stderr,
+ "usage: %s [-m] [inputfile] headername > encodedfile\n",
+ getprogname());
+ exit(1);
+}
diff --git a/usr.bin/wc/wc.1 b/usr.bin/wc/wc.1
new file mode 100644
index 0000000..60454a3
--- /dev/null
+++ b/usr.bin/wc/wc.1
@@ -0,0 +1,146 @@
+.\" $NetBSD: wc.1,v 1.17 2017/07/03 21:34:22 wiz Exp $
+.\"
+.\" Copyright (c) 1991, 1993
+.\" The Regents of the University of California. All rights reserved.
+.\"
+.\" This code is derived from software contributed to Berkeley by
+.\" the Institute of Electrical and Electronics Engineers, Inc.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\" notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must reproduce the above copyright
+.\" notice, this list of conditions and the following disclaimer in the
+.\" documentation and/or other materials provided with the distribution.
+.\" 3. Neither the name of the University nor the names of its contributors
+.\" may be used to endorse or promote products derived from this software
+.\" without specific prior written permission.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+.\" ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+.\" SUCH DAMAGE.
+.\"
+.\" from: @(#)wc.1 8.2 (Berkeley) 4/19/94
+.\"
+.Dd February 18, 2010
+.Dt WC 1
+.Os
+.Sh NAME
+.Nm wc
+.Nd word, line, and byte count
+.Sh SYNOPSIS
+.Nm
+.Op Fl c | Fl m
+.Op Fl Llw
+.Op Ar file ...
+.Sh DESCRIPTION
+The
+.Nm
+utility displays the number of lines, words, bytes and characters contained in each
+input
+.Ar file
+(or standard input, by default) to the standard output.
+A line is defined as a string of characters delimited by a <newline>
+character,
+and a word is defined as a string of characters delimited by white space
+characters.
+White space characters are the set of characters for which the
+.Xr iswspace 3
+function returns true.
+If more than one input file is specified, a line of cumulative counts
+for all the files is displayed on a separate line after the output for
+the last file.
+.Pp
+The following options are available:
+.Bl -tag -width Ds
+.It Fl c
+The number of bytes in each input file
+is written to the standard output.
+.It Fl L
+The number of characters in the longest line of each input file
+is written to the standard output.
+.It Fl l
+The number of lines in each input file
+is written to the standard output.
+.It Fl m
+The number of characters in each input file
+is written to the standard output.
+.It Fl w
+The number of words in each input file
+is written to the standard output.
+.El
+.Pp
+When an option is specified,
+.Nm
+only
+reports the
+information requested by that option.
+The default action is equivalent to all the flags
+.Fl clw
+having been specified.
+.Pp
+The following operands are available:
+.Bl -tag -width Ds
+.It Ar file
+A pathname of an input file.
+.El
+.Pp
+If no file names
+are specified, the standard input is used and
+no file name is displayed.
+.Pp
+By default, the standard output contains a line for each
+input file of the form:
+.Bd -literal -offset indent
+lines words bytes file_name
+.Ed
+.Sh EXIT STATUS
+.Ex -std wc
+.Sh COMPATIBILITY
+Historically, the
+.Nm
+utility was documented to define a word as a ``maximal string of
+characters delimited by
+.Aq space ,
+.Aq tab
+or
+.Aq newline
+characters''.
+The implementation, however, didn't handle non-printing characters
+correctly so that `` ^D^E '' counted as 6 spaces, while ``foo^D^Ebar''
+counted as 8 characters.
+.Bx 4
+systems after
+.Bx 4.3
+modified the implementation to be consistent
+with the documentation.
+This implementation defines a ``word'' in terms of the
+.Xr iswspace 3
+function, as required by
+.St -p1003.2 .
+.Pp
+The
+.Fl L
+option is a non-standard extension, compatible with the
+.Fl L
+option of the GNU and
+.Fx
+.Nm
+utilities.
+.Sh SEE ALSO
+.Xr iswspace 3
+.Sh STANDARDS
+The
+.Nm
+utility conforms to
+.St -p1003.2-92 .
diff --git a/usr.bin/wc/wc.c b/usr.bin/wc/wc.c
new file mode 100644
index 0000000..602a450
--- /dev/null
+++ b/usr.bin/wc/wc.c
@@ -0,0 +1,354 @@
+/* $NetBSD: wc.c,v 1.35 2011/09/16 15:39:30 joerg Exp $ */
+
+/*
+ * Copyright (c) 1980, 1987, 1991, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+#ifndef lint
+__COPYRIGHT("@(#) Copyright (c) 1980, 1987, 1991, 1993\
+ The Regents of the University of California. All rights reserved.");
+#endif /* not lint */
+
+#ifndef lint
+#if 0
+static char sccsid[] = "@(#)wc.c 8.2 (Berkeley) 5/2/95";
+#else
+__RCSID("$NetBSD: wc.c,v 1.35 2011/09/16 15:39:30 joerg Exp $");
+#endif
+#endif /* not lint */
+
+/* wc line, word, char count and optionally longest line. */
+
+#include <sys/param.h>
+#include <sys/file.h>
+#include <sys/stat.h>
+
+#include <ctype.h>
+#include <fcntl.h>
+#include <err.h>
+#include <errno.h>
+#include <locale.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <wchar.h>
+#include <wctype.h>
+
+#ifdef NO_QUAD
+typedef u_long wc_count_t;
+# define WCFMT " %7lu"
+# define WCCAST unsigned long
+#else
+typedef u_quad_t wc_count_t;
+# define WCFMT " %7llu"
+# define WCCAST unsigned long long
+#endif
+
+static wc_count_t tlinect, twordct, tcharct, tlongest;
+static bool doline, doword, dobyte, dochar, dolongest;
+static int rval = 0;
+
+static void cnt(const char *);
+static void print_counts(wc_count_t, wc_count_t, wc_count_t, wc_count_t,
+ const char *);
+__dead static void usage(void);
+static size_t do_mb(wchar_t *, const char *, size_t, mbstate_t *,
+ size_t *, const char *);
+
+int
+main(int argc, char *argv[])
+{
+ int ch;
+
+ setlocale(LC_ALL, "");
+
+ while ((ch = getopt(argc, argv, "lwcmL")) != -1)
+ switch (ch) {
+ case 'l':
+ doline = true;
+ break;
+ case 'w':
+ doword = true;
+ break;
+ case 'm':
+ dochar = true;
+ dobyte = 0;
+ break;
+ case 'c':
+ dochar = 0;
+ dobyte = true;
+ break;
+ case 'L':
+ dolongest = true;
+ break;
+ case '?':
+ default:
+ usage();
+ }
+ argv += optind;
+ argc -= optind;
+
+ /* Wc's flags are on by default. */
+ if (!(doline || doword || dobyte || dochar || dolongest))
+ doline = doword = dobyte = true;
+
+ if (*argv == NULL) {
+ cnt(NULL);
+ } else {
+ bool dototal = (argc > 1);
+
+ do {
+ cnt(*argv);
+ } while(*++argv);
+
+ if (dototal) {
+ print_counts(tlinect, twordct, tcharct, tlongest,
+ "total");
+ }
+ }
+
+ exit(rval);
+}
+
+static size_t
+do_mb(wchar_t *wc, const char *p, size_t len, mbstate_t *st,
+ size_t *retcnt, const char *file)
+{
+ size_t r;
+ size_t c = 0;
+
+ do {
+ r = mbrtowc(wc, p, len, st);
+ if (r == (size_t)-1) {
+ warnx("%s: invalid byte sequence", file);
+ rval = 1;
+
+ /* XXX skip 1 byte */
+ len--;
+ p++;
+ memset(st, 0, sizeof(*st));
+ continue;
+ } else if (r == (size_t)-2)
+ break;
+ else if (r == 0)
+ r = 1;
+ c++;
+ if (wc)
+ wc++;
+ len -= r;
+ p += r;
+ } while (len > 0);
+
+ *retcnt = c;
+
+ return (r);
+}
+
+static void
+cnt(const char *file)
+{
+ u_char buf[MAXBSIZE];
+ wchar_t wbuf[MAXBSIZE];
+ struct stat sb;
+ wc_count_t charct, linect, wordct, longest;
+ mbstate_t st;
+ u_char *C;
+ wchar_t *WC;
+ const char *name; /* filename or <stdin> */
+ size_t r = 0;
+ int fd, len = 0;
+
+ linect = wordct = charct = longest = 0;
+ if (file != NULL) {
+ if ((fd = open(file, O_RDONLY, 0)) < 0) {
+ warn("%s", file);
+ rval = 1;
+ return;
+ }
+ name = file;
+ } else {
+ fd = STDIN_FILENO;
+ name = "<stdin>";
+ }
+
+ if (dochar || doword || dolongest)
+ (void)memset(&st, 0, sizeof(st));
+
+ if (!(doword || dolongest)) {
+ /*
+ * line counting is split out because it's a lot
+ * faster to get lines than to get words, since
+ * the word count requires some logic.
+ */
+ if (doline || dochar) {
+ while ((len = read(fd, buf, MAXBSIZE)) > 0) {
+ if (dochar) {
+ size_t wlen;
+
+ r = do_mb(0, (char *)buf, (size_t)len,
+ &st, &wlen, name);
+ charct += wlen;
+ } else if (dobyte)
+ charct += len;
+ if (doline) {
+ for (C = buf; len--; ++C) {
+ if (*C == '\n')
+ ++linect;
+ }
+ }
+ }
+ }
+
+ /*
+ * if all we need is the number of characters and
+ * it's a directory or a regular or linked file, just
+ * stat the puppy. We avoid testing for it not being
+ * a special device in case someone adds a new type
+ * of inode.
+ */
+ else if (dobyte) {
+ if (fstat(fd, &sb)) {
+ warn("%s", name);
+ rval = 1;
+ } else {
+ if (S_ISREG(sb.st_mode) ||
+ S_ISLNK(sb.st_mode) ||
+ S_ISDIR(sb.st_mode)) {
+ charct = sb.st_size;
+ } else {
+ while ((len =
+ read(fd, buf, MAXBSIZE)) > 0)
+ charct += len;
+ }
+ }
+ }
+ } else {
+ /* do it the hard way... */
+ wc_count_t linelen;
+ bool gotsp;
+
+ linelen = 0;
+ gotsp = true;
+ while ((len = read(fd, buf, MAXBSIZE)) > 0) {
+ size_t wlen;
+
+ r = do_mb(wbuf, (char *)buf, (size_t)len, &st, &wlen,
+ name);
+ if (dochar) {
+ charct += wlen;
+ } else if (dobyte) {
+ charct += len;
+ }
+ for (WC = wbuf; wlen--; ++WC) {
+ if (iswspace(*WC)) {
+ gotsp = true;
+ if (*WC == L'\n') {
+ ++linect;
+ if (linelen > longest)
+ longest = linelen;
+ linelen = 0;
+ } else {
+ linelen++;
+ }
+ } else {
+ /*
+ * This line implements the POSIX
+ * spec, i.e. a word is a "maximal
+ * string of characters delimited by
+ * whitespace." Notice nothing was
+ * said about a character being
+ * printing or non-printing.
+ */
+ if (gotsp) {
+ gotsp = false;
+ ++wordct;
+ }
+
+ linelen++;
+ }
+ }
+ }
+ }
+
+ if (len == -1) {
+ warn("%s", name);
+ rval = 1;
+ }
+ if (dochar && r == (size_t)-2) {
+ warnx("%s: incomplete multibyte character", name);
+ rval = 1;
+ }
+
+ print_counts(linect, wordct, charct, longest, file);
+
+ /*
+ * don't bother checkint doline, doword, or dobyte --- speeds
+ * up the common case
+ */
+ tlinect += linect;
+ twordct += wordct;
+ tcharct += charct;
+ if (dolongest && longest > tlongest)
+ tlongest = longest;
+
+ if (close(fd)) {
+ warn("%s", name);
+ rval = 1;
+ }
+}
+
+static void
+print_counts(wc_count_t lines, wc_count_t words, wc_count_t chars,
+ wc_count_t longest, const char *name)
+{
+
+ if (doline)
+ (void)printf(WCFMT, (WCCAST)lines);
+ if (doword)
+ (void)printf(WCFMT, (WCCAST)words);
+ if (dobyte || dochar)
+ (void)printf(WCFMT, (WCCAST)chars);
+ if (dolongest)
+ (void)printf(WCFMT, (WCCAST)longest);
+
+ if (name != NULL)
+ (void)printf(" %s\n", name);
+ else
+ (void)putchar('\n');
+}
+
+static void
+usage(void)
+{
+
+ (void)fprintf(stderr, "usage: wc [-c | -m] [-Llw] [file ...]\n");
+ exit(1);
+}
diff --git a/usr.bin/what/what.1 b/usr.bin/what/what.1
new file mode 100644
index 0000000..4a96a7a
--- /dev/null
+++ b/usr.bin/what/what.1
@@ -0,0 +1,82 @@
+.\" $NetBSD: what.1,v 1.15 2017/07/03 21:34:22 wiz Exp $
+.\"
+.\" Copyright (c) 1980, 1991, 1993
+.\" The Regents of the University of California. All rights reserved.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\" notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must reproduce the above copyright
+.\" notice, this list of conditions and the following disclaimer in the
+.\" documentation and/or other materials provided with the distribution.
+.\" 3. Neither the name of the University nor the names of its contributors
+.\" may be used to endorse or promote products derived from this software
+.\" without specific prior written permission.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+.\" ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+.\" SUCH DAMAGE.
+.\"
+.\" @(#)what.1 8.1 (Berkeley) 6/6/93
+.\"
+.Dd December 12, 2015
+.Dt WHAT 1
+.Os
+.Sh NAME
+.Nm what
+.Nd search files for SCCS identifiers
+.Sh SYNOPSIS
+.Nm
+.Op Fl s
+.Ar
+.Sh DESCRIPTION
+The
+.Nm
+utility reads each
+.Ar file
+operand and searches for sequences of the form
+.Dq \&@(#)
+as inserted by SCCS and SCCS-compatible version control systems.
+It prints the remainder of the string following this marker, up to
+a null character, newline, double quote, backslash, or
+.Dq \&>
+character.
+.Pp
+If the
+.Fl s
+option is specified, only the first occurrence of an identification string in
+each file is printed.
+.Sh EXIT STATUS
+The
+.Nm
+utility exits 0 if any matches were found, and 1 otherwise.
+.Sh STANDARDS
+The
+.Nm
+utility conforms to
+.St -xpg4.2 .
+.Sh HISTORY
+The
+.Nm
+command appeared in
+.Bx 4.0 .
+.Sh BUGS
+As
+.Bx
+is not licensed to distribute
+.Tn SCCS
+this is a rewrite of the
+.Nm
+command which is part of
+.Tn SCCS ,
+and may not behave exactly the same as that command does.
diff --git a/usr.bin/what/what.c b/usr.bin/what/what.c
new file mode 100644
index 0000000..fb29e52
--- /dev/null
+++ b/usr.bin/what/what.c
@@ -0,0 +1,127 @@
+/* $NetBSD: what.c,v 1.12 2015/12/12 09:50:12 dholland Exp $ */
+
+/*
+ * Copyright (c) 1980, 1988, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+#ifndef lint
+__COPYRIGHT("@(#) Copyright (c) 1980, 1988, 1993\
+ The Regents of the University of California. All rights reserved.");
+#endif /* not lint */
+
+#ifndef lint
+#if 0
+static char sccsid[] = "@(#)what.c 8.1 (Berkeley) 6/6/93";
+#endif
+__RCSID("$NetBSD: what.c,v 1.12 2015/12/12 09:50:12 dholland Exp $");
+#endif /* not lint */
+
+#include <locale.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+static void search(void);
+__dead static void usage(void);
+
+static int matches;
+static int sflag;
+
+/*
+ * what
+ */
+int
+main(int argc, char **argv)
+{
+ int c;
+
+ (void)setlocale(LC_ALL, "");
+
+ matches = sflag = 0;
+ while ((c = getopt(argc, argv, "s")) != -1) {
+ switch (c) {
+ case 's':
+ sflag = 1;
+ break;
+ case '?':
+ default:
+ usage();
+ }
+ }
+ argc -= optind;
+ argv += optind;
+
+ if (argc < 1) {
+ usage();
+ } else do {
+ if (freopen(*argv, "r", stdin) == NULL) {
+ perror(*argv);
+ exit(matches ? EXIT_SUCCESS : 1);
+ }
+ printf("%s\n", *argv);
+ search();
+ } while (*++argv != NULL);
+
+ /* Note: the standard explicitly specifies an exit status of 1. */
+ exit(matches ? EXIT_SUCCESS : 1);
+ /* NOTREACHED */
+}
+
+static void
+search(void)
+{
+ int c;
+
+ while ((c = getchar()) != EOF) {
+loop: if (c != '@')
+ continue;
+ if ((c = getchar()) != '(')
+ goto loop;
+ if ((c = getchar()) != '#')
+ goto loop;
+ if ((c = getchar()) != ')')
+ goto loop;
+ putchar('\t');
+ while ((c = getchar()) != EOF && c != '\0' && c != '"' &&
+ c != '>' && c != '\n' && c != '\\')
+ putchar(c);
+ putchar('\n');
+ matches++;
+ if (sflag)
+ break;
+ }
+}
+
+static void
+usage(void)
+{
+
+ (void)fprintf(stderr, "usage: what [-s] file ...\n");
+ exit(1);
+}
diff --git a/usr.bin/who/utmpentry.c b/usr.bin/who/utmpentry.c
new file mode 100644
index 0000000..c74397b
--- /dev/null
+++ b/usr.bin/who/utmpentry.c
@@ -0,0 +1,329 @@
+/* $NetBSD: utmpentry.c,v 1.18 2015/11/21 15:01:43 christos Exp $ */
+
+/*-
+ * Copyright (c) 2002 The NetBSD Foundation, Inc.
+ * All rights reserved.
+ *
+ * This code is derived from software contributed to The NetBSD Foundation
+ * by Christos Zoulas.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
+ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+#ifndef lint
+__RCSID("$NetBSD: utmpentry.c,v 1.18 2015/11/21 15:01:43 christos Exp $");
+#endif
+
+#include <sys/stat.h>
+
+#include <time.h>
+#include <string.h>
+#include <err.h>
+#include <stdlib.h>
+
+#ifdef SUPPORT_UTMP
+#include <utmp.h>
+#endif
+#ifdef SUPPORT_UTMPX
+#include <utmpx.h>
+#endif
+
+#include "utmpentry.h"
+
+
+/* Fail the compile if x is not true, by constructing an illegal type. */
+#define COMPILE_ASSERT(x) /*LINTED null effect */ \
+ ((void)sizeof(struct { unsigned : ((x) ? 1 : -1); }))
+
+
+#ifdef SUPPORT_UTMP
+static void getentry(struct utmpentry *, struct utmp *);
+static struct timespec utmptime = {0, 0};
+#endif
+#ifdef SUPPORT_UTMPX
+static void getentryx(struct utmpentry *, struct utmpx *);
+static struct timespec utmpxtime = {0, 0};
+#endif
+#if defined(SUPPORT_UTMPX) || defined(SUPPORT_UTMP)
+static int setup(const char *);
+static void adjust_size(struct utmpentry *e);
+#endif
+
+size_t maxname = 8, maxline = 8, maxhost = 16;
+int etype = 1 << USER_PROCESS;
+static size_t numutmp = 0;
+static struct utmpentry *ehead;
+
+#if defined(SUPPORT_UTMPX) || defined(SUPPORT_UTMP)
+static void
+adjust_size(struct utmpentry *e)
+{
+ size_t max;
+
+ if ((max = strlen(e->name)) > maxname)
+ maxname = max;
+ if ((max = strlen(e->line)) > maxline)
+ maxline = max;
+ if ((max = strlen(e->host)) > maxhost)
+ maxhost = max;
+}
+
+static int
+setup(const char *fname)
+{
+ int what = 3;
+ struct stat st;
+ const char *sfname;
+
+ if (fname == NULL) {
+#ifdef SUPPORT_UTMPX
+ setutxent();
+#endif
+#ifdef SUPPORT_UTMP
+ setutent();
+#endif
+ } else {
+ size_t len = strlen(fname);
+ if (len == 0)
+ errx(1, "Filename cannot be 0 length.");
+ what = fname[len - 1] == 'x' ? 1 : 2;
+ if (what == 1) {
+#ifdef SUPPORT_UTMPX
+ if (utmpxname(fname) == 0)
+ warnx("Cannot set utmpx file to `%s'",
+ fname);
+#else
+ warnx("utmpx support not compiled in");
+#endif
+ } else {
+#ifdef SUPPORT_UTMP
+ if (utmpname(fname) == 0)
+ warnx("Cannot set utmp file to `%s'",
+ fname);
+#else
+ warnx("utmp support not compiled in");
+#endif
+ }
+ }
+#ifdef SUPPORT_UTMPX
+ if (what & 1) {
+ sfname = fname ? fname : _PATH_UTMPX;
+ if (stat(sfname, &st) == -1) {
+ warn("Cannot stat `%s'", sfname);
+ what &= ~1;
+ } else {
+ if (timespeccmp(&st.st_mtimespec, &utmpxtime, >))
+ utmpxtime = st.st_mtimespec;
+ else
+ what &= ~1;
+ }
+ }
+#endif
+#ifdef SUPPORT_UTMP
+ if (what & 2) {
+ sfname = fname ? fname : _PATH_UTMP;
+ if (stat(sfname, &st) == -1) {
+ warn("Cannot stat `%s'", sfname);
+ what &= ~2;
+ } else {
+ if (timespeccmp(&st.st_mtimespec, &utmptime, >))
+ utmptime = st.st_mtimespec;
+ else
+ what &= ~2;
+ }
+ }
+#endif
+ return what;
+}
+#endif
+
+void
+endutentries(void)
+{
+ struct utmpentry *ep;
+
+#ifdef SUPPORT_UTMP
+ timespecclear(&utmptime);
+#endif
+#ifdef SUPPORT_UTMPX
+ timespecclear(&utmpxtime);
+#endif
+ ep = ehead;
+ while (ep) {
+ struct utmpentry *sep = ep;
+ ep = ep->next;
+ free(sep);
+ }
+ ehead = NULL;
+ numutmp = 0;
+}
+
+size_t
+getutentries(const char *fname, struct utmpentry **epp)
+{
+#ifdef SUPPORT_UTMPX
+ struct utmpx *utx;
+#endif
+#ifdef SUPPORT_UTMP
+ struct utmp *ut;
+#endif
+#if defined(SUPPORT_UTMP) || defined(SUPPORT_UTMPX)
+ struct utmpentry *ep;
+ int what = setup(fname);
+ struct utmpentry **nextp = &ehead;
+ switch (what) {
+ case 0:
+ /* No updates */
+ *epp = ehead;
+ return numutmp;
+ default:
+ /* Need to re-scan */
+ ehead = NULL;
+ numutmp = 0;
+ }
+#endif
+
+#ifdef SUPPORT_UTMPX
+ while ((what & 1) && (utx = getutxent()) != NULL) {
+ if (fname == NULL && ((1 << utx->ut_type) & etype) == 0)
+ continue;
+ if ((ep = calloc(1, sizeof(struct utmpentry))) == NULL) {
+ warn(NULL);
+ return 0;
+ }
+ getentryx(ep, utx);
+ *nextp = ep;
+ nextp = &(ep->next);
+ }
+#endif
+
+#ifdef SUPPORT_UTMP
+ if ((etype & (1 << USER_PROCESS)) != 0) {
+ while ((what & 2) && (ut = getutent()) != NULL) {
+ if (fname == NULL && (*ut->ut_name == '\0' ||
+ *ut->ut_line == '\0'))
+ continue;
+ /* Don't process entries that we have utmpx for */
+ for (ep = ehead; ep != NULL; ep = ep->next) {
+ if (strncmp(ep->line, ut->ut_line,
+ sizeof(ut->ut_line)) == 0)
+ break;
+ }
+ if (ep != NULL)
+ continue;
+ if ((ep = calloc(1, sizeof(*ep))) == NULL) {
+ warn(NULL);
+ return 0;
+ }
+ getentry(ep, ut);
+ *nextp = ep;
+ nextp = &(ep->next);
+ }
+ }
+#endif
+ numutmp = 0;
+#if defined(SUPPORT_UTMP) || defined(SUPPORT_UTMPX)
+ if (ehead != NULL) {
+ struct utmpentry *from = ehead, *save;
+
+ ehead = NULL;
+ while (from != NULL) {
+ for (nextp = &ehead;
+ (*nextp) && strcmp(from->line, (*nextp)->line) > 0;
+ nextp = &(*nextp)->next)
+ continue;
+ save = from;
+ from = from->next;
+ save->next = *nextp;
+ *nextp = save;
+ numutmp++;
+ }
+ }
+ *epp = ehead;
+ return numutmp;
+#else
+ *epp = NULL;
+ return 0;
+#endif
+}
+
+#ifdef SUPPORT_UTMP
+static void
+getentry(struct utmpentry *e, struct utmp *up)
+{
+ COMPILE_ASSERT(sizeof(e->name) > sizeof(up->ut_name));
+ COMPILE_ASSERT(sizeof(e->line) > sizeof(up->ut_line));
+ COMPILE_ASSERT(sizeof(e->host) > sizeof(up->ut_host));
+
+ /*
+ * e has just been calloc'd. We don't need to clear it or
+ * append null-terminators, because its length is strictly
+ * greater than the source string. Use strncpy to _read_
+ * up->ut_* because they may not be terminated. For this
+ * reason we use the size of the _source_ as the length
+ * argument.
+ */
+ (void)strncpy(e->name, up->ut_name, sizeof(up->ut_name));
+ (void)strncpy(e->line, up->ut_line, sizeof(up->ut_line));
+ (void)strncpy(e->host, up->ut_host, sizeof(up->ut_host));
+
+ e->tv.tv_sec = up->ut_time;
+ e->tv.tv_usec = 0;
+ e->pid = 0;
+ e->term = 0;
+ e->exit = 0;
+ e->sess = 0;
+ e->type = USER_PROCESS;
+ adjust_size(e);
+}
+#endif
+
+#ifdef SUPPORT_UTMPX
+static void
+getentryx(struct utmpentry *e, struct utmpx *up)
+{
+ COMPILE_ASSERT(sizeof(e->name) > sizeof(up->ut_name));
+ COMPILE_ASSERT(sizeof(e->line) > sizeof(up->ut_line));
+ COMPILE_ASSERT(sizeof(e->host) > sizeof(up->ut_host));
+
+ /*
+ * e has just been calloc'd. We don't need to clear it or
+ * append null-terminators, because its length is strictly
+ * greater than the source string. Use strncpy to _read_
+ * up->ut_* because they may not be terminated. For this
+ * reason we use the size of the _source_ as the length
+ * argument.
+ */
+ (void)strncpy(e->name, up->ut_name, sizeof(up->ut_name));
+ (void)strncpy(e->line, up->ut_line, sizeof(up->ut_line));
+ (void)strncpy(e->host, up->ut_host, sizeof(up->ut_host));
+
+ e->tv = up->ut_tv;
+ e->pid = up->ut_pid;
+ e->term = up->ut_exit.e_termination;
+ e->exit = up->ut_exit.e_exit;
+ e->sess = up->ut_session;
+ e->type = up->ut_type;
+ adjust_size(e);
+}
+#endif
diff --git a/usr.bin/who/utmpentry.h b/usr.bin/who/utmpentry.h
new file mode 100644
index 0000000..263a33a
--- /dev/null
+++ b/usr.bin/who/utmpentry.h
@@ -0,0 +1,76 @@
+/* $NetBSD: utmpentry.h,v 1.8 2015/11/21 15:01:43 christos Exp $ */
+
+/*-
+ * Copyright (c) 2002 The NetBSD Foundation, Inc.
+ * All rights reserved.
+ *
+ * This code is derived from software contributed to The NetBSD Foundation
+ * by Christos Zoulas.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
+ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#if defined(SUPPORT_UTMPX)
+# include <utmpx.h>
+# define WHO_NAME_LEN _UTX_USERSIZE
+# define WHO_LINE_LEN _UTX_LINESIZE
+# define WHO_HOST_LEN _UTX_HOSTSIZE
+#elif defined(SUPPORT_UTMP)
+# include <utmp.h>
+# define WHO_NAME_LEN UT_NAMESIZE
+# define WHO_LINE_LEN UT_LINESIZE
+# define WHO_HOST_LEN UT_HOSTSIZE
+#else
+# error Either SUPPORT_UTMPX or SUPPORT_UTMP must be defined!
+#endif
+
+
+struct utmpentry {
+ char name[WHO_NAME_LEN + 1];
+ char line[WHO_LINE_LEN + 1];
+ char host[WHO_HOST_LEN + 1];
+ struct timeval tv;
+ pid_t pid;
+ uint16_t term;
+ uint16_t exit;
+ uint16_t sess;
+ uint16_t type;
+ struct utmpentry *next;
+};
+
+extern size_t maxname, maxline, maxhost;
+extern int etype;
+
+/*
+ * getutentries provides a linked list of struct utmpentry and returns
+ * the number of entries. The first argument, if not null, names an
+ * alternate utmp(x) file to look in.
+ *
+ * The memory returned by getutentries belongs to getutentries. The
+ * list returned (or elements of it) may be returned again later if
+ * utmp hasn't changed in the meantime.
+ *
+ * endutentries clears and frees the cached data.
+ */
+
+size_t getutentries(const char *, struct utmpentry **);
+void endutentries(void);
diff --git a/usr.bin/who/who.1 b/usr.bin/who/who.1
new file mode 100644
index 0000000..3873b47
--- /dev/null
+++ b/usr.bin/who/who.1
@@ -0,0 +1,197 @@
+.\" $NetBSD: who.1,v 1.24 2018/05/11 16:36:57 sevan Exp $
+.\"
+.\" Copyright (c) 1986, 1991, 1993
+.\" The Regents of the University of California. All rights reserved.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\" notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must reproduce the above copyright
+.\" notice, this list of conditions and the following disclaimer in the
+.\" documentation and/or other materials provided with the distribution.
+.\" 3. Neither the name of the University nor the names of its contributors
+.\" may be used to endorse or promote products derived from this software
+.\" without specific prior written permission.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+.\" ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+.\" SUCH DAMAGE.
+.\"
+.\" @(#)who.1 8.2 (Berkeley) 12/30/93
+.\"
+.Dd May 11, 2018
+.Dt WHO 1
+.Os
+.Sh NAME
+.Nm who
+.Nd display who is logged in
+.Sh SYNOPSIS
+.Nm
+.Op Fl abdHlmqrsTtuv
+.Op Ar file
+.Nm
+.Ar am i
+.Sh DESCRIPTION
+The
+.Nm
+utility displays a list of all users currently logged on, showing for
+each user the login name, tty name, the date and time of login, and
+hostname if not local.
+.Pp
+Available options:
+.Bl -tag -width file
+.It Fl a
+Same as
+.Fl -bdlprTtuv .
+.It Fl b
+Time of last system boot.
+.It Fl d
+Print dead processes.
+.It Fl H
+Write column headings above the regular output.
+.It Fl l
+Print system login processes.
+.It Fl m
+Only print information about the current terminal.
+This is the POSIX way of saying
+.Nm
+.Ar am i .
+.It Fl p
+Print active processes spawned by
+.Xr init 8 .
+.It Fl q
+.Dq Quick mode :
+List only the names and the number of users currently logged on.
+When this option is used, all other options are ignored.
+.It Fl r
+Print the current runlevel.
+Supported runlevels are:
+.Bl -tag -width "s (SINGLE_USER)"
+.It Dv d Pq Dv DEATH
+The system has halted.
+.It Dv s Pq Dv SINGLE_USER
+The system is running in single user mode.
+.It Dv r Pq Dv RUNCOM
+The system is executing
+.Pa /etc/rc .
+.It Dv t Pq Dv READ_TTYS
+The system is processing
+.Pa /etc/ttys .
+.It Dv m Pq Dv MULTI_USER
+The system is running in multi-user mode.
+.It Dv T Pq Dv CLEAN_TTYS
+The system is in the process of stopping processes
+associated with terminal devices.
+.It Dv c Pq Dv CATATONIA
+The system is in the process of shutting down and will
+not create new processes.
+.El
+.It Fl s
+List only the name, line and time fields.
+This is the default.
+.It Fl T
+Print a character after the user name indicating the state of the
+terminal line:
+.Sq +
+if the terminal is writable;
+.Sq -
+if it is not;
+and
+.Sq \&?
+if a bad line is encountered.
+.It Fl t
+Print last system clock change.
+.It Fl u
+Print the idle time for each user, and the associated process ID.
+.It Fl v
+When printing of more information is requested with
+.Fl u ,
+this switch can be used to also printed
+process termination signals,
+process exit status,
+session id for windowing
+and the type of the entry, see documentation of ut_type in
+.Xr getutxent 3 .
+.It Ar \&am I
+Returns the invoker's real user name.
+.It Ar file
+By default,
+.Nm
+gathers information from the file
+.Pa /var/run/utmpx .
+An alternative
+.Ar file
+may be specified which is usually
+.Pa /var/log/wtmpx
+(or
+.Pa /var/log/wtmp ,
+or
+.Pa /var/log/wtmpx.[0-6]
+or
+.Pa /var/log/wtmp.[0-6]
+depending on site policy as
+.Pa wtmpx
+can grow quite large and daily versions may or may not
+be kept around after compression by
+.Xr ac 8 ) .
+The
+.Pa wtmpx
+and
+.Pa wtmp
+file contains a record of every login, logout,
+crash, shutdown and date change
+since
+.Pa wtmpx
+and
+.Pa wtmp
+were last truncated or
+created.
+.El
+.Pp
+If
+.Pa /var/log/wtmpx
+or
+.Pa /var/log/wtmp
+are being used as the file, the user name may be empty
+or one of the special characters '|', '}' and '~'.
+Logouts produce an output line without any user name.
+For more information on the
+special characters, see
+.Xr utmp 5 .
+.Sh FILES
+.Bl -tag -width /var/log/wtmp.[0-6] -compact
+.It Pa /var/run/utmp
+.It Pa /var/run/utmpx
+.It Pa /var/log/wtmp
+.It Pa /var/log/wtmp.[0-6]
+.It Pa /var/log/wtmpx
+.It Pa /var/log/wtmpx.[0-6]
+.El
+.Sh SEE ALSO
+.Xr last 1 ,
+.Xr mesg 1 ,
+.Xr users 1 ,
+.Xr getuid 2 ,
+.Xr utmp 5 ,
+.Xr utmpx 5
+.Sh STANDARDS
+The
+.Nm
+utility is expected to conform to
+.St -p1003.2-92 .
+.Sh HISTORY
+A
+.Nm
+utility appeared in
+.At v1 :
+.Lk http://cm.bell-labs.com/cm/cs/who/dmr/pdfs/man14.pdf
diff --git a/usr.bin/who/who.c b/usr.bin/who/who.c
new file mode 100644
index 0000000..f0c6564
--- /dev/null
+++ b/usr.bin/who/who.c
@@ -0,0 +1,391 @@
+/* $NetBSD: who.c,v 1.25 2015/11/21 15:01:43 christos Exp $ */
+
+/*
+ * Copyright (c) 1989, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Michael Fischbein.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+#ifndef lint
+__COPYRIGHT("@(#) Copyright (c) 1989, 1993\
+ The Regents of the University of California. All rights reserved.");
+#endif /* not lint */
+
+#ifndef lint
+#if 0
+static char sccsid[] = "@(#)who.c 8.1 (Berkeley) 6/6/93";
+#endif
+__RCSID("$NetBSD: who.c,v 1.25 2015/11/21 15:01:43 christos Exp $");
+#endif /* not lint */
+
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#include <err.h>
+#include <locale.h>
+#include <pwd.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <unistd.h>
+#ifdef SUPPORT_UTMP
+#include <utmp.h>
+#endif
+#ifdef SUPPORT_UTMPX
+#include <utmpx.h>
+#endif
+
+#include "utmpentry.h"
+
+static void output_labels(void);
+static void who_am_i(const char *, int);
+static void usage(void) __dead;
+static void process(const char *, int);
+static void eprint(const struct utmpentry *);
+static void print(const char *, const char *, time_t, const char *, pid_t pid,
+ uint16_t term, uint16_t xit, uint16_t sess, uint16_t type);
+static void quick(const char *);
+
+static int show_term; /* show term state */
+static int show_idle; /* show idle time */
+static int show_details; /* show exit status etc. */
+
+struct ut_type_names {
+ int type;
+ const char *name;
+} ut_type_names[] = {
+#ifdef SUPPORT_UTMPX
+ { EMPTY, "empty" },
+ { RUN_LVL, "run level" },
+ { BOOT_TIME, "boot time" },
+ { OLD_TIME, "old time" },
+ { NEW_TIME, "new time" },
+ { INIT_PROCESS, "init process" },
+ { LOGIN_PROCESS, "login process" },
+ { USER_PROCESS, "user process" },
+ { DEAD_PROCESS, "dead process" },
+#if defined(_NETBSD_SOURCE)
+ { ACCOUNTING, "accounting" },
+ { SIGNATURE, "signature" },
+ { DOWN_TIME, "down time" },
+#endif /* _NETBSD_SOURCE */
+#endif /* SUPPORT_UTMPX */
+ { -1, "unknown" }
+};
+
+int
+main(int argc, char *argv[])
+{
+ int c, only_current_term, show_labels, quick_mode, default_mode;
+ int et = 0;
+
+ setlocale(LC_ALL, "");
+
+ only_current_term = show_term = show_idle = show_labels = 0;
+ quick_mode = default_mode = 0;
+
+ while ((c = getopt(argc, argv, "abdHlmpqrsTtuv")) != -1) {
+ switch (c) {
+ case 'a':
+ et = -1;
+ show_idle = show_details = 1;
+ break;
+ case 'b':
+ et |= (1 << BOOT_TIME);
+ break;
+ case 'd':
+ et |= (1 << DEAD_PROCESS);
+ break;
+ case 'H':
+ show_labels = 1;
+ break;
+ case 'l':
+ et |= (1 << LOGIN_PROCESS);
+ break;
+ case 'm':
+ only_current_term = 1;
+ break;
+ case 'p':
+ et |= (1 << INIT_PROCESS);
+ break;
+ case 'q':
+ quick_mode = 1;
+ break;
+ case 'r':
+ et |= (1 << RUN_LVL);
+ break;
+ case 's':
+ default_mode = 1;
+ break;
+ case 'T':
+ show_term = 1;
+ break;
+ case 't':
+ et |= (1 << NEW_TIME);
+ break;
+ case 'u':
+ show_idle = 1;
+ break;
+ case 'v':
+ show_details = 1;
+ break;
+ default:
+ usage();
+ /* NOTREACHED */
+ }
+ }
+ argc -= optind;
+ argv += optind;
+
+ if (et != 0)
+ etype = et;
+
+ if (chdir("/dev")) {
+ err(EXIT_FAILURE, "cannot change directory to /dev");
+ /* NOTREACHED */
+ }
+
+ if (default_mode)
+ only_current_term = show_term = show_idle = 0;
+
+ switch (argc) {
+ case 0: /* who */
+ if (quick_mode) {
+ quick(NULL);
+ } else if (only_current_term) {
+ who_am_i(NULL, show_labels);
+ } else {
+ process(NULL, show_labels);
+ }
+ break;
+ case 1: /* who utmp_file */
+ if (quick_mode) {
+ quick(*argv);
+ } else if (only_current_term) {
+ who_am_i(*argv, show_labels);
+ } else {
+ process(*argv, show_labels);
+ }
+ break;
+ case 2: /* who am i */
+ who_am_i(NULL, show_labels);
+ break;
+ default:
+ usage();
+ /* NOTREACHED */
+ }
+
+ return 0;
+}
+
+static char *
+strrstr(const char *str, const char *pat)
+{
+ const char *estr;
+ size_t len;
+ if (*pat == '\0')
+ return __UNCONST(str);
+
+ len = strlen(pat);
+
+ for (estr = str + strlen(str); str < estr; estr--)
+ if (strncmp(estr, pat, len) == 0)
+ return __UNCONST(estr);
+ return NULL;
+}
+
+static void
+who_am_i(const char *fname, int show_labels)
+{
+ struct passwd *pw;
+ const char *p;
+ char *t;
+ time_t now;
+ struct utmpentry *ehead, *ep;
+
+ /* search through the utmp and find an entry for this tty */
+ if ((p = ttyname(STDIN_FILENO)) != NULL) {
+
+ /* strip directory prefixes for ttys */
+ if ((t = strrstr(p, "/pts/")) != NULL ||
+ (t = strrchr(p, '/')) != NULL)
+ p = t + 1;
+
+ (void)getutentries(fname, &ehead);
+ for (ep = ehead; ep; ep = ep->next)
+ if (strcmp(ep->line, p) == 0) {
+ if (show_labels)
+ output_labels();
+ eprint(ep);
+ return;
+ }
+ } else
+ p = "tty??";
+
+ (void)time(&now);
+ pw = getpwuid(getuid());
+ if (show_labels)
+ output_labels();
+ print(pw ? pw->pw_name : "?", p, now, "", getpid(), 0, 0, 0, 0);
+}
+
+static void
+process(const char *fname, int show_labels)
+{
+ struct utmpentry *ehead, *ep;
+ (void)getutentries(fname, &ehead);
+ if (show_labels)
+ output_labels();
+ for (ep = ehead; ep != NULL; ep = ep->next)
+ eprint(ep);
+}
+
+static void
+eprint(const struct utmpentry *ep)
+{
+ print(ep->name, ep->line, (time_t)ep->tv.tv_sec, ep->host, ep->pid,
+ ep->term, ep->exit, ep->sess, ep->type);
+}
+
+static void
+print(const char *name, const char *line, time_t t, const char *host,
+ pid_t pid, uint16_t term, uint16_t xit, uint16_t sess, uint16_t type)
+{
+ struct stat sb;
+ char state;
+ static time_t now = 0;
+ time_t idle;
+ const char *types = NULL;
+ size_t i;
+ char *tstr;
+
+ state = '?';
+ idle = 0;
+
+ for (i = 0; ut_type_names[i].type >= 0; i++) {
+ types = ut_type_names[i].name;
+ if (ut_type_names[i].type == type)
+ break;
+ }
+
+ if (show_term || show_idle) {
+ if (now == 0)
+ time(&now);
+
+ if (stat(line, &sb) == 0) {
+ state = (sb.st_mode & 020) ? '+' : '-';
+ idle = now - sb.st_atime;
+ }
+
+ }
+
+ (void)printf("%-*.*s ", (int)maxname, (int)maxname, name);
+
+ if (show_term)
+ (void)printf("%c ", state);
+
+ (void)printf("%-*.*s ", (int)maxline, (int)maxline, line);
+ tstr = ctime(&t);
+ (void)printf("%.12s ", tstr ? tstr + 4 : "?");
+
+ if (show_idle) {
+ if (idle < 60)
+ (void)printf(" . ");
+ else if (idle < (24 * 60 * 60))
+ (void)printf("%02ld:%02ld ",
+ (long)(idle / (60 * 60)),
+ (long)(idle % (60 * 60)) / 60);
+ else
+ (void)printf(" old ");
+
+ (void)printf("\t%6d", pid);
+
+ if (show_details) {
+ if (type == RUN_LVL)
+ (void)printf("\tnew=%c old=%c", term, xit);
+ else
+ (void)printf("\tterm=%d exit=%d", term, xit);
+ (void)printf(" sess=%d", sess);
+ (void)printf(" type=%s ", types);
+ }
+ }
+
+ if (*host)
+ (void)printf("\t(%.*s)", (int)maxhost, host);
+ (void)putchar('\n');
+}
+
+static void
+output_labels(void)
+{
+ (void)printf("%-*.*s ", (int)maxname, (int)maxname, "USER");
+
+ if (show_term)
+ (void)printf("S ");
+
+ (void)printf("%-*.*s ", (int)maxline, (int)maxline, "LINE");
+ (void)printf("WHEN ");
+
+ if (show_idle) {
+ (void)printf("IDLE ");
+ (void)printf("\t PID");
+
+ (void)printf("\tCOMMENT");
+ }
+
+ (void)putchar('\n');
+}
+
+static void
+quick(const char *fname)
+{
+ struct utmpentry *ehead, *ep;
+ int num = 0;
+
+ (void)getutentries(fname, &ehead);
+ for (ep = ehead; ep != NULL; ep = ep->next) {
+ (void)printf("%-*s ", (int)maxname, ep->name);
+ if ((++num % 8) == 0)
+ (void)putchar('\n');
+ }
+ if (num % 8)
+ (void)putchar('\n');
+
+ (void)printf("# users = %d\n", num);
+}
+
+static void
+usage(void)
+{
+ (void)fprintf(stderr, "Usage: %s [-abdHlmqrsTtuv] [file]\n\t%s am i\n",
+ getprogname(), getprogname());
+ exit(EXIT_FAILURE);
+}
diff --git a/usr.bin/write/term_chk.c b/usr.bin/write/term_chk.c
new file mode 100644
index 0000000..d7d2cbe
--- /dev/null
+++ b/usr.bin/write/term_chk.c
@@ -0,0 +1,137 @@
+/* $NetBSD: term_chk.c,v 1.8 2009/04/14 07:59:17 lukem Exp $ */
+
+/*
+ * Copyright (c) 1989, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Jef Poskanzer and Craig Leres of the Lawrence Berkeley Laboratory.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+#ifndef lint
+__RCSID("$NetBSD: term_chk.c,v 1.8 2009/04/14 07:59:17 lukem Exp $");
+#endif
+
+#include <sys/types.h>
+#include <sys/param.h>
+#include <sys/stat.h>
+#include <time.h>
+#include <stdio.h>
+#include <errno.h>
+#include <unistd.h>
+#include <paths.h>
+#include <fcntl.h>
+#include <string.h>
+#include <err.h>
+
+#include "term_chk.h"
+
+/*
+ * term_chk - check that a terminal exists, and get the message bit
+ * and the access time
+ */
+int
+term_chk(uid_t uid, const char *tty, int *msgsokP, time_t *atimeP, int ismytty,
+ gid_t saved_egid)
+{
+ char path[MAXPATHLEN];
+ struct stat s;
+ int i, fd, serrno;
+
+ if (strstr(tty, "../") != NULL) {
+ errno = EINVAL;
+ return -1;
+ }
+ i = snprintf(path, sizeof path, _PATH_DEV "%s", tty);
+ if (i < 0 || i >= (int)sizeof(path)) {
+ errno = ENOMEM;
+ return -1;
+ }
+
+ (void)setegid(saved_egid);
+ fd = open(path, O_WRONLY, 0);
+ serrno = errno;
+ (void)setegid(getgid());
+ errno = serrno;
+
+ if (fd == -1)
+ return(-1);
+ if (fstat(fd, &s) == -1)
+ goto error;
+ if (!isatty(fd))
+ goto error;
+ if (s.st_uid != uid && uid != 0) {
+ errno = EPERM;
+ goto error;
+ }
+ if (msgsokP)
+ *msgsokP = (s.st_mode & S_IWGRP) != 0; /* group write bit */
+ if (atimeP)
+ *atimeP = s.st_atime;
+ if (ismytty)
+ (void)close(fd);
+ return ismytty ? 0 : fd;
+error:
+ if (fd != -1) {
+ serrno = errno;
+ (void)close(fd);
+ errno = serrno;
+ }
+ return -1;
+}
+
+char *
+check_sender(time_t *atime, uid_t myuid, gid_t saved_egid)
+{
+ int myttyfd;
+ int msgsok;
+ char *mytty;
+
+ /* check that sender has write enabled */
+ if (isatty(fileno(stdin)))
+ myttyfd = fileno(stdin);
+ else if (isatty(fileno(stdout)))
+ myttyfd = fileno(stdout);
+ else if (isatty(fileno(stderr)))
+ myttyfd = fileno(stderr);
+ else if (atime == NULL)
+ return NULL;
+ else
+ errx(1, "Cannot find your tty");
+ if ((mytty = ttyname(myttyfd)) == NULL)
+ err(1, "Cannot find the name of your tty");
+ if (strncmp(mytty, _PATH_DEV, sizeof(_PATH_DEV) - 1) == 0)
+ mytty += sizeof(_PATH_DEV) - 1;
+ if (term_chk(myuid, mytty, &msgsok, atime, 1, saved_egid) == -1)
+ err(1, "%s%s", _PATH_DEV, mytty);
+ if (!msgsok) {
+ warnx(
+ "You have write permission turned off; no reply possible");
+ }
+ return mytty;
+}
diff --git a/usr.bin/write/term_chk.h b/usr.bin/write/term_chk.h
new file mode 100644
index 0000000..aca4568
--- /dev/null
+++ b/usr.bin/write/term_chk.h
@@ -0,0 +1,36 @@
+/* $NetBSD: term_chk.h,v 1.2 2003/08/07 11:17:48 agc Exp $ */
+
+/*
+ * Copyright (c) 1989, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Jef Poskanzer and Craig Leres of the Lawrence Berkeley Laboratory.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+int term_chk(uid_t, const char *, int *, time_t *, int, gid_t);
+char *check_sender(time_t *, uid_t, gid_t);
diff --git a/usr.bin/write/write.1 b/usr.bin/write/write.1
new file mode 100644
index 0000000..28d614c
--- /dev/null
+++ b/usr.bin/write/write.1
@@ -0,0 +1,106 @@
+.\" $NetBSD: write.1,v 1.6 2003/08/07 11:17:48 agc Exp $
+.\"
+.\" Copyright (c) 1989, 1993
+.\" The Regents of the University of California. All rights reserved.
+.\"
+.\" This code is derived from software contributed to Berkeley by
+.\" Jef Poskanzer and Craig Leres of the Lawrence Berkeley Laboratory.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\" notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must reproduce the above copyright
+.\" notice, this list of conditions and the following disclaimer in the
+.\" documentation and/or other materials provided with the distribution.
+.\" 3. Neither the name of the University nor the names of its contributors
+.\" may be used to endorse or promote products derived from this software
+.\" without specific prior written permission.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+.\" ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+.\" SUCH DAMAGE.
+.\"
+.\" from: @(#)write.1 8.1 (Berkeley) 6/6/93
+.\"
+.Dd June 6, 1993
+.Dt WRITE 1
+.Os
+.Sh NAME
+.Nm write
+.Nd send a message to another user
+.Sh SYNOPSIS
+.Nm
+.Ar user
+.Op Ar ttyname
+.Sh DESCRIPTION
+.Nm
+allows you to communicate with other users, by copying lines from
+your terminal to theirs.
+.Pp
+When you run the
+.Nm
+command, the user you are writing to gets a message of the form:
+.Pp
+.Dl Message from yourname@yourhost on yourtty at hh:mm ...
+.Pp
+Any further lines you enter will be copied to the specified user's
+terminal.
+If the other user wants to reply, they must run
+.Nm
+as well.
+.Pp
+When you are done, type an end-of-file or interrupt character.
+The other user will see the message
+.Ql EOF
+indicating that the
+conversation is over.
+.Pp
+You can prevent people (other than the super-user) from writing to you
+with the
+.Xr mesg 1
+command.
+Some commands, for example
+.Xr nroff 1
+and
+.Xr pr 1 ,
+disallow writing automatically, so that your output isn't overwritten.
+.Pp
+If the user you want to write to is logged in on more than one terminal,
+you can specify which terminal to write to by specifying the terminal
+name as the second operand to the
+.Nm
+command.
+Alternatively, you can let
+.Nm
+select one of the terminals \- it will pick the one with the shortest
+idle time.
+This is so that if the user is logged in at work and also dialed up from
+home, the message will go to the right place.
+.Pp
+The traditional protocol for writing to someone is that the string
+.Ql \-o ,
+either at the end of a line or on a line by itself, means that it's the
+other person's turn to talk.
+The string
+.Ql oo
+means that the person believes the conversation to be
+over.
+.Sh SEE ALSO
+.Xr mesg 1 ,
+.Xr talk 1 ,
+.Xr who 1
+.Sh HISTORY
+A
+.Nm
+command appeared in
+.At v6 .
diff --git a/usr.bin/write/write.c b/usr.bin/write/write.c
new file mode 100644
index 0000000..945e92f
--- /dev/null
+++ b/usr.bin/write/write.c
@@ -0,0 +1,292 @@
+/* $NetBSD: write.c,v 1.27 2011/09/06 18:46:35 joerg Exp $ */
+
+/*
+ * Copyright (c) 1989, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Jef Poskanzer and Craig Leres of the Lawrence Berkeley Laboratory.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+#ifndef lint
+__COPYRIGHT("@(#) Copyright (c) 1989, 1993\
+ The Regents of the University of California. All rights reserved.");
+#endif /* not lint */
+
+#ifndef lint
+#if 0
+static char sccsid[] = "@(#)write.c 8.2 (Berkeley) 4/27/95";
+#else
+__RCSID("$NetBSD: write.c,v 1.27 2011/09/06 18:46:35 joerg Exp $");
+#endif
+#endif /* not lint */
+
+#include <sys/types.h>
+#include <sys/param.h>
+#include <sys/stat.h>
+#include <ctype.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <signal.h>
+#include <time.h>
+#include <fcntl.h>
+#include <paths.h>
+#include <pwd.h>
+#include <unistd.h>
+#include <err.h>
+#include <errno.h>
+
+#include "utmpentry.h"
+#include "term_chk.h"
+
+__dead static void done(int);
+static void do_write(int, const char *, const uid_t);
+static void wr_fputs(char *);
+static int search_utmp(char *, char *, uid_t, gid_t);
+static int utmp_chk(const char *, const char *);
+
+int
+main(int argc, char **argv)
+{
+ time_t atime;
+ uid_t myuid, uid;
+ int msgsok, ttyfd;
+ char *mytty;
+ gid_t saved_egid = getegid();
+
+ if (setegid(getgid()) == -1)
+ err(1, "setegid");
+ myuid = getuid();
+ ttyfd = -1;
+
+ mytty = check_sender(&atime, myuid, saved_egid);
+
+ /* check args */
+ switch (argc) {
+ case 2:
+ ttyfd = search_utmp(argv[1], mytty, myuid, saved_egid);
+ break;
+ case 3:
+ if (!strncmp(argv[2], _PATH_DEV, strlen(_PATH_DEV)))
+ argv[2] += strlen(_PATH_DEV);
+ if (uid_from_user(argv[1], &uid) == -1)
+ errx(1, "%s: unknown user", argv[1]);
+ if (utmp_chk(argv[1], argv[2]))
+ errx(1, "%s is not logged in on %s",
+ argv[1], argv[2]);
+ ttyfd = term_chk(uid, argv[2], &msgsok, &atime, 0, saved_egid);
+ if (ttyfd == -1)
+ err(1, "%s%s", _PATH_DEV, argv[2]);
+ if (myuid && !msgsok)
+ errx(1, "%s has messages disabled on %s",
+ argv[1], argv[2]);
+ break;
+ default:
+ (void)fprintf(stderr, "usage: write user [tty]\n");
+ exit(1);
+ }
+ if (setgid(getgid()) == -1)
+ err(1, "setgid");
+ do_write(ttyfd, mytty, myuid);
+ done(0);
+ /* NOTREACHED */
+#ifdef __GNUC__
+ return (0);
+#endif
+}
+
+/*
+ * utmp_chk - checks that the given user is actually logged in on
+ * the given tty
+ */
+static int
+utmp_chk(const char *user, const char *tty)
+{
+ struct utmpentry *ep;
+
+ (void)getutentries(NULL, &ep);
+
+ for (; ep; ep = ep->next)
+ if (strcmp(user, ep->name) == 0 && strcmp(tty, ep->line) == 0)
+ return(0);
+ return(1);
+}
+
+/*
+ * search_utmp - search utmp for the "best" terminal to write to
+ *
+ * Ignores terminals with messages disabled, and of the rest, returns
+ * the one with the most recent access time. Returns as value the number
+ * of the user's terminals with messages enabled, or -1 if the user is
+ * not logged in at all.
+ *
+ * Special case for writing to yourself - ignore the terminal you're
+ * writing from, unless that's the only terminal with messages enabled.
+ */
+static int
+search_utmp(char *user, char *mytty, uid_t myuid, gid_t saved_egid)
+{
+ char tty[MAXPATHLEN];
+ time_t bestatime, atime;
+ int nloggedttys, nttys, msgsok, user_is_me;
+ struct utmpentry *ep;
+ int fd, nfd;
+ uid_t uid;
+
+ if (uid_from_user(user, &uid) == -1)
+ errx(1, "%s: unknown user", user);
+
+ (void)getutentries(NULL, &ep);
+
+ nloggedttys = nttys = 0;
+ bestatime = 0;
+ user_is_me = 0;
+ fd = -1;
+ for (; ep; ep = ep->next)
+ if (strcmp(user, ep->name) == 0) {
+ ++nloggedttys;
+ nfd = term_chk(uid, ep->line, &msgsok, &atime, 0,
+ saved_egid);
+ if (nfd == -1)
+ continue; /* bad term? skip */
+ if (myuid && !msgsok) {
+ close(nfd);
+ continue; /* skip ttys with msgs off */
+ }
+ if (strcmp(ep->line, mytty) == 0) {
+ user_is_me = 1;
+ if (fd == -1)
+ fd = nfd;
+ else
+ close(nfd);
+ continue; /* don't write to yourself */
+ }
+ ++nttys;
+ if (atime > bestatime) {
+ bestatime = atime;
+ (void)strlcpy(tty, ep->line, sizeof(tty));
+ close(fd);
+ fd = nfd;
+ } else
+ close(nfd);
+ }
+
+ if (nloggedttys == 0)
+ errx(1, "%s is not logged in", user);
+ if (nttys == 0) {
+ if (user_is_me) /* ok, so write to yourself! */
+ return fd;
+ errx(1, "%s has messages disabled", user);
+ } else if (nttys > 1)
+ warnx("%s is logged in more than once; writing to %s",
+ user, tty);
+ return fd;
+}
+
+/*
+ * do_write - actually make the connection
+ */
+static void
+do_write(int ttyfd, const char *mytty, const uid_t myuid)
+{
+ const char *login;
+ char *nows;
+ struct passwd *pwd;
+ time_t now;
+ char host[MAXHOSTNAMELEN + 1], line[512];
+
+ /* Determine our login name before we re-open stdout */
+ if ((login = getlogin()) == NULL) {
+ if ((pwd = getpwuid(myuid)) != NULL)
+ login = pwd->pw_name;
+ else login = "???";
+ }
+
+ if (dup2(ttyfd, STDOUT_FILENO) == -1)
+ err(1, "dup2");
+
+ (void)signal(SIGINT, done);
+ (void)signal(SIGHUP, done);
+ (void)close(ttyfd);
+
+ /* print greeting */
+ if (gethostname(host, sizeof(host)) < 0)
+ (void)strlcpy(host, "???", sizeof(host));
+ else
+ host[sizeof(host) - 1] = '\0';
+ now = time(NULL);
+ nows = ctime(&now);
+ nows[16] = '\0';
+ (void)printf("\r\n\a\a\aMessage from %s@%s on %s at %s ...\r\n",
+ login, host, mytty, nows + 11);
+
+ while (fgets(line, sizeof(line), stdin) != NULL)
+ wr_fputs(line);
+}
+
+/*
+ * done - cleanup and exit
+ */
+static void
+done(int signo)
+{
+
+ (void)write(STDOUT_FILENO, "EOF\r\n", sizeof("EOF\r\n") - 1);
+ if (signo == 0)
+ exit(0);
+ else
+ _exit(0);
+}
+
+/*
+ * wr_fputs - like fputs(), but makes control characters visible and
+ * turns \n into \r\n
+ */
+static void
+wr_fputs(char *s)
+{
+ unsigned char c;
+
+#define PUTC(c) if (putchar(c) == EOF) goto err;
+
+ for (; *s != '\0'; ++s) {
+ c = toascii(*s);
+ if (c == '\n') {
+ PUTC('\r');
+ } else if (!isprint(c) && !isspace(c) && c != '\a') {
+ PUTC('^');
+ c ^= 0x40; /* DEL to ?, others to alpha */
+ }
+ PUTC(c);
+ }
+ return;
+
+err: err(1, NULL);
+#undef PUTC
+}
diff --git a/usr.bin/xargs/pathnames.h b/usr.bin/xargs/pathnames.h
new file mode 100644
index 0000000..3f05b3d
--- /dev/null
+++ b/usr.bin/xargs/pathnames.h
@@ -0,0 +1,36 @@
+/*-
+ * Copyright (c) 1990, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. All advertising materials mentioning features or use of this software
+ * must display the following acknowledgement:
+ * This product includes software developed by the University of
+ * California, Berkeley and its contributors.
+ * 4. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * @(#)pathnames.h 8.1 (Berkeley) 6/6/93
+ */
+
+#define _PATH_ECHO "/bin/echo"
diff --git a/usr.bin/xargs/strnsubst.c b/usr.bin/xargs/strnsubst.c
new file mode 100644
index 0000000..0953f49
--- /dev/null
+++ b/usr.bin/xargs/strnsubst.c
@@ -0,0 +1,115 @@
+/* $xMach: strnsubst.c,v 1.3 2002/02/23 02:10:24 jmallett Exp $ */
+
+/*
+ * Copyright (c) 2002 J. Mallett. All rights reserved.
+ * You may do whatever you want with this file as long as
+ * the above copyright and this notice remain intact, along
+ * with the following statement:
+ * For the man who taught me vi, and who got too old, too young.
+ */
+
+#include <sys/cdefs.h>
+#ifndef lint
+#if 0
+__FBSDID("$FreeBSD: src/usr.bin/xargs/strnsubst.c,v 1.8 2005/12/30 23:22:50 jmallett Exp $");
+#endif
+__RCSID("$NetBSD: strnsubst.c,v 1.1 2007/04/18 15:56:07 christos Exp $");
+#endif /* not lint */
+
+#include <err.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <stdint.h>
+
+void strnsubst(char **, const char *, const char *, size_t);
+
+/*
+ * Replaces str with a string consisting of str with match replaced with
+ * replstr as many times as can be done before the constructed string is
+ * maxsize bytes large. It does not free the string pointed to by str, it
+ * is up to the calling program to be sure that the original contents of
+ * str as well as the new contents are handled in an appropriate manner.
+ * If replstr is NULL, then that internally is changed to a nil-string, so
+ * that we can still pretend to do somewhat meaningful substitution.
+ * No value is returned.
+ */
+void
+strnsubst(char **str, const char *match, const char *replstr, size_t maxsize)
+{
+ char *s1, *s2, *this;
+
+ s1 = *str;
+ if (s1 == NULL)
+ return;
+ /*
+ * If maxsize is 0 then set it to to the length of s1, because we have
+ * to duplicate s1. XXX we maybe should double-check whether the match
+ * appears in s1. If it doesn't, then we also have to set the length
+ * to the length of s1, to avoid modifying the argument. It may make
+ * sense to check if maxsize is <= strlen(s1), because in that case we
+ * want to return the unmodified string, too.
+ */
+ if (maxsize == 0) {
+ match = NULL;
+ maxsize = strlen(s1) + 1;
+ }
+ s2 = calloc(maxsize, 1);
+ if (s2 == NULL)
+ err(1, "calloc");
+
+ if (replstr == NULL)
+ replstr = "";
+
+ if (match == NULL || replstr == NULL || maxsize == strlen(s1)) {
+ (void)strlcpy(s2, s1, maxsize);
+ goto done;
+ }
+
+ for (;;) {
+ this = strstr(s1, match);
+ if (this == NULL)
+ break;
+ if ((strlen(s2) + strlen(s1) + strlen(replstr) -
+ strlen(match) + 1) > maxsize) {
+ (void)strlcat(s2, s1, maxsize);
+ goto done;
+ }
+ (void)strncat(s2, s1, (uintptr_t)this - (uintptr_t)s1);
+ (void)strcat(s2, replstr);
+ s1 = this + strlen(match);
+ }
+ (void)strcat(s2, s1);
+done:
+ *str = s2;
+ return;
+}
+
+#ifdef TEST
+#include <stdio.h>
+
+int
+main(void)
+{
+ char *x, *y, *z, *za;
+
+ x = "{}%$";
+ strnsubst(&x, "%$", "{} enpury!", 255);
+ y = x;
+ strnsubst(&y, "}{}", "ybir", 255);
+ z = y;
+ strnsubst(&z, "{", "v ", 255);
+ za = z;
+ strnsubst(&z, NULL, za, 255);
+ if (strcmp(z, "v ybir enpury!") == 0)
+ (void)printf("strnsubst() seems to work!\n");
+ else
+ (void)printf("strnsubst() is broken.\n");
+ (void)printf("%s\n", z);
+ free(x);
+ free(y);
+ free(z);
+ free(za);
+ return 0;
+}
+#endif
diff --git a/usr.bin/xargs/xargs.1 b/usr.bin/xargs/xargs.1
new file mode 100644
index 0000000..d693838
--- /dev/null
+++ b/usr.bin/xargs/xargs.1
@@ -0,0 +1,387 @@
+.\" $NetBSD: xargs.1,v 1.23 2012/10/13 14:18:17 njoly Exp $
+.\"
+.\" Copyright (c) 1990, 1991, 1993
+.\" The Regents of the University of California. All rights reserved.
+.\"
+.\" This code is derived from software contributed to Berkeley by
+.\" John B. Roll Jr. and the Institute of Electrical and Electronics
+.\" Engineers, Inc.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\" notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must reproduce the above copyright
+.\" notice, this list of conditions and the following disclaimer in the
+.\" documentation and/or other materials provided with the distribution.
+.\" 3. Neither the name of the University nor the names of its contributors
+.\" may be used to endorse or promote products derived from this software
+.\" without specific prior written permission.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+.\" ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+.\" SUCH DAMAGE.
+.\"
+.\" @(#)xargs.1 8.1 (Berkeley) 6/6/93
+.\" $FreeBSD: src/usr.bin/xargs/xargs.1,v 1.40 2010/12/11 08:32:16 joel Exp $
+.\" $xMach: xargs.1,v 1.2 2002/02/23 05:23:37 tim Exp $
+.\"
+.Dd December 21, 2010
+.Dt XARGS 1
+.Os
+.Sh NAME
+.Nm xargs
+.Nd "construct argument list(s) and execute utility"
+.Sh SYNOPSIS
+.Nm
+.Op Fl 0oprt
+.Op Fl E Ar eofstr
+.Oo
+.Fl I Ar replstr
+.Op Fl R Ar replacements
+.Op Fl S Ar replsize
+.Oc
+.Op Fl J Ar replstr
+.Op Fl L Ar number
+.Oo
+.Fl n Ar number
+.Op Fl x
+.Oc
+.Op Fl P Ar maxprocs
+.Op Fl s Ar size
+.Op Ar utility Op Ar argument ...
+.Sh DESCRIPTION
+The
+.Nm
+utility reads space, tab, newline and end-of-file delimited strings
+from the standard input and executes
+.Ar utility
+with the strings as
+arguments.
+.Pp
+Any arguments specified on the command line are given to
+.Ar utility
+upon each invocation, followed by some number of the arguments read
+from the standard input of
+.Nm .
+This is repeated until standard input is exhausted.
+.Pp
+Spaces, tabs and newlines may be embedded in arguments using single
+(``\ '\ '')
+or double (``"'') quotes or backslashes (``\e'').
+Single quotes escape all non-single quote characters, excluding newlines,
+up to the matching single quote.
+Double quotes escape all non-double quote characters, excluding newlines,
+up to the matching double quote.
+Any single character, including newlines, may be escaped by a backslash.
+.Pp
+The options are as follows:
+.Bl -tag -width indent
+.It Fl 0
+Change
+.Nm
+to expect NUL
+(``\e0'')
+characters as separators, instead of spaces and newlines.
+This is expected to be used in concert with the
+.Fl print0
+function in
+.Xr find 1 .
+.It Fl E Ar eofstr
+Use
+.Ar eofstr
+as a logical EOF marker.
+.It Fl I Ar replstr
+Execute
+.Ar utility
+for each input line, replacing one or more occurrences of
+.Ar replstr
+in up to
+.Ar replacements
+(or 5 if no
+.Fl R
+flag is specified) arguments to
+.Ar utility
+with the entire line of input.
+The resulting arguments, after replacement is done, will not be allowed to grow
+beyond
+.Ar replsize
+(or 255 if no
+.Fl S
+flag is specified)
+bytes; this is implemented by concatenating as much of the argument
+containing
+.Ar replstr
+as possible, to the constructed arguments to
+.Ar utility ,
+up to
+.Ar replsize
+bytes.
+The size limit does not apply to arguments to
+.Ar utility
+which do not contain
+.Ar replstr ,
+and furthermore, no replacement will be done on
+.Ar utility
+itself.
+Implies
+.Fl x .
+.It Fl J Ar replstr
+If this option is specified,
+.Nm
+will use the data read from standard input to replace the first occurrence of
+.Ar replstr
+instead of appending that data after all other arguments.
+This option will not affect how many arguments will be read from input
+.Pq Fl n ,
+or the size of the command(s)
+.Nm
+will generate
+.Pq Fl s .
+The option just moves where those arguments will be placed in the command(s)
+that are executed.
+The
+.Ar replstr
+must show up as a distinct
+.Ar argument
+to
+.Nm .
+It will not be recognized if, for instance, it is in the middle of a
+quoted string.
+Furthermore, only the first occurrence of the
+.Ar replstr
+will be replaced.
+For example, the following command will copy the list of files and
+directories which start with an uppercase letter in the current
+directory to
+.Pa destdir :
+.Pp
+.Dl /bin/ls -1d [A-Z]* | xargs -J % cp -rp % destdir
+.It Fl L Ar number
+Call
+.Ar utility
+for every
+.Ar number
+lines read.
+If EOF is reached and fewer lines have been read than
+.Ar number
+then
+.Ar utility
+will be called with the available lines.
+.It Fl n Ar number
+Set the maximum number of arguments taken from standard input for each
+invocation of
+.Ar utility .
+An invocation of
+.Ar utility
+will use less than
+.Ar number
+standard input arguments if the number of bytes accumulated (see the
+.Fl s
+option) exceeds the specified
+.Ar size
+or there are fewer than
+.Ar number
+arguments remaining for the last invocation of
+.Ar utility .
+The current default value for
+.Ar number
+is 5000.
+.It Fl o
+Reopen stdin as
+.Pa /dev/tty
+in the child process before executing the command.
+This is useful if you want
+.Nm
+to run an interactive application.
+.It Fl P Ar maxprocs
+Parallel mode: run at most
+.Ar maxprocs
+invocations of
+.Ar utility
+at once.
+.It Fl p
+Echo each command to be executed and ask the user whether it should be
+executed.
+An affirmative response,
+.Ql y
+in the POSIX locale,
+causes the command to be executed, any other response causes it to be
+skipped.
+No commands are executed if the process is not attached to a terminal.
+.It Fl r
+Compatibility with GNU
+.Nm .
+The GNU version of
+.Nm
+runs the
+.Ar utility
+argument at least once, even if
+.Nm
+input is empty, and it supports a
+.Fl r
+option to inhibit this behavior.
+The
+.Nx
+version of
+.Nm
+does not run the
+.Ar utility
+argument on empty input, but it supports the
+.Fl r
+option for command-line compatibility with GNU
+.Nm ;
+but the
+.Fl r
+option does nothing in the
+.Nx
+version of
+.Nm .
+.It Fl R Ar replacements
+Specify the maximum number of arguments that
+.Fl I
+will do replacement in.
+If
+.Ar replacements
+is negative, the number of arguments in which to replace is unbounded.
+.It Fl S Ar replsize
+Specify the amount of space (in bytes) that
+.Fl I
+can use for replacements.
+The default for
+.Ar replsize
+is 255.
+.It Fl s Ar size
+Set the maximum number of bytes for the command line length provided to
+.Ar utility .
+The sum of the length of the utility name, the arguments passed to
+.Ar utility
+(including
+.Dv NULL
+terminators) and the current environment will be less than or equal to
+this number.
+The current default value for
+.Ar size
+is
+.Dv ARG_MAX
+- 4096.
+.It Fl t
+Echo the command to be executed to standard error immediately before it
+is executed.
+.It Fl x
+Force
+.Nm
+to terminate immediately if a command line containing
+.Ar number
+arguments will not fit in the specified (or default) command line length.
+.El
+.Pp
+If
+.Ar utility
+is omitted,
+.Xr echo 1
+is used.
+.Pp
+Undefined behavior may occur if
+.Ar utility
+reads from the standard input.
+.Pp
+The
+.Nm
+utility exits immediately (without processing any further input) if a
+command line cannot be assembled,
+.Ar utility
+cannot be invoked, an invocation of
+.Ar utility
+is terminated by a signal,
+or an invocation of
+.Ar utility
+exits with a value of 255.
+.Sh FILES
+.Bl -tag -width /dev/tty -compact
+.It Pa /dev/tty
+used to read responses in prompt mode
+.El
+.Sh EXIT STATUS
+.Nm
+exits with one of the following values:
+.Bl -tag -width Ds -compact
+.It 0
+All invocations of
+.Ar utility
+returned a zero exit status.
+.It 123
+One or more invocations of
+.Ar utility
+returned a nonzero exit status.
+.It 124
+The
+.Ar utility
+exited with a 255 exit status.
+.It 125
+The
+.Ar utility
+was killed or stopped by a signal.
+.It 126
+The
+.Ar utility
+was found but could not be invoked.
+.It 127
+The
+.Ar utility
+could not be found.
+.It 1
+Some other error occurred.
+.El
+.Sh SEE ALSO
+.Xr echo 1 ,
+.Xr find 1 ,
+.Xr execvp 3
+.Sh STANDARDS
+The
+.Nm
+utility is expected to be
+.St -p1003.2
+compliant.
+The
+.Fl J , o , P , R ,
+and
+.Fl S
+options are non-standard
+.Fx
+extensions which may not be available on other operating systems.
+.Sh HISTORY
+The
+.Nm
+utility appeared in PWB UNIX 1.0.
+It made its first BSD appearance in the 4.3 Reno release.
+.Pp
+The meaning of 123, 124, and 125 exit values and the
+.Fl 0
+option were taken from GNU xargs.
+.Sh BUGS
+If
+.Ar utility
+attempts to invoke another command such that the number of arguments or the
+size of the environment is increased, it risks
+.Xr execvp 3
+failing with
+.Er E2BIG .
+.Pp
+The
+.Nm
+utility does not take multibyte characters into account when performing
+string comparisons for the
+.Fl I
+and
+.Fl J
+options, which may lead to incorrect results in some locales.
diff --git a/usr.bin/xargs/xargs.c b/usr.bin/xargs/xargs.c
new file mode 100644
index 0000000..5c89377
--- /dev/null
+++ b/usr.bin/xargs/xargs.c
@@ -0,0 +1,652 @@
+/* $NetBSD: xargs.c,v 1.20 2010/12/17 11:32:57 plunky Exp $ */
+
+/*-
+ * Copyright (c) 1990, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * John B. Roll Jr.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * $xMach: xargs.c,v 1.6 2002/02/23 05:27:47 tim Exp $
+ */
+
+#include <sys/cdefs.h>
+#ifndef lint
+__COPYRIGHT("@(#) Copyright (c) 1990, 1993\
+ The Regents of the University of California. All rights reserved.");
+#if 0
+static char sccsid[] = "@(#)xargs.c 8.1 (Berkeley) 6/6/93";
+__FBSDID("$FreeBSD: src/usr.bin/xargs/xargs.c,v 1.62 2006/01/01 22:59:54 jmallett Exp $");
+#endif
+__RCSID("$NetBSD: xargs.c,v 1.20 2010/12/17 11:32:57 plunky Exp $");
+#endif /* not lint */
+
+#include <sys/param.h>
+#include <sys/wait.h>
+
+#include <err.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <langinfo.h>
+#include <locale.h>
+#include <paths.h>
+#include <regex.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <signal.h>
+#include <unistd.h>
+
+#include "pathnames.h"
+
+static void parse_input(int, char *[]);
+static void prerun(int, char *[]);
+static int prompt(void);
+static void run(char **);
+static void usage(void) __dead;
+void strnsubst(char **, const char *, const char *, size_t);
+static void waitchildren(const char *, int);
+
+static char echo[] = _PATH_ECHO;
+static char **av, **bxp, **ep, **endxp, **xp;
+static char *argp, *bbp, *ebp, *inpline, *p, *replstr;
+static const char *eofstr;
+static int count, insingle, indouble, oflag, pflag, tflag, Rflag, rval, zflag;
+static int cnt, Iflag, jfound, Lflag, Sflag, wasquoted, xflag;
+static int curprocs, maxprocs;
+
+static volatile int childerr;
+
+extern char **environ;
+
+int
+main(int argc, char *argv[])
+{
+ long arg_max;
+ int ch, Jflag, nargs, nflag, nline;
+ size_t linelen;
+ char *endptr;
+
+ setprogname(argv[0]);
+
+ inpline = replstr = NULL;
+ ep = environ;
+ eofstr = "";
+ Jflag = nflag = 0;
+
+ (void)setlocale(LC_ALL, "");
+
+ /*
+ * SUSv3 says of the exec family of functions:
+ * The number of bytes available for the new process'
+ * combined argument and environment lists is {ARG_MAX}. It
+ * is implementation-defined whether null terminators,
+ * pointers, and/or any alignment bytes are included in this
+ * total.
+ *
+ * SUSv3 says of xargs:
+ * ... the combined argument and environment lists ...
+ * shall not exceed {ARG_MAX}-2048.
+ *
+ * To be conservative, we use ARG_MAX - 4K, and we do include
+ * nul terminators and pointers in the calculation.
+ *
+ * Given that the smallest argument is 2 bytes in length, this
+ * means that the number of arguments is limited to:
+ *
+ * (ARG_MAX - 4K - LENGTH(env + utility + arguments)) / 2.
+ *
+ * We arbitrarily limit the number of arguments to 5000. This is
+ * allowed by POSIX.2 as long as the resulting minimum exec line is
+ * at least LINE_MAX. Realloc'ing as necessary is possible, but
+ * probably not worthwhile.
+ */
+ nargs = 5000;
+ if ((arg_max = sysconf(_SC_ARG_MAX)) == -1)
+ errx(1, "sysconf(_SC_ARG_MAX) failed");
+ nline = arg_max - 4 * 1024;
+ while (*ep != NULL) {
+ /* 1 byte for each '\0' */
+ nline -= strlen(*ep++) + 1 + sizeof(*ep);
+ }
+ maxprocs = 1;
+ while ((ch = getopt(argc, argv, "0E:I:J:L:n:oP:pR:S:s:rtx")) != -1)
+ switch (ch) {
+ case 'E':
+ eofstr = optarg;
+ break;
+ case 'I':
+ Jflag = 0;
+ Iflag = 1;
+ Lflag = 1;
+ replstr = optarg;
+ break;
+ case 'J':
+ Iflag = 0;
+ Jflag = 1;
+ replstr = optarg;
+ break;
+ case 'L':
+ Lflag = atoi(optarg);
+ break;
+ case 'n':
+ nflag = 1;
+ if ((nargs = atoi(optarg)) <= 0)
+ errx(1, "illegal argument count");
+ break;
+ case 'o':
+ oflag = 1;
+ break;
+ case 'P':
+ if ((maxprocs = atoi(optarg)) <= 0)
+ errx(1, "max. processes must be >0");
+ break;
+ case 'p':
+ pflag = 1;
+ break;
+ case 'R':
+ Rflag = strtol(optarg, &endptr, 10);
+ if (*endptr != '\0')
+ errx(1, "replacements must be a number");
+ break;
+ case 'r':
+ /* GNU compatibility */
+ break;
+ case 'S':
+ Sflag = strtoul(optarg, &endptr, 10);
+ if (*endptr != '\0')
+ errx(1, "replsize must be a number");
+ break;
+ case 's':
+ nline = atoi(optarg);
+ break;
+ case 't':
+ tflag = 1;
+ break;
+ case 'x':
+ xflag = 1;
+ break;
+ case '0':
+ zflag = 1;
+ break;
+ case '?':
+ default:
+ usage();
+ }
+ argc -= optind;
+ argv += optind;
+
+ if (!Iflag && Rflag)
+ usage();
+ if (!Iflag && Sflag)
+ usage();
+ if (Iflag && !Rflag)
+ Rflag = 5;
+ if (Iflag && !Sflag)
+ Sflag = 255;
+ if (xflag && !nflag)
+ usage();
+ if (Iflag || Lflag)
+ xflag = 1;
+ if (replstr != NULL && *replstr == '\0')
+ errx(1, "replstr may not be empty");
+
+ /*
+ * Allocate pointers for the utility name, the utility arguments,
+ * the maximum arguments to be read from stdin and the trailing
+ * NULL.
+ */
+ linelen = 1 + argc + nargs + 1;
+ if ((av = bxp = malloc(linelen * sizeof(char **))) == NULL)
+ errx(1, "malloc failed");
+
+ /*
+ * Use the user's name for the utility as argv[0], just like the
+ * shell. Echo is the default. Set up pointers for the user's
+ * arguments.
+ */
+ if (*argv == NULL)
+ cnt = strlen(*bxp++ = echo);
+ else {
+ do {
+ if (Jflag && strcmp(*argv, replstr) == 0) {
+ char **avj;
+ jfound = 1;
+ argv++;
+ for (avj = argv; *avj; avj++)
+ cnt += strlen(*avj) + 1;
+ break;
+ }
+ cnt += strlen(*bxp++ = *argv) + 1;
+ } while (*++argv != NULL);
+ }
+
+ /*
+ * Set up begin/end/traversing pointers into the array. The -n
+ * count doesn't include the trailing NULL pointer, so the malloc
+ * added in an extra slot.
+ */
+ endxp = (xp = bxp) + nargs;
+
+ /*
+ * Allocate buffer space for the arguments read from stdin and the
+ * trailing NULL. Buffer space is defined as the default or specified
+ * space, minus the length of the utility name and arguments. Set up
+ * begin/end/traversing pointers into the array. The -s count does
+ * include the trailing NULL, so the malloc didn't add in an extra
+ * slot.
+ */
+ nline -= cnt;
+ if (nline <= 0)
+ errx(1, "insufficient space for command");
+
+ if ((bbp = malloc((size_t)(nline + 1))) == NULL)
+ errx(1, "malloc failed");
+ ebp = (argp = p = bbp) + nline - 1;
+ for (;;)
+ parse_input(argc, argv);
+}
+
+static void
+parse_input(int argc, char *argv[])
+{
+ int ch, foundeof;
+ char **avj;
+
+ foundeof = 0;
+
+ switch (ch = getchar()) {
+ case EOF:
+ /* No arguments since last exec. */
+ if (p == bbp) {
+ waitchildren(*argv, 1);
+ exit(rval);
+ }
+ goto arg1;
+ case ' ':
+ case '\t':
+ /* Quotes escape tabs and spaces. */
+ if (insingle || indouble || zflag)
+ goto addch;
+ goto arg2;
+ case '\0':
+ if (zflag) {
+ /*
+ * Increment 'count', so that nulls will be treated
+ * as end-of-line, as well as end-of-argument. This
+ * is needed so -0 works properly with -I and -L.
+ */
+ count++;
+ goto arg2;
+ }
+ goto addch;
+ case '\n':
+ if (zflag)
+ goto addch;
+ count++; /* Indicate end-of-line (used by -L) */
+
+ /* Quotes do not escape newlines. */
+arg1: if (insingle || indouble)
+ errx(1, "unterminated quote");
+arg2:
+ foundeof = *eofstr != '\0' &&
+ strncmp(argp, eofstr, (size_t)(p - argp)) == 0;
+
+ /* Do not make empty args unless they are quoted */
+ if ((argp != p || wasquoted) && !foundeof) {
+ *p++ = '\0';
+ *xp++ = argp;
+ if (Iflag) {
+ size_t curlen;
+
+ if (inpline == NULL)
+ curlen = 0;
+ else {
+ /*
+ * If this string is not zero
+ * length, append a space for
+ * separation before the next
+ * argument.
+ */
+ if ((curlen = strlen(inpline)) != 0)
+ (void)strcat(inpline, " ");
+ }
+ curlen++;
+ /*
+ * Allocate enough to hold what we will
+ * be holding in a second, and to append
+ * a space next time through, if we have
+ * to.
+ */
+ inpline = realloc(inpline, curlen + 2 +
+ strlen(argp));
+ if (inpline == NULL)
+ errx(1, "realloc failed");
+ if (curlen == 1)
+ (void)strcpy(inpline, argp);
+ else
+ (void)strcat(inpline, argp);
+ }
+ }
+
+ /*
+ * If max'd out on args or buffer, or reached EOF,
+ * run the command. If xflag and max'd out on buffer
+ * but not on args, object. Having reached the limit
+ * of input lines, as specified by -L is the same as
+ * maxing out on arguments.
+ */
+ if (xp == endxp || p > ebp || ch == EOF ||
+ (Lflag <= count && xflag) || foundeof) {
+ if (xflag && xp != endxp && p > ebp)
+ errx(1, "insufficient space for arguments");
+ if (jfound) {
+ for (avj = argv; *avj; avj++)
+ *xp++ = *avj;
+ }
+ prerun(argc, av);
+ if (ch == EOF || foundeof) {
+ waitchildren(*argv, 1);
+ exit(rval);
+ }
+ p = bbp;
+ xp = bxp;
+ count = 0;
+ }
+ argp = p;
+ wasquoted = 0;
+ break;
+ case '\'':
+ if (indouble || zflag)
+ goto addch;
+ insingle = !insingle;
+ wasquoted = 1;
+ break;
+ case '"':
+ if (insingle || zflag)
+ goto addch;
+ indouble = !indouble;
+ wasquoted = 1;
+ break;
+ case '\\':
+ if (zflag)
+ goto addch;
+ /* Backslash escapes anything, is escaped by quotes. */
+ if (!insingle && !indouble && (ch = getchar()) == EOF)
+ errx(1, "backslash at EOF");
+ /* FALLTHROUGH */
+ default:
+addch: if (p < ebp) {
+ *p++ = ch;
+ break;
+ }
+
+ /* If only one argument, not enough buffer space. */
+ if (bxp == xp)
+ errx(1, "insufficient space for argument");
+ /* Didn't hit argument limit, so if xflag object. */
+ if (xflag)
+ errx(1, "insufficient space for arguments");
+
+ if (jfound) {
+ for (avj = argv; *avj; avj++)
+ *xp++ = *avj;
+ }
+ prerun(argc, av);
+ xp = bxp;
+ cnt = ebp - argp;
+ (void)memcpy(bbp, argp, (size_t)cnt);
+ p = (argp = bbp) + cnt;
+ *p++ = ch;
+ break;
+ }
+}
+
+/*
+ * Do things necessary before run()'ing, such as -I substitution,
+ * and then call run().
+ */
+static void
+prerun(int argc, char *argv[])
+{
+ char **tmp, **tmp2, **avj;
+ int repls;
+
+ repls = Rflag;
+
+ if (argc == 0 || repls == 0) {
+ *xp = NULL;
+ run(argv);
+ return;
+ }
+
+ avj = argv;
+
+ /*
+ * Allocate memory to hold the argument list, and
+ * a NULL at the tail.
+ */
+ tmp = malloc((argc + 1) * sizeof(char**));
+ if (tmp == NULL)
+ errx(1, "malloc failed");
+ tmp2 = tmp;
+
+ /*
+ * Save the first argument and iterate over it, we
+ * cannot do strnsubst() to it.
+ */
+ if ((*tmp++ = strdup(*avj++)) == NULL)
+ errx(1, "strdup failed");
+
+ /*
+ * For each argument to utility, if we have not used up
+ * the number of replacements we are allowed to do, and
+ * if the argument contains at least one occurrence of
+ * replstr, call strnsubst(), else just save the string.
+ * Iterations over elements of avj and tmp are done
+ * where appropriate.
+ */
+ while (--argc) {
+ *tmp = *avj++;
+ if (repls && strstr(*tmp, replstr) != NULL) {
+ strnsubst(tmp++, replstr, inpline, (size_t)Sflag);
+ if (repls > 0)
+ repls--;
+ } else {
+ if ((*tmp = strdup(*tmp)) == NULL)
+ errx(1, "strdup failed");
+ tmp++;
+ }
+ }
+
+ /*
+ * Run it.
+ */
+ *tmp = NULL;
+ run(tmp2);
+
+ /*
+ * Walk from the tail to the head, free along the way.
+ */
+ for (; tmp2 != tmp; tmp--)
+ free(*tmp);
+ /*
+ * Now free the list itself.
+ */
+ free(tmp2);
+
+ /*
+ * Free the input line buffer, if we have one.
+ */
+ if (inpline != NULL) {
+ free(inpline);
+ inpline = NULL;
+ }
+}
+
+static void
+run(char **argv)
+{
+ int fd;
+ char **avec;
+
+ /*
+ * If the user wants to be notified of each command before it is
+ * executed, notify them. If they want the notification to be
+ * followed by a prompt, then prompt them.
+ */
+ if (tflag || pflag) {
+ (void)fprintf(stderr, "%s", *argv);
+ for (avec = argv + 1; *avec != NULL; ++avec)
+ (void)fprintf(stderr, " %s", *avec);
+ /*
+ * If the user has asked to be prompted, do so.
+ */
+ if (pflag)
+ /*
+ * If they asked not to exec, return without execution
+ * but if they asked to, go to the execution. If we
+ * could not open their tty, break the switch and drop
+ * back to -t behaviour.
+ */
+ switch (prompt()) {
+ case 0:
+ return;
+ case 1:
+ goto exec;
+ case 2:
+ break;
+ }
+ (void)fprintf(stderr, "\n");
+ (void)fflush(stderr);
+ }
+exec:
+ childerr = 0;
+ switch (vfork()) {
+ case -1:
+ err(1, "vfork");
+ /*NOTREACHED*/
+ case 0:
+ if (oflag) {
+ if ((fd = open(_PATH_TTY, O_RDONLY)) == -1)
+ err(1, "can't open /dev/tty");
+ } else {
+ fd = open(_PATH_DEVNULL, O_RDONLY);
+ }
+ if (fd > STDIN_FILENO) {
+ if (dup2(fd, STDIN_FILENO) != 0)
+ err(1, "can't dup2 to stdin");
+ (void)close(fd);
+ }
+ (void)execvp(argv[0], argv);
+ childerr = errno;
+ _exit(1);
+ }
+ curprocs++;
+ waitchildren(*argv, 0);
+}
+
+static void
+waitchildren(const char *name, int waitall)
+{
+ pid_t pid;
+ int status;
+
+ while ((pid = waitpid(-1, &status, !waitall && curprocs < maxprocs ?
+ WNOHANG : 0)) > 0) {
+ curprocs--;
+ /* If we couldn't invoke the utility, exit. */
+ if (childerr != 0) {
+ errno = childerr;
+ err(errno == ENOENT ? 127 : 126, "%s", name);
+ }
+ /*
+ * According to POSIX, we have to exit if the utility exits
+ * with a 255 status, or is interrupted by a signal. xargs
+ * is allowed to return any exit status between 1 and 125
+ * in these cases, but we'll use 124 and 125, the same
+ * values used by GNU xargs.
+ */
+ if (WIFEXITED(status)) {
+ if (WEXITSTATUS (status) == 255) {
+ warnx ("%s exited with status 255", name);
+ exit(124);
+ } else if (WEXITSTATUS (status) != 0) {
+ rval = 123;
+ }
+ } else if (WIFSIGNALED (status)) {
+ if (WTERMSIG(status) < NSIG) {
+ warnx("%s terminated by SIG%s", name,
+ sys_signame[WTERMSIG(status)]);
+ } else {
+ warnx("%s terminated by signal %d", name,
+ WTERMSIG(status));
+ }
+ exit(125);
+ }
+ }
+ if (pid == -1 && errno != ECHILD)
+ err(1, "waitpid");
+}
+
+/*
+ * Prompt the user about running a command.
+ */
+static int
+prompt(void)
+{
+ regex_t cre;
+ size_t rsize;
+ int match;
+ char *response;
+ FILE *ttyfp;
+
+ if ((ttyfp = fopen(_PATH_TTY, "r")) == NULL)
+ return (2); /* Indicate that the TTY failed to open. */
+ (void)fprintf(stderr, "?...");
+ (void)fflush(stderr);
+ if ((response = fgetln(ttyfp, &rsize)) == NULL ||
+ regcomp(&cre, nl_langinfo(YESEXPR), REG_BASIC) != 0) {
+ (void)fclose(ttyfp);
+ return (0);
+ }
+ response[rsize - 1] = '\0';
+ match = regexec(&cre, response, 0, NULL, 0);
+ (void)fclose(ttyfp);
+ regfree(&cre);
+ return (match == 0);
+}
+
+static void
+usage(void)
+{
+ (void)fprintf(stderr,
+"Usage: %s [-0opt] [-E eofstr] [-I replstr [-R replacements] [-S replsize]]\n"
+" [-J replstr] [-L number] [-n number [-x]] [-P maxprocs]\n"
+" [-s size] [utility [argument ...]]\n", getprogname());
+ exit(1);
+}
diff --git a/usr.sbin/link/link.8 b/usr.sbin/link/link.8
new file mode 100644
index 0000000..9b994ed
--- /dev/null
+++ b/usr.sbin/link/link.8
@@ -0,0 +1,64 @@
+.\" $NetBSD: link.8,v 1.8 2017/07/04 07:08:42 wiz Exp $
+.\"
+.\" Copyright (c) 1999 The NetBSD Foundation, Inc.
+.\" All rights reserved.
+.\"
+.\" This code is derived from software contributed to The NetBSD Foundation
+.\" by Klaus Klein.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\" notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must reproduce the above copyright
+.\" notice, this list of conditions and the following disclaimer in the
+.\" documentation and/or other materials provided with the distribution.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
+.\" ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+.\" TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+.\" PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
+.\" BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+.\" CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+.\" SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+.\" INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+.\" CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+.\" ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+.\" POSSIBILITY OF SUCH DAMAGE.
+.\"
+.Dd July 18, 1999
+.Dt LINK 8
+.Os
+.Sh NAME
+.Nm link
+.Nd call the
+.Fn link
+function
+.Sh SYNOPSIS
+.Nm
+.Ar file1
+.Ar file2
+.Sh DESCRIPTION
+The
+.Nm
+utility performs the function call
+.Fn link file1 file2 .
+.Pp
+.Ar file1
+must be the pathname of an existing file, and
+.Ar file2
+is the pathname of the new link to
+.Ar file1
+to be created.
+.Sh EXIT STATUS
+.Ex -std link
+.Sh SEE ALSO
+.Xr ln 1 ,
+.Xr link 2 ,
+.Xr unlink 8
+.Sh STANDARDS
+The
+.Nm
+utility conforms to
+.St -xcu5 .
diff --git a/usr.sbin/link/link.c b/usr.sbin/link/link.c
new file mode 100644
index 0000000..6a45cc1
--- /dev/null
+++ b/usr.sbin/link/link.c
@@ -0,0 +1,71 @@
+/* $NetBSD: link.c,v 1.5 2011/08/30 19:18:17 joerg Exp $ */
+
+/*-
+ * Copyright (c) 1999 The NetBSD Foundation, Inc.
+ * All rights reserved.
+ *
+ * This code is derived from software contributed to The NetBSD Foundation
+ * by Klaus Klein.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
+ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+#ifndef lint
+__COPYRIGHT(
+"@(#) Copyright (c) 1999\
+ The NetBSD Foundation, Inc. All rights reserved.");
+__RCSID("$NetBSD: link.c,v 1.5 2011/08/30 19:18:17 joerg Exp $");
+#endif
+
+#include <locale.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+__dead static void usage(void);
+
+int
+main(int argc, char *argv[])
+{
+
+ (void)setlocale(LC_ALL, "");
+
+ if (argc != 3)
+ usage();
+
+ if (link(argv[1], argv[2]) != 0) {
+ perror("link");
+ exit(EXIT_FAILURE);
+ }
+
+ exit(EXIT_SUCCESS);
+ /* NOTREACHED */
+}
+
+static void
+usage(void)
+{
+
+ (void)fprintf(stderr, "usage: link file1 file2\n");
+ exit(EXIT_FAILURE);
+}
diff --git a/usr.sbin/unlink/unlink.8 b/usr.sbin/unlink/unlink.8
new file mode 100644
index 0000000..a921140
--- /dev/null
+++ b/usr.sbin/unlink/unlink.8
@@ -0,0 +1,58 @@
+.\" $NetBSD: unlink.8,v 1.8 2017/07/04 07:13:43 wiz Exp $
+.\"
+.\" Copyright (c) 1999 The NetBSD Foundation, Inc.
+.\" All rights reserved.
+.\"
+.\" This code is derived from software contributed to The NetBSD Foundation
+.\" by Klaus Klein.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\" notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must reproduce the above copyright
+.\" notice, this list of conditions and the following disclaimer in the
+.\" documentation and/or other materials provided with the distribution.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
+.\" ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+.\" TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+.\" PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
+.\" BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+.\" CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+.\" SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+.\" INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+.\" CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+.\" ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+.\" POSSIBILITY OF SUCH DAMAGE.
+.\"
+.Dd July 18, 1998
+.Dt UNLINK 8
+.Os
+.Sh NAME
+.Nm unlink
+.Nd call the unlink function
+.Sh SYNOPSIS
+.Nm
+.Ar file
+.Sh DESCRIPTION
+The
+.Nm
+utility performs the function call
+.Fn unlink file .
+.Pp
+.Ar file
+must be the pathname of an existing file.
+.Sh EXIT STATUS
+.Ex -std unlink
+.Sh SEE ALSO
+.Xr rm 1 ,
+.Xr rmdir 1 ,
+.Xr unlink 2 ,
+.Xr link 8
+.Sh STANDARDS
+The
+.Nm
+utility conforms to
+.St -xcu5 .
diff --git a/usr.sbin/unlink/unlink.c b/usr.sbin/unlink/unlink.c
new file mode 100644
index 0000000..40d715d
--- /dev/null
+++ b/usr.sbin/unlink/unlink.c
@@ -0,0 +1,70 @@
+/* $NetBSD: unlink.c,v 1.5 2011/08/30 20:50:24 joerg Exp $ */
+
+/*-
+ * Copyright (c) 1999 The NetBSD Foundation, Inc.
+ * All rights reserved.
+ *
+ * This code is derived from software contributed to The NetBSD Foundation
+ * by Klaus Klein.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
+ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+#ifndef lint
+__COPYRIGHT("@(#) Copyright (c) 1999\
+ The NetBSD Foundation, Inc. All rights reserved.");
+__RCSID("$NetBSD: unlink.c,v 1.5 2011/08/30 20:50:24 joerg Exp $");
+#endif
+
+#include <locale.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+__dead static void usage(void);
+
+int
+main(int argc, char *argv[])
+{
+
+ (void)setlocale(LC_ALL, "");
+
+ if (argc != 2)
+ usage();
+
+ if (unlink(argv[1]) != 0) {
+ perror("unlink");
+ exit(EXIT_FAILURE);
+ }
+
+ exit(EXIT_SUCCESS);
+ /* NOTREACHED */
+}
+
+static void
+usage(void)
+{
+
+ (void)fprintf(stderr, "usage: unlink file\n");
+ exit(EXIT_FAILURE);
+}