summaryrefslogtreecommitdiff
path: root/bin
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 /bin
downloaduserland-5b57d28ffb6e1ef86b50f7d05d977826eae89bfe.tar.gz
userland-5b57d28ffb6e1ef86b50f7d05d977826eae89bfe.tar.bz2
userland-5b57d28ffb6e1ef86b50f7d05d977826eae89bfe.tar.xz
userland-5b57d28ffb6e1ef86b50f7d05d977826eae89bfe.zip
initial population
Diffstat (limited to 'bin')
-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
367 files changed, 79735 insertions, 0 deletions
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);
+}